[SCM] davmail packaging branch, master, updated. 02c2b101f323ee4dc652f941a0d9441a59cfa0da

Alexandre Rossi alexandre.rossi at gmail.com
Mon Oct 1 09:07:46 UTC 2012


The following commit has been merged in the master branch:
commit bfffebffcf7628bb03abce9df7566dd1ac81f594
Author: Alexandre Rossi <alexandre.rossi at gmail.com>
Date:   Mon Oct 1 10:30:39 2012 +0200

    Imported Upstream version 3.9.9-1976

diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..b3b941b
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,405 @@
+<project name="DavMail" default="dist" basedir=".">
+    <property file="user.properties"/>
+    <property name="version" value="3.9.9"/>
+
+    <path id="classpath">
+        <pathelement location="classes"/>
+        <fileset dir="lib">
+            <include name="*.jar"/>
+        </fileset>
+    </path>
+
+    <target name="clean">
+        <delete dir="target"/>
+        <delete dir="dist"/>
+        <delete file="build.log"/>
+    </target>
+
+    <condition property="is.windows">
+        <os family="windows"/>
+    </condition>
+    <condition property="is.svn">
+        <available file=".svn"/>
+    </condition>
+
+    <condition property="is.java6">
+        <equals arg1="${ant.java.version}" arg2="1.6"/>
+    </condition>
+
+    <target name="check-java6" unless="is.java6">
+        <fail message="Java 6 needed to build DavMail, current version is ${ant.java.version}, check JAVA_HOME"/>
+    </target>
+
+    <target name="svnrelease" if="is.svn">
+        <typedef resource="org/tigris/subversion/svnant/svnantlib.xml">
+            <classpath>
+                <fileset dir="svnant">
+                    <include name="*.jar"/>
+                </fileset>
+            </classpath>
+        </typedef>
+        <svn svnkit="true">
+            <wcversion path="."/>
+        </svn>
+        <!-- failover value for revision -->
+        <property name="revision.max" value=""/>
+        <property name="release" value="${version}-${revision.max}"/>
+    </target>
+
+    <target name="defaultrelease" unless="is.svn">
+        <property name="release" value="${version}"/>
+    </target>
+
+    <target name="init" depends="check-java6, svnrelease, defaultrelease">
+        <echo message="Creating DavMail ${release} dist package"/>
+        <mkdir dir="target/classes"/>
+    </target>
+
+    <target name="compile" depends="init">
+        <mkdir dir="target/classes"/>
+        <javac srcdir="src/java" destdir="target/classes" source="1.5" target="1.5" debug="on" encoding="UTF-8"
+               includeantruntime="false">
+            <classpath>
+                <path refid="classpath"/>
+            </classpath>
+        </javac>
+        <copy todir="target/classes">
+            <fileset dir="src/java">
+                <include name="**/*"/>
+                <exclude name="**/*.java"/>
+            </fileset>
+        </copy>
+    </target>
+
+    <target name="dist-nsis" if="is.windows">
+        <taskdef name="nsis" classname="net.sf.nsisant.Task">
+            <classpath location="lib/nsisant-1.2.jar"/>
+        </taskdef>
+        <nsis script="davmail-setup.nsi" verbosity="4" out="build.log" noconfig="yes">
+            <define name="VERSION" value="${release}"/>
+        </nsis>
+    </target>
+
+    <target name="dist-osx">
+        <taskdef name="jarbundler"
+                 classname="net.sourceforge.jarbundler.JarBundler">
+            <classpath location="lib/jarbundler-2.1.0.jar"/>
+        </taskdef>
+        <jarbundler dir="dist"
+                    shortname="DavMail"
+                    name="DavMail"
+                    mainclass="davmail.DavGateway"
+                    build="${release}"
+                    icon="src/osx/tray.icns"
+                    infostring="DavMail Gateway ${release}"
+                    jvmversion="1.5+"
+                    version="${release}"
+                    vmoptions="-Dsun.net.inetaddr.ttl=60 -Xmx512m"
+                    workingdirectory="$APP_PACKAGE"
+                    stubfile="src/osx/davmail">
+            <jarfileset dir="dist">
+                <include name="*.jar"/>
+            </jarfileset>
+            <jarfileset dir="dist/lib">
+                <include name="*.jar"/>
+                <include name="*.jnilib"/>
+                <exclude name="swt*.jar"/>
+                <exclude name="junit-*.jar"/>
+                <exclude name="winrun4j-*.jar"/>
+            </jarfileset>
+        </jarbundler>
+        <!-- prepare hide from dock option -->
+        <replaceregexp file="dist/DavMail.app/Contents/Info.plist" match="<key>CFBundleName</key>"
+                       replace="<key>LSUIElement</key><string>0</string><key>CFBundleName</key>"/>
+        <zip file="dist/DavMail-MacOSX-${release}.app.zip">
+            <zipfileset dir="dist">
+                <include name="DavMail.app/**/*"/>
+                <exclude name="DavMail.app/Contents/MacOS/davmail"/>
+            </zipfileset>
+            <zipfileset dir="dist" filemode="755">
+                <include name="DavMail.app/Contents/MacOS/davmail"/>
+            </zipfileset>
+        </zip>
+        <!--delete dir="dist/DavMail.app"/-->
+    </target>
+
+    <target name="dist-deb">
+        <taskdef resource="ant_deb_task.properties">
+            <classpath location="lib/ant-deb-0.0.1.jar"/>
+        </taskdef>
+        <desktopentry
+                toFile="dist/davmail.desktop"
+                name="DavMail"
+                comment="DavMail POP/SMTP/Caldav/LDAP Exchange Gateway"
+                exec="davmail"
+                icon="/usr/share/davmail/davmail.png"
+                categories="Network;"
+                />
+        <deb todir="dist"
+             package="davmail"
+             section="mail"
+             depends="openjdk-7-jre|openjdk-6-jre|sun-java6-jre,libswt-gtk-3-java|libswt-gtk-3.6-java|libswt-gtk-3.5-java|libswt-gtk-3.4-java">
+            <version upstream="${release}"/>
+            <maintainer email="mguessan at free.fr" name="Mickaël Guessant"/>
+            <description synopsis="DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway">
+                Ever wanted to get rid of Outlook ? DavMail is a POP/IMAP/SMTP/Caldav/Carddav/LDAP exchange
+                gateway allowing users to use any mail/calendar client (e.g. Thunderbird with Lightning or
+                Apple iCal) with an Exchange server, even from the internet or behind a firewall through
+                Outlook Web Access.
+                DavMail now includes an LDAP gateway to Exchange global address book and user personal
+                contacts to allow recipient address completion in mail compose window and full calendar
+                support with attendees free/busy display.
+                The main goal of DavMail is to provide standard compliant protocols in front of proprietary
+                Exchange. This means LDAP for global address book, SMTP to send messages, IMAP to browse
+                messages on the server in any folder, POP to retrieve inbox messages only, Caldav for
+                calendar support and Carddav for personal contacts sync.
+                Thus any standard compliant client can be used with Microsoft Exchange.
+                DavMail gateway is implemented in java and should run on any platform. Releases are tested
+                on Windows, Linux (Ubuntu) and Mac OSX. Tested successfully with the Iphone
+                (gateway running on a server).
+
+                http://davmail.sourceforge.net
+            </description>
+            <tarfileset dir="dist" prefix="usr/share/davmail">
+                <include name="lib/*.jar"/>
+                <include name="*.jar"/>
+                <!-- exclude swt jars from debian package -->
+                <exclude name="lib/swt*.jar"/>
+                <exclude name="lib/libgrowl-*.jar"/>
+            </tarfileset>
+            <tarfileset dir="src/bin" prefix="usr/bin" filemode="755">
+                <include name="davmail"/>
+            </tarfileset>
+            <tarfileset dir="dist" prefix="usr/share/davmail">
+                <include name="davmail.png"/>
+            </tarfileset>
+            <tarfileset dir="dist" prefix="usr/share/applications">
+                <include name="davmail.desktop"/>
+            </tarfileset>
+        </deb>
+    </target>
+
+    <target name='dist-rpm'>
+        <taskdef name='redline' classname='org.freecompany.redline.ant.RedlineTask'>
+            <classpath location="lib/redline-1.1.9.jar"/>
+        </taskdef>
+
+        <redline name='davmail'
+             summary='DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway'
+             version='${release}'
+             release='1'
+             license='GPL'
+             group='Applications/Internet'
+             sourcePackage='davmail-${release}.src.rpm'
+             description='Ever wanted to get rid of Outlook ? DavMail is a POP/IMAP/SMTP/Caldav/Carddav/LDAP exchange
+                gateway allowing users to use any mail/calendar client (e.g. Thunderbird with Lightning or
+                Apple iCal) with an Exchange server, even from the internet or behind a firewall through
+                Outlook Web Access.
+                DavMail now includes an LDAP gateway to Exchange global address book and user personal
+                contacts to allow recipient address completion in mail compose window and full calendar
+                support with attendees free/busy display.
+                The main goal of DavMail is to provide standard compliant protocols in front of proprietary
+                Exchange. This means LDAP for global address book, SMTP to send messages, IMAP to browse
+                messages on the server in any folder, POP to retrieve inbox messages only, Caldav for
+                calendar support and Carddav for personal contacts sync.
+                Thus any standard compliant client can be used with Microsoft Exchange.
+                DavMail gateway is implemented in java and should run on any platform. Releases are tested
+                on Windows, Linux (Ubuntu) and Mac OSX. Tested successfully with the Iphone
+                (gateway running on a server).'
+             url='http://davmail.sourceforge.net'
+             vendor='Mickael Guessant'
+             destination='dist'>
+            <depends name="jre" version="1.6"/>
+            <zipfileset dir="dist" prefix="usr/share/davmail">
+                <include name="lib/*.jar"/>
+                <include name="*.jar"/>
+                <!-- exclude swt jars from package -->
+                <exclude name="lib/swt*.jar"/>
+                <exclude name="lib/libgrowl-*.jar"/>
+            </zipfileset>
+            <zipfileset dir="src/bin" prefix="usr/bin" filemode="755">
+                <include name="davmail"/>
+            </zipfileset>
+            <zipfileset dir="dist" prefix="usr/share/davmail">
+                <include name="davmail.png"/>
+            </zipfileset>
+            <zipfileset dir="dist" prefix="usr/share/applications">
+                <include name="davmail.desktop"/>
+            </zipfileset>
+        </redline>
+
+    </target>
+
+
+    <target name="dist" depends="compile">
+        <property name="release-name" value="${release}-trunk"/>
+        <delete dir="dist"/>
+        <mkdir dir="dist"/>
+        <echo file="dist/version.txt" message="${release}"/>
+        <jar basedir="target/classes" destfile="dist/davmail.jar">
+            <manifest>
+                <section name="davmail/">
+                    <attribute name="Implementation-Title" value="DavMail Gateway"/>
+                    <attribute name="Implementation-Version" value="${release-name}"/>
+                    <attribute name="Implementation-Vendor" value="Mickael Guessant"/>
+                </section>
+            </manifest>
+        </jar>
+        <copy todir="dist/lib">
+            <fileset dir="lib">
+                <include name="*.jar"/>
+                <include name="*.jnilib"/>
+                <exclude name="nsisant*.jar"/>
+                <exclude name="jsmoothgen-ant-*.jar"/>
+                <exclude name="jarbundler-*.jar"/>
+                <exclude name="servlet-api.jar"/>
+                <exclude name="ant-deb-*.jar"/>
+                <exclude name="redline-*.jar"/>
+            </fileset>
+        </copy>
+        <copy file="src/java/tray48.png" tofile="dist/davmail.png"/>
+        <copy file="davmail.sh" todir="dist"/>
+        <taskdef name="jsmoothgen"
+                 classname="net.charabia.jsmoothgen.ant.JSmoothGen"
+                 classpathref="classpath"/>
+        <jsmoothgen project="davmail.jsmooth" skeletonroot="src/jsmooth/skeletons"/>
+        <jsmoothgen project="davmailconsole.jsmooth" skeletonroot="src/jsmooth/skeletons"/>
+        <!-- use WinRun4J to generate DavMail service -->
+        <copy file="src/winrun4j/davmailservice.exe" todir="dist"/>
+        <jsmoothgen project="davmail64.jsmooth" skeletonroot="src/jsmooth/skeletons"/>
+        <zip file="dist/davmail-${release-name}.zip">
+            <fileset dir="dist">
+                <include name="lib/*.jar"/>
+                <include name="*.jar"/>
+                <include name="davmail.desktop"/>
+                <include name="davmail.sh"/>
+                <!-- exclude swt jars from platform independent package -->
+                <exclude name="lib/swt*.jar"/>
+                <exclude name="lib/libgrowl-*.jar"/>
+                <exclude name="lib/junit-*.jar"/>
+                <exclude name="lib/winrun4j-*.jar"/>
+            </fileset>
+        </zip>
+        <copy todir="dist/web">
+            <fileset dir="src/web"/>
+        </copy>
+        <copy todir="dist/web/WEB-INF/lib">
+            <fileset dir="dist">
+                <include name="*.jar"/>
+            </fileset>
+            <fileset dir="dist/lib">
+                <include name="*.jar"/>
+                <exclude name="nsisant*.jar"/>
+                <exclude name="jsmoothgen-ant*.jar"/>
+                <exclude name="swt*.jar"/>
+                <exclude name="lib/libgrowl-*.jar"/>
+            </fileset>
+        </copy>
+        <jar destfile="dist/davmail-${release-name}.war">
+            <fileset dir="dist/web"/>
+        </jar>
+        <tar tarfile="dist/davmail-linux-x86-${release-name}.tgz" compression="gzip">
+            <tarfileset prefix="davmail-linux-x86-${release}" dir="dist" filemode="755">
+                <include name="davmail.sh"/>
+            </tarfileset>
+            <tarfileset prefix="davmail-linux-x86-${release}" dir="dist">
+                <include name="davmail.desktop"/>
+                <include name="lib/*.jar"/>
+                <include name="*.jar"/>
+                <include name="lib/swt-*-gtk-linux-x86.jar"/>
+                <exclude name="lib/swt-*-gtk-linux-x86_64.jar"/>
+                <exclude name="lib/swt-*-carbon-macosx.jar"/>
+                <exclude name="lib/swt-*-win32-*.jar"/>
+                <exclude name="lib/libgrowl-*.jar"/>
+            </tarfileset>
+        </tar>
+        <tar tarfile="dist/davmail-linux-x86_64-${release-name}.tgz" compression="gzip">
+            <tarfileset prefix="davmail-linux-x86_64-${release}" dir="dist" filemode="755">
+                <include name="davmail.sh"/>
+            </tarfileset>
+            <tarfileset prefix="davmail-linux-x86_64-${release}" dir="dist">
+                <include name="davmail.desktop"/>
+                <include name="lib/*.jar"/>
+                <include name="*.jar"/>
+                <include name="lib/swt-*-gtk-linux-x86_64.jar"/>
+                <exclude name="lib/swt-*-gtk-linux-x86.jar"/>
+                <exclude name="lib/swt-*-carbon-macosx.jar"/>
+                <exclude name="lib/swt-*-win32-*.jar"/>
+                <exclude name="lib/libgrowl-*.jar"/>
+            </tarfileset>
+        </tar>
+        <zip file="dist/davmail-${release-name}-windows-noinstall.zip">
+            <fileset dir="dist">
+                <include name="lib/*.jar"/>
+                <include name="*.jar"/>
+                <include name="davmail*.exe"/>
+                <exclude name="lib/swt-*-gtk-*.jar"/>
+                <exclude name="lib/libgrowl-*.jar"/>
+                <exclude name="lib/junit-*.jar"/>
+            </fileset>
+        </zip>
+        <antcall target="dist-nsis"/>
+        <antcall target="dist-deb"/>
+        <antcall target="dist-rpm"/>
+        <antcall target="dist-osx"/>
+        <!-- source package -->
+        <tar tarfile="dist/davmail-src-${release-name}.tgz" compression="gzip" longfile="gnu">
+            <tarfileset prefix="davmail-src-${release-name}" dir=".">
+                <include name="**/*"/>
+                <exclude name="build.log"/>
+                <exclude name="dist/**"/>
+                <exclude name="target/**"/>
+                <exclude name="archive/**"/>
+            </tarfileset>
+        </tar>
+    </target>
+
+    <target name='fix-site'>
+        <!-- fix site title generated by mvn site -->
+        <replaceregexp match="Maven - "
+                       replace="">
+            <fileset dir="target/site"/>
+        </replaceregexp>
+        <replaceregexp match="<a href="http://sourceforge.net/projects/davmail" id="bannerRight">"
+                       replace="<script type="text/javascript"><!-- ${line.separator}var pkBaseURL = (("https:" == document.location.protocol) ? "https://sourceforge.net/apps/piwik/davmail/" : "http://sourceforge.net/apps/piwik/davmail/"); ${line.separator}document.write(unescape("%3Cscript src='" + pkBaseURL + "piwik.js' type='text/javascript'%3E%3C/script%3E")); ${line.separator}//--></script>${line.separator}<script type="text/javascript">${line.separator}try {var piwikTracker = Piwik.getTracker(pkBaseURL + "piwik.php", 1);piwikTracker.trackPageView();piwikTracker.enableLinkTracking();}catch(err){}${line.separator}</script><noscript><p><img src="http://sourceforge.net/apps/piwik/davmail/piwik.php?idsite=1" style="border:0" alt=""/></p></noscript>">
+            <fileset dir="target/site"/>
+        </replaceregexp>
+    </target>
+
+    <target name='upload-site' depends="fix-site">
+        <scp todir="${username},davmail at web.sourceforge.net:htdocs"
+             keyfile="${keyfile}" passphrase="${passphrase}" verbose="true" trust="true">
+            <fileset dir="target/site"/>
+        </scp>
+    </target>
+
+    <target name="upload-release" depends="release">
+        <mkdir dir="dist/${version}"/>
+        <move todir="dist/${version}">
+            <fileset dir="dist">
+                <include name="*${version}-*"/>
+            </fileset>
+        </move>
+
+        <scp todir="${username},davmail at frs.sourceforge.net:/home/frs/project/d/da/davmail/davmail"
+             keyfile="${keyfile}" passphrase="${passphrase}" verbose="true" trust="true">
+            <fileset dir="dist">
+                <include name="${version}/*"/>
+            </fileset>
+        </scp>
+    </target>
+
+    <target name="upload-version">
+        <scp todir="${username},davmail at web.sourceforge.net:htdocs"
+             keyfile="${keyfile}" passphrase="${passphrase}" verbose="true">
+            <fileset dir="dist">
+                <include name="version.txt"/>
+            </fileset>
+        </scp>
+    </target>
+
+    <target name="release" depends="init">
+        <property name="release-name" value="${release}"/>
+        <antcall target="dist"/>
+    </target>
+</project>
diff --git a/davmail-setup.nsi b/davmail-setup.nsi
new file mode 100644
index 0000000..c7d2dd4
--- /dev/null
+++ b/davmail-setup.nsi
@@ -0,0 +1,208 @@
+; Script generated by the HM NIS Edit Script Wizard.
+
+; HM NIS Edit Wizard helper defines
+!define PRODUCT_NAME "DavMail"
+!define PRODUCT_VERSION "${VERSION}"
+!define PRODUCT_PUBLISHER "Mickaël Guessant"
+!define PRODUCT_WEB_SITE "http://sourceforge.net/projects/davmail"
+!define PRODUCT_DIR_REGKEY "Software\Microsoft\Windows\CurrentVersion\App Paths\davmail.exe"
+!define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
+!define PRODUCT_UNINST_ROOT_KEY "HKLM"
+
+; MUI 1.67 compatible ------
+!include "MUI.nsh"
+
+; MUI Settings
+!define MUI_ABORTWARNING
+!define MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\modern-install.ico"
+!define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\modern-uninstall.ico"
+
+; Language Selection Dialog Settings
+!define MUI_LANGDLL_REGISTRY_ROOT "${PRODUCT_UNINST_ROOT_KEY}"
+!define MUI_LANGDLL_REGISTRY_KEY "${PRODUCT_UNINST_KEY}"
+!define MUI_LANGDLL_REGISTRY_VALUENAME "NSIS:Language"
+
+; Welcome page
+!insertmacro MUI_PAGE_WELCOME
+; License page
+!insertmacro MUI_PAGE_LICENSE "src\license.txt"
+; Directory page
+!insertmacro MUI_PAGE_DIRECTORY
+; Instfiles page
+!insertmacro MUI_PAGE_INSTFILES
+; Finish page
+!define MUI_FINISHPAGE_RUN "$INSTDIR\davmail.exe"
+!insertmacro MUI_PAGE_FINISH
+
+; Uninstaller pages
+!insertmacro MUI_UNPAGE_INSTFILES
+
+; Language files
+!insertmacro MUI_LANGUAGE "English"
+!insertmacro MUI_LANGUAGE "French"
+
+; MUI end ------
+
+Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"
+OutFile "dist\davmail-${PRODUCT_VERSION}-setup.exe"
+InstallDir "$PROGRAMFILES\DavMail"
+InstallDirRegKey HKLM "${PRODUCT_DIR_REGKEY}" ""
+ShowInstDetails show
+ShowUnInstDetails show
+
+Function .onInit
+  !insertmacro MUI_LANGDLL_DISPLAY
+FunctionEnd
+
+Section
+Push $5
+loop:
+  push "davmail.exe"
+  processwork::existsprocess
+  pop $5
+  IntCmp $5 0 no_quest
+  MessageBox MB_RETRYCANCEL|MB_ICONSTOP 'DavMail must be closed during this installation.$\r$\n Close DavMail now, or press "Retry" to automatically close DavMail and continue or press "Cancel" to cancel the installation entirely.'  IDCANCEL BailOut
+  push "davmail.exe"
+  processwork::KillProcess
+  Sleep 2000
+Goto loop
+
+BailOut:
+  Abort
+
+no_quest:
+SectionEnd
+
+Section "MainSection" SEC01
+  SetOutPath "$INSTDIR"
+  SetOverwrite try
+  File "dist\davmail.exe"
+  CreateDirectory "$SMPROGRAMS\DavMail"
+  CreateShortCut "$SMPROGRAMS\DavMail\DavMail.lnk" "$INSTDIR\davmail.exe"
+  CreateShortCut "$SMPROGRAMS\DavMail\DavMail Console.lnk" "$INSTDIR\davmailconsole.exe"
+  CreateShortCut "$DESKTOP\DavMail.lnk" "$INSTDIR\davmail.exe"
+  File "dist\davmail.jar"
+  File "dist\davmailconsole.exe"
+  File "dist\davmailservice.exe"
+  File "dist\davmail64.exe"
+  SetOutPath "$INSTDIR\lib"
+  File "dist\lib\activation-1.1.1.jar"
+  File "dist\lib\commons-codec-1.3.jar"
+  File "dist\lib\commons-collections-3.1.jar"
+  File "dist\lib\commons-httpclient-3.1.jar"
+  File "dist\lib\commons-logging-1.0.4.jar"
+  File "dist\lib\htmlcleaner-2.1.jar"
+  File "dist\lib\jackrabbit-webdav-1.4.jar"
+  File "dist\lib\jcharset-1.3.jar"
+  File "dist\lib\jcifs-1.3.14.jar"
+  File "dist\lib\jdom-1.0.jar"
+  File "dist\lib\log4j-1.2.16.jar"
+  File "dist\lib\mail-1.4.3.jar"
+  File "dist\lib\slf4j-api-1.3.1.jar"
+  File "dist\lib\slf4j-log4j12-1.3.1.jar"
+  File "dist\lib\stax-api-1.0.1.jar"
+  File "dist\lib\stax2-api-3.1.1.jar"
+  File "dist\lib\swt-3.7-win32-x86.jar"
+  File "dist\lib\swt-3.7-win32-x86_64.jar"
+  File "dist\lib\winrun4j-0.4.4.jar"
+  File "dist\lib\woodstox-core-asl-4.1.2.jar"
+  File "dist\lib\xercesImpl-2.8.1.jar"
+
+  WriteRegStr HKEY_CURRENT_USER "Software\Microsoft\Windows\CurrentVersion\Run" "DavMail" "$INSTDIR\davmail.exe"
+SectionEnd
+
+Section -AdditionalIcons
+  SetOutPath $INSTDIR
+  WriteIniStr "$INSTDIR\${PRODUCT_NAME}.url" "InternetShortcut" "URL" "${PRODUCT_WEB_SITE}"
+  CreateShortCut "$SMPROGRAMS\DavMail\Website.lnk" "$INSTDIR\${PRODUCT_NAME}.url"
+  CreateShortCut "$SMPROGRAMS\DavMail\Uninstall.lnk" "$INSTDIR\uninst.exe"
+SectionEnd
+
+Section -Post
+  WriteUninstaller "$INSTDIR\uninst.exe"
+  WriteRegStr HKLM "${PRODUCT_DIR_REGKEY}" "" "$INSTDIR\davmail.exe"
+  WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayName" "$(^Name)"
+  WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "UninstallString" "$INSTDIR\uninst.exe"
+  WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayIcon" "$INSTDIR\davmail.exe"
+  WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayVersion" "${PRODUCT_VERSION}"
+  WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "URLInfoAbout" "${PRODUCT_WEB_SITE}"
+  WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "Publisher" "${PRODUCT_PUBLISHER}"
+SectionEnd
+
+
+Function un.onUninstSuccess
+  HideWindow
+  MessageBox MB_ICONINFORMATION|MB_OK "$(^Name) has been removed from your system."
+FunctionEnd
+
+Function un.onInit
+!insertmacro MUI_UNGETLANGUAGE
+  MessageBox MB_ICONQUESTION|MB_YESNO|MB_DEFBUTTON2 "Are you sure you want to completely remove $(^Name) and all of its components?" IDYES +2
+  Abort
+FunctionEnd
+
+Section Uninstall
+Push $5
+loop:
+  push "davmail.exe"
+  processwork::existsprocess
+  pop $5
+  IntCmp $5 0 no_quest
+  MessageBox MB_RETRYCANCEL|MB_ICONSTOP 'DavMail must be closed during this installation.$\r$\n Close DavMail now, or press "Retry" to automatically close DavMail and continue or press "Cancel" to cancel the installation entirely.'  IDCANCEL BailOut
+  push "davmail.exe"
+  processwork::KillProcess
+  Sleep 2000
+Goto loop
+
+BailOut:
+  Abort
+
+no_quest:
+  Delete "$INSTDIR\${PRODUCT_NAME}.url"
+  Delete "$INSTDIR\uninst.exe"
+  
+  Delete "$INSTDIR\lib\activation-1.1.1.jar"
+  Delete "$INSTDIR\lib\commons-codec-1.3.jar"
+  Delete "$INSTDIR\lib\commons-collections-3.1.jar"
+  Delete "$INSTDIR\lib\commons-httpclient-3.1.jar"
+  Delete "$INSTDIR\lib\commons-logging-1.0.4.jar"
+  Delete "$INSTDIR\lib\htmlcleaner-2.1.jar"
+  Delete "$INSTDIR\lib\jackrabbit-webdav-1.4.jar"
+  Delete "$INSTDIR\lib\jcharset-1.3.jar"
+  Delete "$INSTDIR\lib\jcifs-1.3.14.jar"
+  Delete "$INSTDIR\lib\jdom-1.0.jar"
+  Delete "$INSTDIR\lib\log4j-1.2.16.jar"
+  Delete "$INSTDIR\lib\mail-1.4.3.jar"
+  Delete "$INSTDIR\lib\slf4j-api-1.3.1.jar"
+  Delete "$INSTDIR\lib\slf4j-log4j12-1.3.1.jar"
+  Delete "$INSTDIR\lib\stax-api-1.0.1.jar"
+  Delete "$INSTDIR\lib\stax2-api-3.1.1.jar"
+  Delete "$INSTDIR\lib\swt-3.7-win32-x86.jar"
+  Delete "$INSTDIR\lib\swt-3.7-win32-x86_64.jar"  
+  Delete "$INSTDIR\lib\winrun4j-0.4.4.jar"
+  Delete "$INSTDIR\lib\woodstox-core-asl-4.1.2.jar"
+  Delete "$INSTDIR\lib\xercesImpl-2.8.1.jar"
+
+  Delete "$INSTDIR\davmail64.exe"
+  Delete "$INSTDIR\davmailservice.exe"
+  Delete "$INSTDIR\davmailconsole.exe"
+  Delete "$INSTDIR\davmail.log"
+  Delete "$INSTDIR\davmail.jar"
+  Delete "$INSTDIR\davmail.exe"
+
+  Delete "$SMPROGRAMS\DavMail\Uninstall.lnk"
+  Delete "$SMPROGRAMS\DavMail\Website.lnk"
+  Delete "$DESKTOP\DavMail.lnk"
+  Delete "$SMPROGRAMS\DavMail\DavMail.lnk"
+  Delete "$SMPROGRAMS\DavMail\DavMail Console.lnk"
+
+  RMDir "$SMPROGRAMS\DavMail"
+  RMDir "$INSTDIR\lib"
+  RMDir "$INSTDIR"
+
+  DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "DavMail"
+
+  DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}"
+  DeleteRegKey HKLM "${PRODUCT_DIR_REGKEY}"
+  SetAutoClose true
+SectionEnd
\ No newline at end of file
diff --git a/davmail.desktop b/davmail.desktop
new file mode 100644
index 0000000..2bf824f
--- /dev/null
+++ b/davmail.desktop
@@ -0,0 +1,9 @@
+[Desktop Entry]
+Version=1.0
+Encoding=UTF-8
+Name=DavMail
+Type=Application
+Terminal=false
+Exec=/usr/bin/java -cp davmail.jar:lib/* davmail.DavGateway
+Path=.
+Comment=DavMail POP/SMTP Webdav Exchange Gateway
diff --git a/davmail.jsmooth b/davmail.jsmooth
new file mode 100644
index 0000000..ebb7fd2
--- /dev/null
+++ b/davmail.jsmooth
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<jsmoothproject>
+<JVMSearchPath>registry</JVMSearchPath>
+<JVMSearchPath>javahome</JVMSearchPath>
+<JVMSearchPath>jrepath</JVMSearchPath>
+<JVMSearchPath>jdkpath</JVMSearchPath>
+<JVMSearchPath>exepath</JVMSearchPath>
+<JVMSearchPath>jview</JVMSearchPath>
+<arguments></arguments>
+<classPath>dist/davmail.jar</classPath>
+<classPath>dist/lib/activation-1.1.1.jar</classPath>
+<classPath>dist/lib/commons-codec-1.3.jar</classPath>
+<classPath>dist/lib/commons-collections-3.1.jar</classPath>
+<classPath>dist/lib/commons-httpclient-3.1.jar</classPath>
+<classPath>dist/lib/commons-logging-1.0.4.jar</classPath>
+<classPath>dist/lib/htmlcleaner-2.1.jar</classPath>
+<classPath>dist/lib/jackrabbit-webdav-1.4.jar</classPath>
+<classPath>dist/lib/jcharset-1.3.jar</classPath>
+<classPath>dist/lib/jcifs-1.3.14.jar</classPath>
+<classPath>dist/lib/jdom-1.0.jar</classPath>
+<classPath>dist/lib/log4j-1.2.16.jar</classPath>
+<classPath>dist/lib/mail-1.4.3.jar</classPath>
+<classPath>dist/lib/slf4j-api-1.3.1.jar</classPath>
+<classPath>dist/lib/slf4j-log4j12-1.3.1.jar</classPath>
+<classPath>dist/lib/stax-api-1.0.1.jar</classPath>
+<classPath>dist/lib/stax2-api-3.1.1.jar</classPath>
+<classPath>dist/lib/swt-3.7-win32-x86.jar</classPath>
+<classPath>dist/lib/woodstox-core-asl-4.1.2.jar</classPath>
+<classPath>dist/lib/xercesImpl-2.8.1.jar</classPath>
+<embeddedJar>false</embeddedJar>
+<executableName>dist/davmail.exe</executableName>
+<iconLocation>src/java/tray48.png</iconLocation>
+<initialMemoryHeap>-1</initialMemoryHeap>
+<javaProperties>
+<name>sun.net.inetaddr.ttl</name>
+<value>60</value>
+</javaProperties>
+<mainClassName>davmail.DavGateway</mainClassName>
+<maximumMemoryHeap>268435456</maximumMemoryHeap>
+<maximumVersion></maximumVersion>
+<minimumVersion>1.6</minimumVersion>
+<skeletonName>Autodownload Wrapper</skeletonName>
+<skeletonProperties>
+<key>Message</key>
+<value>Java has not been found on your computer. Do you want to download it?</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>DownloadURL</key>
+<value>http://java.sun.com/update/1.6.0/jinstall-6-windows-i586.cab</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>SingleProcess</key>
+<value>1</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>SingleInstance</key>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>JniSmooth</key>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>Debug</key>
+<value>0</value>
+</skeletonProperties>
+</jsmoothproject>
diff --git a/davmail.sh b/davmail.sh
new file mode 100644
index 0000000..5f7ee6d
--- /dev/null
+++ b/davmail.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+# Ubuntu setup instructions :
+# install java :
+# sudo apt-get install sun-java6-bin
+# launch davmail :
+BASE=`dirname $0`
+for i in $BASE/lib/*; do export CLASSPATH=$CLASSPATH:$i; done
+java -cp $BASE/davmail.jar:$CLASSPATH davmail.DavGateway $1
diff --git a/davmail64.jsmooth b/davmail64.jsmooth
new file mode 100644
index 0000000..8addd7c
--- /dev/null
+++ b/davmail64.jsmooth
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<jsmoothproject>
+<JVMSearchPath>registry</JVMSearchPath>
+<JVMSearchPath>javahome</JVMSearchPath>
+<JVMSearchPath>jrepath</JVMSearchPath>
+<JVMSearchPath>jdkpath</JVMSearchPath>
+<JVMSearchPath>exepath</JVMSearchPath>
+<JVMSearchPath>jview</JVMSearchPath>
+<arguments></arguments>
+<classPath>dist\davmail.jar</classPath>
+<classPath>dist\lib\activation-1.1.1.jar</classPath>
+<classPath>dist\lib\commons-codec-1.3.jar</classPath>
+<classPath>dist\lib\commons-collections-3.1.jar</classPath>
+<classPath>dist\lib\commons-httpclient-3.1.jar</classPath>
+<classPath>dist\lib\commons-logging-1.0.4.jar</classPath>
+<classPath>dist\lib\htmlcleaner-2.1.jar</classPath>
+<classPath>dist\lib\jackrabbit-webdav-1.4.jar</classPath>
+<classPath>dist\lib\jcharset-1.3.jar</classPath>
+<classPath>dist\lib\jcifs-1.3.14.jar</classPath>
+<classPath>dist\lib\jdom-1.0.jar</classPath>
+<classPath>dist\lib\log4j-1.2.16.jar</classPath>
+<classPath>dist\lib\mail-1.4.3.jar</classPath>
+<classPath>dist\lib\slf4j-api-1.3.1.jar</classPath>
+<classPath>dist\lib\slf4j-log4j12-1.3.1.jar</classPath>
+<classPath>dist\lib\stax-api-1.0.1.jar</classPath>
+<classPath>dist\lib\stax2-api-3.1.1.jar</classPath>
+<classPath>dist\lib\swt-3.7-win32-x86_64.jar</classPath>
+<classPath>dist\lib\woodstox-core-asl-4.1.2.jar</classPath>
+<classPath>dist\lib\xercesImpl-2.8.1.jar</classPath>
+<embeddedJar>false</embeddedJar>
+<executableName>dist\davmail64.exe</executableName>
+<iconLocation>src\java\tray48.png</iconLocation>
+<initialMemoryHeap>-1</initialMemoryHeap>
+<javaProperties>
+<name>sun.net.inetaddr.ttl</name>
+<value>60</value>
+</javaProperties>
+<mainClassName>davmail.DavGateway</mainClassName>
+<maximumMemoryHeap>268435456</maximumMemoryHeap>
+<maximumVersion></maximumVersion>
+<minimumVersion>1.6</minimumVersion>
+<skeletonName>Windowed Wrapper x64</skeletonName>
+<skeletonProperties>
+<key>Message</key>
+<value>Java has not been found on your computer. Do you want to download it?</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>URL</key>
+<value>http://www.java.com</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>SingleProcess</key>
+<value>1</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>SingleInstance</key>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>JniSmooth</key>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>Debug</key>
+<value>0</value>
+</skeletonProperties>
+</jsmoothproject>
diff --git a/davmailconsole.jsmooth b/davmailconsole.jsmooth
new file mode 100644
index 0000000..6b11532
--- /dev/null
+++ b/davmailconsole.jsmooth
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<jsmoothproject>
+<JVMSearchPath>registry</JVMSearchPath>
+<JVMSearchPath>javahome</JVMSearchPath>
+<JVMSearchPath>jrepath</JVMSearchPath>
+<JVMSearchPath>jdkpath</JVMSearchPath>
+<JVMSearchPath>exepath</JVMSearchPath>
+<JVMSearchPath>jview</JVMSearchPath>
+<arguments></arguments>
+<classPath>dist/davmail.jar</classPath>
+<classPath>dist/lib/activation-1.1.1.jar</classPath>
+<classPath>dist/lib/commons-codec-1.3.jar</classPath>
+<classPath>dist/lib/commons-collections-3.1.jar</classPath>
+<classPath>dist/lib/commons-httpclient-3.1.jar</classPath>
+<classPath>dist/lib/commons-logging-1.0.4.jar</classPath>
+<classPath>dist/lib/htmlcleaner-2.1.jar</classPath>
+<classPath>dist/lib/jackrabbit-webdav-1.4.jar</classPath>
+<classPath>dist/lib/jcharset-1.3.jar</classPath>
+<classPath>dist/lib/jcifs-1.3.14.jar</classPath>
+<classPath>dist/lib/jdom-1.0.jar</classPath>
+<classPath>dist/lib/log4j-1.2.16.jar</classPath>
+<classPath>dist/lib/mail-1.4.3.jar</classPath>
+<classPath>dist/lib/slf4j-api-1.3.1.jar</classPath>
+<classPath>dist/lib/slf4j-log4j12-1.3.1.jar</classPath>
+<classPath>dist/lib/stax-api-1.0.1.jar</classPath>
+<classPath>dist/lib/stax2-api-3.1.1.jar</classPath>
+<classPath>dist/lib/swt-3.7-win32-x86.jar</classPath>
+<classPath>dist/lib/woodstox-core-asl-4.1.2.jar</classPath>
+<classPath>dist/lib/xercesImpl-2.8.1.jar</classPath>
+<embeddedJar>false</embeddedJar>
+<executableName>dist/davmailconsole.exe</executableName>
+<iconLocation>src/java/tray32.png</iconLocation>
+<initialMemoryHeap>-1</initialMemoryHeap>
+<javaProperties>
+<name>sun.net.inetaddr.ttl</name>
+<value>60</value>
+</javaProperties>
+<mainClassName>davmail.DavGateway</mainClassName>
+<maximumMemoryHeap>268435456</maximumMemoryHeap>
+<maximumVersion></maximumVersion>
+<minimumVersion>1.5</minimumVersion>
+<skeletonName>Console Wrapper</skeletonName>
+<skeletonProperties>
+<key>Message</key>
+<value>Java has not been found on your computer. Do you want to download it?</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>PressKey</key>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>Debug</key>
+<value>0</value>
+</skeletonProperties>
+</jsmoothproject>
diff --git a/davmailservice.jsmooth b/davmailservice.jsmooth
new file mode 100644
index 0000000..bd78ac7
--- /dev/null
+++ b/davmailservice.jsmooth
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<jsmoothproject>
+<JVMSearchPath>registry</JVMSearchPath>
+<JVMSearchPath>javahome</JVMSearchPath>
+<JVMSearchPath>jrepath</JVMSearchPath>
+<JVMSearchPath>jdkpath</JVMSearchPath>
+<JVMSearchPath>exepath</JVMSearchPath>
+<JVMSearchPath>jview</JVMSearchPath>
+<arguments>davmail.properties</arguments>
+<classPath>dist/davmail.jar</classPath>
+<classPath>dist/lib/activation-1.1.1.jar</classPath>
+<classPath>dist/lib/commons-codec-1.3.jar</classPath>
+<classPath>dist/lib/commons-collections-3.1.jar</classPath>
+<classPath>dist/lib/commons-httpclient-3.1.jar</classPath>
+<classPath>dist/lib/commons-logging-1.0.4.jar</classPath>
+<classPath>dist/lib/htmlcleaner-2.1.jar</classPath>
+<classPath>dist/lib/jackrabbit-webdav-1.4.jar</classPath>
+<classPath>dist/lib/jcharset-1.3.jar</classPath>
+<classPath>dist/lib/jcifs-1.3.14.jar</classPath>
+<classPath>dist/lib/jdom-1.0.jar</classPath>
+<classPath>dist/lib/log4j-1.2.16.jar</classPath>
+<classPath>dist/lib/mail-1.4.3.jar</classPath>
+<classPath>dist/lib/slf4j-api-1.3.1.jar</classPath>
+<classPath>dist/lib/slf4j-log4j12-1.3.1.jar</classPath>
+<classPath>dist/lib/stax-api-1.0.1.jar</classPath>
+<classPath>dist/lib/stax2-api-3.1.1.jar</classPath>
+<classPath>dist/lib/swt-3.7-win32-x86.jar</classPath>
+<classPath>dist/lib/woodstox-core-asl-4.1.2.jar</classPath>
+<classPath>dist/lib/xercesImpl-2.8.1.jar</classPath>
+<currentDirectory>${EXECUTABLEPATH}</currentDirectory>
+<embeddedJar>false</embeddedJar>
+<executableName>dist/davmailservice.exe</executableName>
+<iconLocation>src/java/tray32.png</iconLocation>
+<initialMemoryHeap>-1</initialMemoryHeap>
+<javaProperties>
+<name>sun.net.inetaddr.ttl</name>
+<value>60</value>
+<name>-Xrs</name>
+<value></value>
+</javaProperties>
+<mainClassName>davmail.DavGateway</mainClassName>
+<maximumMemoryHeap>268435456</maximumMemoryHeap>
+<maximumVersion></maximumVersion>
+<minimumVersion>1.6</minimumVersion>
+<skeletonName>WinService Wrapper</skeletonName>
+<skeletonProperties>
+<key>ServiceName</key>
+<value>DavMail</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>ServiceDisplayName</key>
+<value>DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>Message</key>
+<value>Java has not been found on your computer. Do you want to download it?</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>Autostart</key>
+<value>1</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>Logfile</key>
+<value>davmailservice.log</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>Interactive</key>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>JniSmooth</key>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>Debug</key>
+<value>0</value>
+</skeletonProperties>
+</jsmoothproject>
diff --git a/debian/ant.properties b/debian/ant.properties
deleted file mode 100644
index 163300d..0000000
--- a/debian/ant.properties
+++ /dev/null
@@ -1,15 +0,0 @@
-# Build flags
-windows-dist = false
-installdir   = /usr/share/java
-
-# Deps
-commons-codec      = /usr/share/java/commons-codec.jar
-servlet-api        = /usr/share/java/servlet-api.jar
-commons-httpclient = /usr/share/java/commons-httpclient.jar
-htmlcleaner        = /usr/share/java/htmlcleaner.jar
-gnumail            = /usr/share/java/gnumail.jar
-jcifs              = /usr/share/java/jcifs.jar
-jackrabbit-webdav  = /usr/share/java/jackrabbit-webdav.jar
-woodstox-core      = /usr/share/java/woodstox-core-lgpl.jar
-stax2-api          = /usr/share/java/stax2-api.jar
-swt                = /usr/share/java/swt.jar
diff --git a/debian/changelog b/debian/changelog
deleted file mode 100644
index bf11f07..0000000
--- a/debian/changelog
+++ /dev/null
@@ -1,5 +0,0 @@
-davmail (3.9.9-1976-1~pre+1) unstable; urgency=low
-
-  * Initial release (Closes: #569668).
-
- -- Alexandre Rossi <alexandre.rossi at gmail.com>  Thu, 26 May 2011 15:33:26 +0200
diff --git a/debian/compat b/debian/compat
deleted file mode 100644
index 7f8f011..0000000
--- a/debian/compat
+++ /dev/null
@@ -1 +0,0 @@
-7
diff --git a/debian/control b/debian/control
deleted file mode 100644
index 2c457c7..0000000
--- a/debian/control
+++ /dev/null
@@ -1,38 +0,0 @@
-Source: davmail
-Section: net
-Priority: extra
-Maintainer: Alexandre Rossi <alexandre.rossi at gmail.com>
-Build-Depends: debhelper (>= 7.0.50~), default-jdk, ant, ant-optional,
-               javahelper (>=0.20),
-               libcommons-codec-java, libservlet2.4-java,
-               libcommons-httpclient-java, libhtmlcleaner-java,
-               libgnumail-java, libjcifs-java, libjackrabbit-java,
-               liblog4j1.2-java, libwoodstox-java, libstax2-api-java,
-			   libswt-gtk-3-java
-Standards-Version: 3.9.2
-Homepage: http://davmail.sourceforge.net/
-#Vcs-Git: git://git.debian.org/collab-maint/davmail-src-3.9.1.git
-#Vcs-Browser: http://git.debian.org/?p=collab-maint/davmail-src-3.9.1.git;a=summary
-
-Package: davmail
-Architecture: all
-Depends: lsb-base (>= 3.0-6), logrotate, adduser,
-         ${shlibs:Depends}, ${misc:Depends}, ${java:Depends}
-Suggests: libswt-gtk-3.6-java | libswt-gtk-3.5-java | libswt-gtk-3.4-java
-Description: POP/IMAP/SMTP/CalDav/LDAP to Microsoft Exchange gateway
- Ever wanted to get rid of Outlook? DavMail is a POP/IMAP/SMTP/Caldav/LDAP
- exchange gateway allowing users to use any mail/calendar client (e.g.
- Thunderbird with Lightning or Apple iCal) with an Exchange server, even from
- the internet or behind a firewall through Outlook Web Access. DavMail now
- includes an LDAP gateway to Exchange global address book to allow recipient
- address completion in mail compoze window and full calendar support with
- attendees free/busy display.
- .
- The main goal of DavMail is to provide standard compliant protocols in front
- of proprietary Exchange.  This means LDAP for address book, SMTP to send
- messages, IMAP to browse messages on the server in any folder, POP to retrieve
- inbox messages only and Caldav for calendar support. Thus any standard
- compliant client can be used with Microsoft Exchange.
- .
- DavMail gateway is implemented in java and should run on any platform.
- Releases are tested on Windows, Linux (Ubuntu) and Mac OSX.
diff --git a/debian/copyright b/debian/copyright
deleted file mode 100644
index be9a401..0000000
--- a/debian/copyright
+++ /dev/null
@@ -1,189 +0,0 @@
-Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
-Upstream-Name: Davmail
-Upstream-Contact: Mickaël Guessant <mguessan at free.fr>
-Source: http://davmail.sourceforge.net/download.html
-
-Files: *
-Copyright: 2009-2012, Mickael Guessant
-License: GPL-2+
- This program is free software; you can redistribute it
- and/or modify it under the terms of the GNU General Public
- License as published by the Free Software Foundation; either
- version 2 of the License, or (at your option) any later
- version.
- .
- This program is distributed in the hope that it will be
- useful, but WITHOUT ANY WARRANTY; without even the implied
- warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
- PURPOSE.  See the GNU General Public License for more
- details.
- .
- You should have received a copy of the GNU General Public
- License along with this package; if not, write to the Free
- Software Foundation, Inc., 51 Franklin St, Fifth Floor,
- Boston, MA  02110-1301 USA
- .
- On Debian systems, the full text of the GNU General Public
- License version 2 can be found in the file
- `/usr/share/common-licenses/GPL-2'.
-
-Files: ./jsmooth-0.9.9-7-patch/skeletons/*
-Copyright: 2003-2007, Rodrigo Reyes <reyes at charabia.net>
-License: LGPL-2+
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Library General Public
- License as published by the Free Software Foundation; either
- version 2 of the License, or (at your option) any later version.
- .
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- Library General Public License for more details.
- .
- You should have received a copy of the GNU Library General Public
- License along with this library; if not, write to the Free Software
- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
- .
- On Debian systems, the full text of the GNU Library General Public
- License version 2 can be found in the file
- `/usr/share/common-licenses/LGPL-2'.
-
-Files: ./jsmooth-0.9.9-7-patch/src/net/*
-Copyright: 2003, Rodrigo Reyes <reyes at charabia.net>
-License: GPL-2+
- This program is free software; you can redistribute it
- and/or modify it under the terms of the GNU General Public
- License as published by the Free Software Foundation; either
- version 2 of the License, or (at your option) any later
- version.
- .
- This program is distributed in the hope that it will be
- useful, but WITHOUT ANY WARRANTY; without even the implied
- warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
- PURPOSE.  See the GNU General Public License for more
- details.
- .
- You should have received a copy of the GNU General Public
- License along with this package; if not, write to the Free
- Software Foundation, Inc., 51 Franklin St, Fifth Floor,
- Boston, MA  02110-1301 USA
- .
- On Debian systems, the full text of the GNU General Public
- License version 2 can be found in the file
- `/usr/share/common-licenses/GPL-2'.
-
-Files: ./libgrowl/*
-Copyright: 2008, Michael Stringer
-License: BSD (3 clause)
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions
- are met:
- 1. Redistributions of source code must retain the above copyright
-    notice, this list of conditions and the following disclaimer.
- 2. Redistributions in binary form must reproduce the above copyright
-    notice, this list of conditions and the following disclaimer in the
-    documentation and/or other materials provided with the distribution.
- 3. Neither the name of the University nor the names of its contributors
-    may be used to endorse or promote products derived from this software
-    without specific prior written permission.
- .
- THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- SUCH DAMAGE.
-
-Files: src/java/com/ctc/wstx/sr/StreamScanner.java
-Copyright: 2004 Tatu Saloranta <tatu.saloranta at iki.fi>
-License: GPL-2
- This program is free software; you can redistribute it
- and/or modify it under the terms of the GNU General Public
- License as published by the Free Software Foundation; either
- version 2 of the License, or (at your option) any later
- version.
- .
- This program is distributed in the hope that it will be
- useful, but WITHOUT ANY WARRANTY; without even the implied
- warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
- PURPOSE.  See the GNU General Public License for more
- details.
- .
- You should have received a copy of the GNU General Public
- License along with this package; if not, write to the Free
- Software Foundation, Inc., 51 Franklin St, Fifth Floor,
- Boston, MA  02110-1301 USA
- .
- On Debian systems, the full text of the GNU General Public
- License version 2 can be found in the file
- `/usr/share/common-licenses/GPL-2'.
-
-Files: debian/*
-Copyright: 2012 Alexandre Rossi <alexandre.rossi at gmail.com>
-License: GPL-2+
- This program is free software; you can redistribute it
- and/or modify it under the terms of the GNU General Public
- License as published by the Free Software Foundation; either
- version 2 of the License, or (at your option) any later
- version.
- .
- This program is distributed in the hope that it will be
- useful, but WITHOUT ANY WARRANTY; without even the implied
- warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
- PURPOSE.  See the GNU General Public License for more
- details.
- .
- You should have received a copy of the GNU General Public
- License along with this package; if not, write to the Free
- Software Foundation, Inc., 51 Franklin St, Fifth Floor,
- Boston, MA  02110-1301 USA
- .
- On Debian systems, the full text of the GNU General Public
- License version 2 can be found in the file
- `/usr/share/common-licenses/GPL-2'.
-
-Files: ./nsis/processwork.dll
-Files: ./lib/libgrowl-0.2.jar
-Files: ./lib/slf4j-log4j12-1.3.1.jar
-Files: ./lib/jdom-1.0.jar
-Files: ./lib/redline-1.1.9.jar
-Files: ./lib/xercesImpl-2.8.1.jar
-Files: ./lib/jcharset-1.3.jar
-Files: ./lib/activation-1.1.1.jar
-Files: ./lib/swt-3.7-gtk-linux-x86.jar
-Files: ./lib/htmlcleaner-2.1.jar
-Files: ./lib/jcifs-1.3.14.jar
-Files: ./lib/stax-api-1.0.1.jar
-Files: ./lib/commons-httpclient-3.1.jar
-Files: ./lib/log4j-1.2.16.jar
-Files: ./lib/stax2-api-3.1.1.jar
-Files: ./lib/junit-3.8.1.jar
-Files: ./lib/woodstox-core-asl-4.1.2.jar
-Files: ./lib/nsisant-1.2.jar
-Files: ./lib/slf4j-api-1.3.1.jar
-Files: ./lib/commons-logging-1.0.4.jar
-Files: ./lib/libgrowl.jnilib
-Files: ./lib/commons-codec-1.3.jar
-Files: ./lib/swt-3.7-win32-x86_64.jar
-Files: ./lib/mail-1.4.3.jar
-Files: ./lib/jsmoothgen-ant-0.9.9-7-mgu2.jar
-Files: ./lib/jarbundler-2.1.0.jar
-Files: ./lib/servlet-api.jar
-Files: ./lib/swt-3.7-win32-x86.jar
-Files: ./lib/winrun4j-0.4.4.jar
-Files: ./lib/commons-collections-3.1.jar
-Files: ./lib/swt-3.7-gtk-linux-x86_64.jar
-Files: ./lib/ant-deb-0.0.1.jar
-Files: ./lib/jackrabbit-webdav-1.4.jar
-Files: ./svnant/svnkit-javahl16-1.7.0-beta1.jar
-Files: ./svnant/sequence-library-1.0.0.jar
-Files: ./svnant/antlr-runtime-3.4.jar
-Files: ./svnant/svnkit-1.7.0-beta1.jar
-Files: ./svnant/sqljet-1.1.0-SNAPSHOT_r1190_v20120116_2007.jar
-Files: ./svnant/svnClientAdapter.jar
-Files: ./svnant/svnant.jar
diff --git a/debian/davmail.1 b/debian/davmail.1
deleted file mode 100644
index 07f726f..0000000
--- a/debian/davmail.1
+++ /dev/null
@@ -1,45 +0,0 @@
-.\"                                      Hey, EMACS: -*- nroff -*-
-.\" (C) Copyright 2012 Alexandre Rossi <alexandre.rossi at gmail.com>
-.\"
-.TH DAVMAIL 1 "September 2012"
-.\" Please adjust this date whenever revising the manpage.
-.\"
-.\" Some roff macros, for reference:
-.\" .nh        disable hyphenation
-.\" .hy        enable hyphenation
-.\" .ad l      left justify
-.\" .ad b      justify to both left and right margins
-.\" .nf        disable filling
-.\" .fi        enable filling
-.\" .br        insert line break
-.\" .sp <n>    insert n+1 empty lines
-.\" for manpage-specific macros, see man(7)
-.SH NAME
-davmail \- POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway
-.SH SYNOPSIS
-.B davmail
-.RI [ config-file-path ]
-.br
-.SH DESCRIPTION
-This manual page documents briefly the
-.B davmail
-command.
-.PP
-\fBdavmail\fP creates a gateway making the Microsoft Exchange mail, contacts
-and calendar protocol available in standard protocols such as POP, IMAP, CalDAV,
-SMTP and LDAP.
-.SH OPTIONS
-\fBdavmail\fP takes a single command line argument, the configuration file
-path. The configuration file syntax is described deeply on the \fBdavmail\fP
-website.
-.SH FILES
-.I /etc/davmail.conf
-.RS
-The system wide configuration file.
-.I /etc/default/davmail
-.RS
-System wide initscript behaviour handling.
-.SH SEE ALSO
-.BR http://davmail.sourceforge.net/serversetup.html
-.BR http://davmail.sourceforge.net/advanced.html
-.BR http://davmail.sourceforge.net/sslsetup.html
diff --git a/debian/davmail.conf b/debian/davmail.conf
deleted file mode 100644
index 6cbe280..0000000
--- a/debian/davmail.conf
+++ /dev/null
@@ -1,51 +0,0 @@
-davmail.url=https://owa.example.com/owa/
-davmail.enableEws=false
-davmail.ldapPort=1389
-davmail.caldavPort=1080
-davmail.smtpPort=1025
-davmail.popPort=1110
-davmail.imapPort=1143
-davmail.proxyPort=
-davmail.disableUpdateCheck=true
-davmail.bindAddress=127.0.0.1
-davmail.logFilePath=/var/log/davmail.log
-davmail.server=true
-davmail.server.certificate.hash=
-davmail.caldavPastDelay=90
-davmail.sentKeepDelay=90
-davmail.keepDelay=30
-davmail.allowRemote=false
-
-davmail.enableProxy=false
-davmail.proxyHost=
-davmail.proxyPassword=
-davmail.proxyUser=
-
-log4j.logger.davmail=WARN
-log4j.logger.httpclient.wire=WARN
-log4j.logger.org.apache.commons.httpclient=WARN
-log4j.rootLogger=WARN
-
-# SSL configuration. From http://davmail.sourceforge.net/sslsetup.html :
-#
-# SSL is not necessary when DavMail is used in workstation mode, as
-# communication between clients and DavMail remain local. However, in server
-# (shared) mode e.g. with a smartphone connecting to DavMail over the internet,
-# you should make sure encryption is enabled.
-#
-# The simplest way to secure communication between mail/calendar clients and
-# DavMail is to create a self signed certificate:
-#
-#   keytool -genkey -keyalg rsa -keysize 2048 -storepass password\
-#     -keystore davmail.p12 -storetype pkcs12 -validity 3650\
-#     -dname cn=davmailhostname.company.com,ou=davmail,o=sf,o=net
-#
-# Note to iPhone users: iOS does not support the default DSA algorithm, make
-# sure you use an RSA key pair.
-# Another note : do not use blank passwords, both keystore and key passwords
-# must be set.
-#
-#davmail.ssl.keystoreType=PKCS12
-#davmail.ssl.keyPass=password
-#davmail.ssl.keystoreFile=/etc/davmail.p12
-#davmail.ssl.keystorePass=password
diff --git a/debian/davmail.desktop b/debian/davmail.desktop
deleted file mode 100644
index dd139f0..0000000
--- a/debian/davmail.desktop
+++ /dev/null
@@ -1,9 +0,0 @@
-[Desktop Entry]
-Version=1.0
-Encoding=UTF-8
-Name=DavMail
-Type=Application
-Terminal=false
-Exec=/usr/bin/davmail
-Comment=DavMail Exchange Gateway
-Icon=/usr/share/icons/davmail.png
diff --git a/debian/davmail.menu b/debian/davmail.menu
deleted file mode 100644
index 93c1e82..0000000
--- a/debian/davmail.menu
+++ /dev/null
@@ -1,3 +0,0 @@
-?package(davmail):needs="X11" section="Applications/Network/Communication" \
-  title="DavMail Exchange Gateway" command="/usr/bin/davmail" \
-  icon="/usr/share/pixmaps/davmail.xpm"
diff --git a/debian/davmail.xpm b/debian/davmail.xpm
deleted file mode 100644
index ffae546..0000000
--- a/debian/davmail.xpm
+++ /dev/null
@@ -1,519 +0,0 @@
-/* XPM */
-static char * davmail_xpm[] = {
-"32 32 484 2",
-"  	c None",
-". 	c #C95900",
-"+ 	c #D77F00",
-"@ 	c #E3A200",
-"# 	c #EABB00",
-"$ 	c #EEC900",
-"% 	c #EEC800",
-"& 	c #EAC200",
-"* 	c #DFA100",
-"= 	c #D88000",
-"- 	c #B34900",
-"; 	c #C16500",
-"> 	c #E3A100",
-", 	c #FBEF00",
-"' 	c #FFFF05",
-") 	c #FFFF17",
-"! 	c #FEFF21",
-"~ 	c #FEFF28",
-"{ 	c #FEFF24",
-"] 	c #FFFF19",
-"^ 	c #FFFF06",
-"/ 	c #FDF700",
-"( 	c #DEA200",
-"_ 	c #AF5D00",
-": 	c #CB5F00",
-"< 	c #E7B100",
-"[ 	c #FBF300",
-"} 	c #FFFF09",
-"| 	c #FEFA74",
-"1 	c #FDFDB7",
-"2 	c #FFFFCE",
-"3 	c #FFFFD8",
-"4 	c #FFFFE4",
-"5 	c #FFFFDF",
-"6 	c #FFFFDE",
-"7 	c #FFFFBA",
-"8 	c #FFFE7F",
-"9 	c #FFFF14",
-"0 	c #FBF400",
-"a 	c #E0B100",
-"b 	c #AB4A00",
-"c 	c #CF6D00",
-"d 	c #F0D500",
-"e 	c #FFFF0A",
-"f 	c #FEFE65",
-"g 	c #FEFEDF",
-"h 	c #FEFEE0",
-"i 	c #FDFDDB",
-"j 	c #E9DCD8",
-"k 	c #EEE5DC",
-"l 	c #ECE2DB",
-"m 	c #EBE2DA",
-"n 	c #EBE2DB",
-"o 	c #EEE6DB",
-"p 	c #EDE3E0",
-"q 	c #EEE5DE",
-"r 	c #FEFE73",
-"s 	c #FFFF0D",
-"t 	c #F3DE00",
-"u 	c #C66800",
-"v 	c #D47300",
-"w 	c #F7E700",
-"x 	c #FEFF19",
-"y 	c #FDFDAD",
-"z 	c #FEFED1",
-"A 	c #FEFECD",
-"B 	c #FEFFCD",
-"C 	c #ECE3CD",
-"D 	c #F8F3DA",
-"E 	c #FEFFDE",
-"F 	c #FEFEE2",
-"G 	c #FEFEDE",
-"H 	c #FEFEE1",
-"I 	c #FDFDDD",
-"J 	c #E9DED2",
-"K 	c #F8F6CD",
-"L 	c #FAF9D0",
-"M 	c #FEFEB2",
-"N 	c #FEFF27",
-"O 	c #F7E900",
-"P 	c #C36D00",
-"Q 	c #BE5D00",
-"R 	c #F8E700",
-"S 	c #FEFE22",
-"T 	c #FEFEC4",
-"U 	c #FEFEC5",
-"V 	c #FEFEC2",
-"W 	c #FDFCC6",
-"X 	c #E5D6C2",
-"Y 	c #FEFEDC",
-"Z 	c #FDFDDC",
-"` 	c #F6F2D7",
-" .	c #EEE5C3",
-"..	c #FFFFC4",
-"+.	c #FDFDC5",
-"@.	c #FEFEC9",
-"#.	c #FEFF2A",
-"$.	c #FAF100",
-"%.	c #B85B00",
-"&.	c #EFCA00",
-"*.	c #FEFF17",
-"=.	c #FDFDAB",
-"-.	c #FEFDBC",
-";.	c #FEFEB6",
-">.	c #FEFEBB",
-",.	c #F9F7BC",
-"'.	c #ECE1C7",
-").	c #FEFEDD",
-"!.	c #FEFED5",
-"~.	c #FDFEDD",
-"{.	c #FEFDDB",
-"].	c #E0CEBD",
-"^.	c #FCFBBA",
-"/.	c #FDFDBD",
-"(.	c #FEFEBC",
-"_.	c #FEFEB3",
-":.	c #FDFDB2",
-"<.	c #773700",
-"[.	c #D98300",
-"}.	c #FDFD00",
-"|.	c #FEFE8C",
-"1.	c #FDFEB2",
-"2.	c #FEFEB0",
-"3.	c #FEFEB1",
-"4.	c #F4EEB0",
-"5.	c #F3EBD0",
-"6.	c #E4D3C1",
-"7.	c #F3ECB0",
-"8.	c #FEFD9C",
-"9.	c #FEFE00",
-"0.	c #D28400",
-"a.	c #C65100",
-"b.	c #F6E300",
-"c.	c #FEFF4C",
-"d.	c #FEFEA5",
-"e.	c #FEFEA6",
-"f.	c #FFFFA8",
-"g.	c #E9DAAB",
-"h.	c #F9F5D8",
-"i.	c #F5EFD2",
-"j.	c #E9D9A9",
-"k.	c #FFFFA7",
-"l.	c #FEFFA6",
-"m.	c #FDFCA5",
-"n.	c #FDFDA7",
-"o.	c #FDFDA6",
-"p.	c #FEFEAA",
-"q.	c #FEFC5F",
-"r.	c #FEF500",
-"s.	c #A34000",
-"t.	c #DB8E00",
-"u.	c #FFFF08",
-"v.	c #FEFE92",
-"w.	c #FEFD99",
-"x.	c #FEFEA4",
-"y.	c #FEFD97",
-"z.	c #FEFEA1",
-"A.	c #FDFDA0",
-"B.	c #FFFF99",
-"C.	c #DCC3A4",
-"D.	c #FDFCDD",
-"E.	c #FFFFE0",
-"F.	c #DFCAB9",
-"G.	c #E8D8A1",
-"H.	c #ECDEA2",
-"I.	c #ECDDA1",
-"J.	c #F6F09B",
-"K.	c #FEFE9A",
-"L.	c #FEFD9A",
-"M.	c #FAF89E",
-"N.	c #FDFD8D",
-"O.	c #FFFF0E",
-"P.	c #C48700",
-"Q.	c #EFBE00",
-"R.	c #FEFE24",
-"S.	c #FDFB92",
-"T.	c #FEFC92",
-"U.	c #FEFC94",
-"V.	c #FEFC93",
-"W.	c #F5EE92",
-"X.	c #DEC8B9",
-"Y.	c #FEFFE3",
-"Z.	c #FEFEE3",
-"`.	c #F9F5DB",
-" +	c #F6F2DA",
-".+	c #F7F3DB",
-"++	c #E6D2C0",
-"@+	c #E5D291",
-"#+	c #FEFD94",
-"$+	c #FDFC92",
-"%+	c #FEFB92",
-"&+	c #FEFD2C",
-"*+	c #EACC00",
-"=+	c #FEFA50",
-"-+	c #FDFA8B",
-";+	c #FDFA89",
-">+	c #FEFC89",
-",+	c #E0C37C",
-"'+	c #ECE2D8",
-")+	c #F8F3DD",
-"!+	c #F8F3DC",
-"~+	c #FAF7DF",
-"{+	c #FEFEE5",
-"]+	c #FEFEEB",
-"^+	c #FEFDE4",
-"/+	c #FDFDEB",
-"(+	c #FAF7DD",
-"_+	c #D7B694",
-":+	c #FFFF84",
-"<+	c #FEFB5C",
-"[+	c #F8E200",
-"}+	c #733800",
-"|+	c #975200",
-"1+	c #FAE700",
-"2+	c #FFF46C",
-"3+	c #FEF885",
-"4+	c #FEF67C",
-"5+	c #FEF67E",
-"6+	c #FEF780",
-"7+	c #FFF77D",
-"8+	c #F4E67D",
-"9+	c #E6CE88",
-"0+	c #E7CF89",
-"a+	c #E6D08B",
-"b+	c #E1C5B0",
-"c+	c #FFFFEE",
-"d+	c #FDFEEB",
-"e+	c #FFFFF6",
-"f+	c #E3CCB3",
-"g+	c #F5E57C",
-"h+	c #FEF985",
-"i+	c #FEF67D",
-"j+	c #FEFD7B",
-"k+	c #FBEB00",
-"l+	c #9C5300",
-"m+	c #CC7000",
-"n+	c #FFF262",
-"o+	c #FEF57A",
-"p+	c #FEF372",
-"q+	c #FEF473",
-"r+	c #FFF67A",
-"s+	c #FFF778",
-"t+	c #FFFA78",
-"u+	c #E8CD7D",
-"v+	c #F5EAE4",
-"w+	c #FEFDF7",
-"x+	c #FEFDF6",
-"y+	c #F2E8DB",
-"z+	c #DAB571",
-"A+	c #FAEE7A",
-"B+	c #FEF57B",
-"C+	c #FEF26D",
-"D+	c #FCE900",
-"E+	c #B56400",
-"F+	c #CD7300",
-"G+	c #FAE800",
-"H+	c #FEE508",
-"I+	c #FDEB57",
-"J+	c #FDF271",
-"K+	c #FEF16E",
-"L+	c #FDF16E",
-"M+	c #FDF473",
-"N+	c #FEF16D",
-"O+	c #FCEE6D",
-"P+	c #D6AE97",
-"Q+	c #FEFEFA",
-"R+	c #FDFDFC",
-"S+	c #FEFDFC",
-"T+	c #FAF8F5",
-"U+	c #D8B185",
-"V+	c #FFF76D",
-"W+	c #FFF76E",
-"X+	c #FDF170",
-"Y+	c #FFF260",
-"Z+	c #F8E609",
-"`+	c #FCEA00",
-" @	c #B96500",
-".@	c #C96D00",
-"+@	c #FDDC00",
-"@@	c #FDE300",
-"#@	c #FEE200",
-"$@	c #FEEA40",
-"%@	c #FDEC5F",
-"&@	c #FEF065",
-"*@	c #FDED64",
-"=@	c #FEED63",
-"-@	c #FEF069",
-";@	c #D9AA61",
-">@	c #F6EFEC",
-",@	c #FEFEFD",
-"'@	c #F6EDE7",
-")@	c #D7AC88",
-"!@	c #D8AE89",
-"~@	c #D19E6E",
-"{@	c #FFF264",
-"]@	c #FDEC64",
-"^@	c #FDED62",
-"/@	c #FEEF64",
-"(@	c #FEEE61",
-"_@	c #FEE100",
-":@	c #FDE200",
-"<@	c #B86300",
-"[@	c #984F00",
-"}@	c #FBDB00",
-"|@	c #FEDB00",
-"1@	c #FEE30B",
-"2@	c #FDE53D",
-"3@	c #FDEB5E",
-"4@	c #FEEB5C",
-"5@	c #FDEB5C",
-"6@	c #F7DD56",
-"7@	c #E2C2A7",
-"8@	c #FFFFFF",
-"9@	c #F9F3F0",
-"0@	c #DCAE69",
-"a@	c #FFED5A",
-"b@	c #FDE656",
-"c@	c #FDEA60",
-"d@	c #FEE840",
-"e@	c #FDDE0D",
-"f@	c #FDDB00",
-"g@	c #FEE400",
-"h@	c #FEDC00",
-"i@	c #FCD800",
-"j@	c #9D5400",
-"k@	c #F7C900",
-"l@	c #FDD900",
-"m@	c #FEDA00",
-"n@	c #FED900",
-"o@	c #FEDB0B",
-"p@	c #FEE134",
-"q@	c #FEE543",
-"r@	c #EEC84F",
-"s@	c #E7C3A3",
-"t@	c #EACEB4",
-"u@	c #F9F3EE",
-"v@	c #DCAD7E",
-"w@	c #F9DD4B",
-"x@	c #FDE446",
-"y@	c #FEE73B",
-"z@	c #FEDB0A",
-"A@	c #FACF00",
-"B@	c #763A00",
-"C@	c #EBB300",
-"D@	c #FED400",
-"E@	c #FED500",
-"F@	c #FED300",
-"G@	c #FDD503",
-"H@	c #FDD40A",
-"I@	c #F7CA0A",
-"J@	c #E3A720",
-"K@	c #F3E3D9",
-"L@	c #D59C7D",
-"M@	c #EEB80B",
-"N@	c #FED60A",
-"O@	c #FED503",
-"P@	c #FDD500",
-"Q@	c #F1B900",
-"R@	c #D58500",
-"S@	c #FFD800",
-"T@	c #FDD200",
-"U@	c #FED200",
-"V@	c #FFD500",
-"W@	c #D78F1C",
-"X@	c #F9F1ED",
-"Y@	c #FEFFFE",
-"Z@	c #EFD9C7",
-"`@	c #DD9404",
-" #	c #FFD700",
-".#	c #CC8000",
-"+#	c #BA5200",
-"@#	c #FCC800",
-"##	c #FECC00",
-"$#	c #FECD00",
-"%#	c #FDCD00",
-"&#	c #FFCE00",
-"*#	c #D18342",
-"=#	c #FEFEFE",
-"-#	c #FBFCFF",
-";#	c #D2770F",
-">#	c #FDCC00",
-",#	c #FECB00",
-"'#	c #FFD000",
-")#	c #AB4900",
-"!#	c #4E1F00",
-"~#	c #DE8201",
-"{#	c #FECA00",
-"]#	c #FDCA00",
-"^#	c #FEC400",
-"/#	c #FEC900",
-"(#	c #FEC800",
-"_#	c #EFAC00",
-":#	c #E5B28F",
-"<#	c #FEFFFF",
-"[#	c #FDFCFA",
-"}#	c #D88841",
-"|#	c #F7BD00",
-"1#	c #DA8300",
-"2#	c #572200",
-"3#	c #F2B200",
-"4#	c #FEC300",
-"5#	c #FEC200",
-"6#	c #E18100",
-"7#	c #FAEFE6",
-"8#	c #EDC59D",
-"9#	c #F1A300",
-"0#	c #FEC600",
-"a#	c #FDC400",
-"b#	c #F5B600",
-"c#	c #723500",
-"d#	c #B75C01",
-"e#	c #FEBA00",
-"f#	c #FEB900",
-"g#	c #FEC000",
-"h#	c #FEBF00",
-"i#	c #E3871B",
-"j#	c #F8E5D6",
-"k#	c #E37F00",
-"l#	c #FFBC00",
-"m#	c #FEBC00",
-"n#	c #FEC100",
-"o#	c #FDBB00",
-"p#	c #B35D00",
-"q#	c #C46800",
-"r#	c #FDB400",
-"s#	c #FEB600",
-"t#	c #FEBB00",
-"u#	c #FAB100",
-"v#	c #ECB272",
-"w#	c #FDFAF8",
-"x#	c #E18925",
-"y#	c #D26F00",
-"z#	c #2C1100",
-"A#	c #C66600",
-"B#	c #FBAF00",
-"C#	c #FEB400",
-"D#	c #FDB300",
-"E#	c #F6A300",
-"F#	c #F6DBC2",
-"G#	c #EAAA6A",
-"H#	c #FAAB00",
-"I#	c #FEB300",
-"J#	c #FEB500",
-"K#	c #FBAE00",
-"L#	c #C46C00",
-"M#	c #371600",
-"N#	c #9A4C00",
-"O#	c #F19600",
-"P#	c #FFB200",
-"Q#	c #FFAF00",
-"R#	c #EC9715",
-"S#	c #E8AC7B",
-"T#	c #ED8F00",
-"U#	c #FEAD00",
-"V#	c #FEAF00",
-"W#	c #F19B00",
-"X#	c #A54F00",
-"Y#	c #1F0B00",
-"Z#	c #823200",
-"`#	c #BE6700",
-" $	c #F39B00",
-".$	c #ED8D01",
-"+$	c #E58400",
-"@$	c #FEAC00",
-"#$	c #FDAB00",
-"$$	c #FCA800",
-"%$	c #F29A00",
-"&$	c #BC6200",
-"*$	c #6D2A00",
-"=$	c #592800",
-"-$	c #A14E00",
-";$	c #D37000",
-">$	c #EC8900",
-",$	c #F39500",
-"'$	c #F99E00",
-")$	c #F89D00",
-"!$	c #F29400",
-"~$	c #ED8A00",
-"{$	c #D27000",
-"]$	c #A24F00",
-"^$	c #5E2800",
-"/$	c #562200",
-"($	c #773100",
-"_$	c #823400",
-"                                                                ",
-"                    . + @ # $ % & * = -                         ",
-"                ; > , ' ) ! ~ ~ { ] ^ / ( _                     ",
-"            : < [ } | 1 2 3 4 5 6 2 7 8 9 0 a b                 ",
-"          c d e f g h i j k k l m n o p q r s t u               ",
-"        v w x y z A B C D E F G H F I J K L M N O P             ",
-"      Q R S T U T V W X 6 I Y I Z Y `  ...V +. at .#.$.%.          ",
-"      &.*.=.-.;.;.>.,.'.6 ).I !.~.{.].^./.>.(._.:.x d <.        ",
-"    [.}.|.1.2.2.2.3.4.5.).).Y I ).6.7.2.2.2.3.2.3.8.9.0.        ",
-"  a.b.c.=.d.e.e.e.f.g.h.Z Y ).).i.j.k.l.m.m.n.e.o.p.q.r.s.      ",
-"  t.u.v.w.x.y.z.A.B.C.D.).F ).E.F.G.H.I.J.K.L.M.K.K.N.O.P.      ",
-"  Q.R.S.T.U.T.U.V.W.X.Y.F F Z.Z.`. +.+++ at +#+$+U.$+V.%+&+*+      ",
-"  b.=+-+;+;+;+;+>+,+'+)+!+~+{+]+^+/+(+_+:+;+;+;+;+;+-+<+[+}+    ",
-"|+1+2+3+4+3+5+6+7+8+9+0+a+b+c+]+d+e+f+g+h+i+7+i+i+i+i+j+k+l+    ",
-"m+k+n+o+o+p+o+q+o+r+s+t+u+v+w+x+x+y+z+r+p+r+A+o+o+o+B+C+D+E+    ",
-"F+G+H+I+J+K+L+M+L+L+N+O+P+Q+R+S+T+U+V+W+L+L+K+L+L+X+Y+Z+`+ @    ",
-". at +@@@#@$@%@&@*@=@=@-@;@>@,@,@,@'@)@!@~@{@]@^@/@(@$@@@_@:@<@    ",
-"[@}@|@#@|@1 at 2@3 at 4@5 at 6@7 at 8@,@,@, at S+S+9@0 at a@b at c@d at e@f at g@h at i@j@    ",
-"  k at l@m at m@m at n@o at p@q at r@s at t@u@,@,@, at 8@v at w@x at y@z at m@|@n at m@|@A at B@    ",
-"  C at D@E at E@E at D@E at F@G at H@I at J@K@,@, at 8@L at M@N at O@F at E@E at E@D at E@P at Q@      ",
-"  R at S@T at U@U at F@U at U@U at U@V at W@X at R+Y@Z@`@ #T at F@F at U@U at F@U at T@S at .#      ",
-"  +#@#######$#######%#&#*#=#, at -#;#@#>#,#$#$#,#,#$#,#,#'#)#      ",
-"  !#~#{#]#{#^#^#/#(#,#_#:#<#[#}#|#,#^#,#^#^#/#/#^#]#,#1#2#      ",
-"      3#^#4#^#^#5#5#4#6#7#8 at 8#9#0#a#^#^#^#^#4#4#^#^#b#c#        ",
-"      d#e#4#f#g#g#f#h#i#8 at j#k#l#m#m#m#m#e#f#n#g#m#o#p#          ",
-"        q#r#o#s#t#s#u#v#w#x#f#e#s#t#t#t#s#s#s#o#r#y#z#          ",
-"          A#B#C#D#C#E#F#G#H#D#I#C#I#I#I#J#r#f#K#L#M#            ",
-"            N#O#P#Q#R#S#T#U#V#V#U#U#U#I#U#P#W#X#Y#              ",
-"              Z#`# $.$+$Q#@$@$#$#$U#@$$$%$&$*$                  ",
-"                  =$-$;$>$,$'$)$!$~${$]$^$                      ",
-"                      Y#/$($_$_$($/$Y#                          ",
-"                                                                "};
diff --git a/debian/default b/debian/default
deleted file mode 100644
index dbfda55..0000000
--- a/debian/default
+++ /dev/null
@@ -1,4 +0,0 @@
-# Defaults for davmail initscript
-
-# Whether to run the daemon using the supplied initscript
-ENABLE_DAEMON="false"
diff --git a/debian/gbp.conf b/debian/gbp.conf
deleted file mode 100644
index 958d084..0000000
--- a/debian/gbp.conf
+++ /dev/null
@@ -1,2 +0,0 @@
-[git-import-orig]
-filter = ['lib', 'libgrowl', 'nsis', 'svnant', ]
diff --git a/debian/init b/debian/init
deleted file mode 100755
index d0248dd..0000000
--- a/debian/init
+++ /dev/null
@@ -1,162 +0,0 @@
-#! /bin/sh
-### BEGIN INIT INFO
-# Provides:          davmail
-# Required-Start:    $local_fs $remote_fs
-# Required-Stop:     $local_fs $remote_fs
-# Default-Start:     2 3 4 5
-# Default-Stop:      0 1 6
-# Short-Description: Launch Davmail Exchange gateway
-# Description:       Prepare environement and launch the Davmail Exchange
-#                    gateway daemon.
-### END INIT INFO
-
-# Author: Alexandre Rossi <alexandre.rossi at gmail.com>
-
-# Do NOT "set -e"
-
-# PATH should only include /usr/* if it runs after the mountnfs.sh script
-PATH=/sbin:/usr/sbin:/bin:/usr/bin
-DESC="Davmail Exchange gateway"
-NAME=davmail
-DAEMON=/usr/bin/$NAME
-DAEMON_USER=$NAME
-HOME=/var/lib/$DAEMON_USER
-PIDFILE=/var/run/$NAME.pid
-LOGFILE=/var/log/$NAME.log
-SCRIPTNAME=/etc/init.d/$NAME
-
-
-# Exit if the package is not installed
-[ -x "$DAEMON" ] || exit 0
-
-# Read configuration variable file if it is present
-[ -r /etc/default/$NAME ] && . /etc/default/$NAME
-
-# Exit if daemon run by initscript is disabled
-[ "$ENABLE_DAEMON" = "true" ] || exit 0
-
-DAEMON_ARGS="/etc/davmail.conf"
-
-# Create logfiles if they do not exist
-if [ ! -r "$LOGFILE" ]
-then
-    touch $LOGFILE
-    chown $NAME:adm $LOGFILE
-fi
-
-# Load the VERBOSE setting and other rcS variables
-. /lib/init/vars.sh
-
-# Define LSB log_* functions.
-# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
-. /lib/lsb/init-functions
-
-do_start()
-{
-    # Return
-    #   0 if daemon has been started
-    #   2 if daemon could not be started
-    $ENABLE_DAEMON
-    is_alive && return 0
-    start-stop-daemon --start --pidfile $PIDFILE --chuid $DAEMON_USER\
-        --background --make-pidfile\
-        --exec $DAEMON -- $DAEMON_ARGS \
-        || return 2
-}
-
-do_stop()
-{
-    # Return
-    #   0 if daemon has been stopped
-    #   1 if daemon was already stopped
-    #   2 if daemon could not be stopped
-    #   other if a failure occurred
-    start-stop-daemon --user $DAEMON_USER --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
-    RETVAL="$?"
-    [ "$RETVAL" = 2 ] && return 2
-    start-stop-daemon --user $DAEMON_USER --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
-    [ "$?" = 2 ] && return 2
-    rm -f $PIDFILE
-    return "$RETVAL"
-}
-
-do_reload() {
-    #
-    # If the daemon can reload its configuration without
-    # restarting (for example, when it is sent a SIGHUP),
-    # then implement that here.
-    #
-    start-stop-daemon --user $DAEMON_USER --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
-    return 0
-}
-
-is_alive () {
-    ret=1
-    if [ -r $PIDFILE ] ; then
-        pid=`cat $PIDFILE`
-        if [ -e /proc/$pid ] ; then
-            procname=`/bin/ps h -p $pid -C $NAME`
-            [ -n "$procname" ] && ret=0
-        fi
-    fi
-    return $ret
-}
-
-
-case "$1" in
-    start)
-        [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
-        do_start
-        case "$?" in
-            0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
-            2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
-        esac
-        ;;
-    stop)
-        [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
-        do_stop
-        case "$?" in
-            0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
-            2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
-        esac
-        ;;
-    reload|force-reload)
-        log_daemon_msg "Reloading $DESC" "$NAME"
-        do_reload
-        log_end_msg $?
-        ;;
-    restart)
-        log_daemon_msg "Restarting $DESC" "$NAME"
-        do_stop
-        case "$?" in
-            0|1)
-            do_start
-            case "$?" in
-                0) log_end_msg 0 ;;
-                1) log_end_msg 1 ;; # Old process is still running
-                *) log_end_msg 1 ;; # Failed to start
-            esac
-            ;;
-        *)
-            # Failed to stop
-            log_end_msg 1
-            ;;
-        esac
-        ;;
-    status)
-        echo -n "Status of $DESC: "
-        if is_alive ; then
-            echo "alive."
-        else
-            echo "dead."
-            exit 1
-        fi
-        ;;
-    *)
-        echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload|status}" >&2
-        exit 3
-        ;;
-esac
-
-:
-# vim: ts=4 sw=4 expandtab
diff --git a/debian/install b/debian/install
deleted file mode 100644
index 55d6ef5..0000000
--- a/debian/install
+++ /dev/null
@@ -1,4 +0,0 @@
-debian/davmail.conf /etc
-debian/davmail.xpm /usr/share/pixmaps
-dist/davmail.png /usr/share/icons
-debian/davmail.desktop /usr/share/applications
diff --git a/debian/jlibs b/debian/jlibs
deleted file mode 100644
index bde75da..0000000
--- a/debian/jlibs
+++ /dev/null
@@ -1 +0,0 @@
-dist/davmail.jar
diff --git a/debian/links b/debian/links
deleted file mode 100644
index ba120b0..0000000
--- a/debian/links
+++ /dev/null
@@ -1 +0,0 @@
-usr/share/java/davmail.jar usr/bin/davmail
diff --git a/debian/logrotate b/debian/logrotate
deleted file mode 100644
index 2ae1891..0000000
--- a/debian/logrotate
+++ /dev/null
@@ -1,14 +0,0 @@
-/var/log/davmail.log /var/log/davmail.*.log {
-    rotate 4
-    weekly
-    compress
-    copytruncate
-    missingok
-    notifempty
-    sharedscripts
-    postrotate
-        if invoke-rc.d --quiet davmail status > /dev/null; then
-            invoke-rc.d --quiet davmail reload > /dev/null
-        fi
-    endscript
-}
diff --git a/debian/manifest b/debian/manifest
deleted file mode 100644
index b6f54d9..0000000
--- a/debian/manifest
+++ /dev/null
@@ -1,3 +0,0 @@
-usr/share/java/davmail.jar:
-    Class-Path: /usr/share/java/commons-codec.jar /usr/share/java/servlet-api.jar /usr/share/java/commons-httpclient.jar /usr/share/java/htmlcleaner.jar /usr/share/java/gnumail.jar /usr/share/java/jcifs.jar /usr/share/java/jackrabbit-webdav.jar /usr/share/java/woodstox-core-lgpl.jar /usr/share/java/stax2-api.jar /usr/share/java/swt.jar /usr/share/java/log4j-1.2.jar /usr/share/java/slf4j-simple.jar /usr/share/java/slf4j-api.jar
-    Main-Class: davmail.DavGateway
diff --git a/debian/manpages b/debian/manpages
deleted file mode 100644
index 10ecf14..0000000
--- a/debian/manpages
+++ /dev/null
@@ -1 +0,0 @@
-debian/davmail.1
diff --git a/debian/patches/base64-enc-dec b/debian/patches/base64-enc-dec
deleted file mode 100644
index 5fbebbc..0000000
--- a/debian/patches/base64-enc-dec
+++ /dev/null
@@ -1,474 +0,0 @@
-Description: Add BASE64MailboxDecoder and BASE64MailboxEncoder
-
-Those are missing in libgnumail-java and needed to build davmail. The
-alternative would be to depend on javamail which is now DFSG-free
-(RFP #596469, http://bugs.debian.org/596469 )
-
-Origin: javamail source code, http://kenai.com/projects/javamail/downloads
-Forwarded: not-needed
-Author: Alexandre Rossi <alexandre.rossi at gmail.com>
-
-Index: davmail-src-3.9.9-1976/src/java/com/sun/mail/imap/protocol/BASE64MailboxDecoder.java
-===================================================================
---- /dev/null	1970-01-01 00:00:00.000000000 +0000
-+++ davmail-src-3.9.9-1976/src/java/com/sun/mail/imap/protocol/BASE64MailboxDecoder.java	2012-08-24 15:31:07.946121347 +0200
-@@ -0,0 +1,194 @@
-+/*
-+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
-+ *
-+ * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
-+ *
-+ * The contents of this file are subject to the terms of either the GNU
-+ * General Public License Version 2 only ("GPL") or the Common Development
-+ * and Distribution License("CDDL") (collectively, the "License").  You
-+ * may not use this file except in compliance with the License.  You can
-+ * obtain a copy of the License at
-+ * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
-+ * or packager/legal/LICENSE.txt.  See the License for the specific
-+ * language governing permissions and limitations under the License.
-+ *
-+ * When distributing the software, include this License Header Notice in each
-+ * file and include the License file at packager/legal/LICENSE.txt.
-+ *
-+ * GPL Classpath Exception:
-+ * Oracle designates this particular file as subject to the "Classpath"
-+ * exception as provided by Oracle in the GPL Version 2 section of the License
-+ * file that accompanied this code.
-+ *
-+ * Modifications:
-+ * If applicable, add the following below the License Header, with the fields
-+ * enclosed by brackets [] replaced by your own identifying information:
-+ * "Portions Copyright [year] [name of copyright owner]"
-+ *
-+ * Contributor(s):
-+ * If you wish your version of this file to be governed by only the CDDL or
-+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
-+ * elects to include this software in this distribution under the [CDDL or GPL
-+ * Version 2] license."  If you don't indicate a single choice of license, a
-+ * recipient has the option to distribute your version of this file under
-+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
-+ * its licensees as provided above.  However, if you add GPL Version 2 code
-+ * and therefore, elected the GPL Version 2 license, then the option applies
-+ * only if the new code is made subject to such option by the copyright
-+ * holder.
-+ */
-+
-+package com.sun.mail.imap.protocol;
-+
-+import java.text.StringCharacterIterator;
-+import java.text.CharacterIterator;
-+
-+/**
-+ * See the BASE64MailboxEncoder for a description of the RFC2060 and how
-+ * mailbox names should be encoded.  This class will do the correct decoding
-+ * for mailbox names.
-+ *
-+ * @author	Christopher Cotton
-+ */
-+
-+public class BASE64MailboxDecoder {
-+
-+    public static String decode(String original) {
-+	if (original == null || original.length() == 0)
-+	    return original;
-+
-+	boolean changedString = false;
-+	int copyTo = 0;
-+	// it will always be less than the original
-+	char[] chars = new char[original.length()];
-+	StringCharacterIterator iter = new StringCharacterIterator(original);
-+
-+	for(char c = iter.first(); c != CharacterIterator.DONE;
-+	    c = iter.next()) {
-+
-+	    if (c == '&') {
-+		changedString = true;
-+		copyTo = base64decode(chars, copyTo, iter);
-+	    } else {
-+		chars[copyTo++] = c;
-+	    }
-+	}
-+
-+	// now create our string from the char array
-+	if (changedString) {
-+	    return new String(chars, 0, copyTo);
-+	} else {
-+	    return original;
-+	}
-+    }
-+
-+
-+    protected static int base64decode(char[] buffer, int offset,
-+				      CharacterIterator iter) {
-+	boolean firsttime = true;
-+	int leftover = -1;
-+
-+	while(true) {
-+	    // get the first byte
-+	    byte orig_0 = (byte) iter.next();
-+	    if (orig_0 == -1) break; // no more chars
-+	    if (orig_0 == '-') {
-+		if (firsttime) {
-+		    // means we got the string "&-" which is turned into a "&"
-+		    buffer[offset++] = '&';
-+		}
-+		// we are done now
-+		break;
-+	    }
-+	    firsttime = false;
-+
-+	    // next byte
-+	    byte orig_1 = (byte) iter.next();
-+	    if (orig_1 == -1 || orig_1 == '-')
-+		break; // no more chars, invalid base64
-+
-+	    byte a, b, current;
-+	    a = pem_convert_array[orig_0 & 0xff];
-+	    b = pem_convert_array[orig_1 & 0xff];
-+	    // The first decoded byte
-+	    current = (byte)(((a << 2) & 0xfc) | ((b >>> 4) & 3));
-+
-+	    // use the leftover to create a Unicode Character (2 bytes)
-+	    if (leftover != -1) {
-+		buffer[offset++] = (char)(leftover << 8 | (current & 0xff));
-+		leftover = -1;
-+	    } else {
-+		leftover = current & 0xff;
-+	    }
-+
-+	    byte orig_2 = (byte) iter.next();
-+	    if (orig_2 == '=') { // End of this BASE64 encoding
-+		continue;
-+	    } else if (orig_2 == -1 || orig_2 == '-') {
-+	    	break; // no more chars
-+	    }
-+
-+	    // second decoded byte
-+	    a = b;
-+	    b = pem_convert_array[orig_2 & 0xff];
-+	    current = (byte)(((a << 4) & 0xf0) | ((b >>> 2) & 0xf));
-+
-+	    // use the leftover to create a Unicode Character (2 bytes)
-+	    if (leftover != -1) {
-+		buffer[offset++] = (char)(leftover << 8 | (current & 0xff));
-+		leftover = -1;
-+	    } else {
-+		leftover = current & 0xff;
-+	    }
-+
-+	    byte orig_3 = (byte) iter.next();
-+	    if (orig_3 == '=') { // End of this BASE64 encoding
-+		continue;
-+	    } else if (orig_3 == -1 || orig_3 == '-') {
-+	    	break;  // no more chars
-+	    }
-+
-+	    // The third decoded byte
-+	    a = b;
-+	    b = pem_convert_array[orig_3 & 0xff];
-+	    current = (byte)(((a << 6) & 0xc0) | (b & 0x3f));
-+
-+	    // use the leftover to create a Unicode Character (2 bytes)
-+	    if (leftover != -1) {
-+		buffer[offset++] = (char)(leftover << 8 | (current & 0xff));
-+		leftover = -1;
-+	    } else {
-+		leftover = current & 0xff;
-+	    }
-+	}
-+
-+	return offset;
-+    }
-+
-+    /**
-+     * This character array provides the character to value map
-+     * based on RFC1521, but with the modification from RFC2060
-+     * which changes the '/' to a ','.
-+     */
-+
-+    // shared with BASE64MailboxEncoder
-+    static final char pem_array[] = {
-+	'A','B','C','D','E','F','G','H', // 0
-+	'I','J','K','L','M','N','O','P', // 1
-+	'Q','R','S','T','U','V','W','X', // 2
-+	'Y','Z','a','b','c','d','e','f', // 3
-+	'g','h','i','j','k','l','m','n', // 4
-+	'o','p','q','r','s','t','u','v', // 5
-+	'w','x','y','z','0','1','2','3', // 6
-+	'4','5','6','7','8','9','+',','  // 7
-+    };
-+
-+    private static final byte pem_convert_array[] = new byte[256];
-+
-+    static {
-+	for (int i = 0; i < 255; i++)
-+	    pem_convert_array[i] = -1;
-+	for(int i = 0; i < pem_array.length; i++)
-+	    pem_convert_array[pem_array[i]] = (byte) i;
-+    }
-+}
-Index: davmail-src-3.9.9-1976/src/java/com/sun/mail/imap/protocol/BASE64MailboxEncoder.java
-===================================================================
---- /dev/null	1970-01-01 00:00:00.000000000 +0000
-+++ davmail-src-3.9.9-1976/src/java/com/sun/mail/imap/protocol/BASE64MailboxEncoder.java	2012-08-24 15:31:08.017876275 +0200
-@@ -0,0 +1,260 @@
-+/*
-+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
-+ *
-+ * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
-+ *
-+ * The contents of this file are subject to the terms of either the GNU
-+ * General Public License Version 2 only ("GPL") or the Common Development
-+ * and Distribution License("CDDL") (collectively, the "License").  You
-+ * may not use this file except in compliance with the License.  You can
-+ * obtain a copy of the License at
-+ * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
-+ * or packager/legal/LICENSE.txt.  See the License for the specific
-+ * language governing permissions and limitations under the License.
-+ *
-+ * When distributing the software, include this License Header Notice in each
-+ * file and include the License file at packager/legal/LICENSE.txt.
-+ *
-+ * GPL Classpath Exception:
-+ * Oracle designates this particular file as subject to the "Classpath"
-+ * exception as provided by Oracle in the GPL Version 2 section of the License
-+ * file that accompanied this code.
-+ *
-+ * Modifications:
-+ * If applicable, add the following below the License Header, with the fields
-+ * enclosed by brackets [] replaced by your own identifying information:
-+ * "Portions Copyright [year] [name of copyright owner]"
-+ *
-+ * Contributor(s):
-+ * If you wish your version of this file to be governed by only the CDDL or
-+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
-+ * elects to include this software in this distribution under the [CDDL or GPL
-+ * Version 2] license."  If you don't indicate a single choice of license, a
-+ * recipient has the option to distribute your version of this file under
-+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
-+ * its licensees as provided above.  However, if you add GPL Version 2 code
-+ * and therefore, elected the GPL Version 2 license, then the option applies
-+ * only if the new code is made subject to such option by the copyright
-+ * holder.
-+ */
-+
-+package com.sun.mail.imap.protocol;
-+
-+import java.io.*;
-+
-+
-+/**
-+ *
-+ *
-+  from RFC2060
-+
-+5.1.3.  Mailbox International Naming Convention
-+
-+   By convention, international mailbox names are specified using a
-+   modified version of the UTF-7 encoding described in [UTF-7].  The
-+   purpose of these modifications is to correct the following problems
-+   with UTF-7:
-+
-+      1) UTF-7 uses the "+" character for shifting; this conflicts with
-+         the common use of "+" in mailbox names, in particular USENET
-+         newsgroup names.
-+
-+      2) UTF-7's encoding is BASE64 which uses the "/" character; this
-+         conflicts with the use of "/" as a popular hierarchy delimiter.
-+
-+      3) UTF-7 prohibits the unencoded usage of "\"; this conflicts with
-+         the use of "\" as a popular hierarchy delimiter.
-+
-+      4) UTF-7 prohibits the unencoded usage of "~"; this conflicts with
-+         the use of "~" in some servers as a home directory indicator.
-+
-+      5) UTF-7 permits multiple alternate forms to represent the same
-+         string; in particular, printable US-ASCII chararacters can be
-+         represented in encoded form.
-+
-+   In modified UTF-7, printable US-ASCII characters except for "&"
-+   represent themselves; that is, characters with octet values 0x20-0x25
-+   and 0x27-0x7e.  The character "&" (0x26) is represented by the two-
-+   octet sequence "&-".
-+
-+   All other characters (octet values 0x00-0x1f, 0x7f-0xff, and all
-+   Unicode 16-bit octets) are represented in modified BASE64, with a
-+   further modification from [UTF-7] that "," is used instead of "/".
-+   Modified BASE64 MUST NOT be used to represent any printing US-ASCII
-+   character which can represent itself.
-+
-+   "&" is used to shift to modified BASE64 and "-" to shift back to US-
-+   ASCII.  All names start in US-ASCII, and MUST end in US-ASCII (that
-+   is, a name that ends with a Unicode 16-bit octet MUST end with a "-
-+   ").
-+
-+
-+
-+
-+
-+Crispin                     Standards Track                    [Page 15]
-+
-+RFC 2060                       IMAP4rev1                   December 1996
-+
-+
-+      For example, here is a mailbox name which mixes English, Japanese,
-+      and Chinese text: ~peter/mail/&ZeVnLIqe-/&U,BTFw-
-+
-+
-+ * This class will do the correct Encoding for the IMAP mailboxes
-+ *
-+ * @author	Christopher Cotton
-+ */
-+
-+public class BASE64MailboxEncoder {
-+    protected byte[] buffer = new byte[4];
-+    protected int bufsize = 0;
-+    protected boolean started = false;
-+    protected Writer out = null;
-+
-+
-+    public static String encode(String original) {
-+	BASE64MailboxEncoder base64stream = null;
-+	char origchars[] = original.toCharArray();
-+	int length = origchars.length;
-+	boolean changedString = false;
-+	CharArrayWriter writer = new CharArrayWriter(length);
-+
-+	// loop over all the chars
-+	for(int index = 0; index < length; index++) {
-+	    char current = origchars[index];
-+
-+	    // octets in the range 0x20-0x25,0x27-0x7e are themselves
-+	    // 0x26 "&" is represented as "&-"
-+	    if (current >= 0x20 && current <= 0x7e) {
-+		if (base64stream != null) {
-+		    base64stream.flush();
-+		}
-+
-+		if (current == '&') {
-+		    changedString = true;
-+		    writer.write('&');
-+		    writer.write('-');
-+		} else {
-+		    writer.write(current);
-+		}
-+	    } else {
-+
-+		// use a B64MailboxEncoder to write out the other bytes
-+		// as a modified BASE64.  The stream will write out
-+		// the beginning '&' and the ending '-' which is part
-+		// of every encoding.
-+
-+		if (base64stream == null) {
-+		    base64stream = new BASE64MailboxEncoder(writer);
-+		    changedString = true;
-+		}
-+
-+		base64stream.write(current);
-+	    }
-+	}
-+
-+
-+	if (base64stream != null) {
-+	    base64stream.flush();
-+	}
-+
-+	if (changedString) {
-+	    return writer.toString();
-+	} else {
-+	    return original;
-+	}
-+    }
-+
-+
-+    /**
-+     * Create a BASE64 encoder
-+     */
-+    public BASE64MailboxEncoder(Writer what) {
-+	out = what;
-+    }
-+
-+    public void write(int c) {
-+	try {
-+	    // write out the initial character if this is the first time
-+	    if (!started) {
-+		started = true;
-+		out.write('&');
-+	    }
-+
-+	    // we write each character as a 2 byte unicode character
-+	    buffer[bufsize++] = (byte) (c >> 8);
-+	    buffer[bufsize++] = (byte) (c & 0xff);
-+
-+	    if (bufsize >= 3) {
-+		encode();
-+		bufsize -= 3;
-+	    }
-+	} catch (IOException e) {
-+	    //e.printStackTrace();
-+	}
-+    }
-+
-+
-+    public void flush() {
-+	try {
-+	    // flush any bytes we have
-+	    if (bufsize > 0) {
-+		encode();
-+		bufsize = 0;
-+	    }
-+
-+	    // write the terminating character of the encoding
-+	    if (started) {
-+		out.write('-');
-+		started = false;
-+	    }
-+	} catch (IOException e) {
-+	    //e.printStackTrace();
-+	}
-+    }
-+
-+
-+    protected void encode() throws IOException {
-+	byte a, b, c;
-+	if (bufsize == 1) {
-+	    a = buffer[0];
-+	    b = 0;
-+	    c = 0;
-+	    out.write(pem_array[(a >>> 2) & 0x3F]);
-+	    out.write(pem_array[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
-+		// no padding characters are written
-+	} else if (bufsize == 2) {
-+	    a = buffer[0];
-+	    b = buffer[1];
-+	    c = 0;
-+	    out.write(pem_array[(a >>> 2) & 0x3F]);
-+	    out.write(pem_array[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
-+	    out.write(pem_array[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
-+		// no padding characters are written
-+	} else {
-+	    a = buffer[0];
-+	    b = buffer[1];
-+	    c = buffer[2];
-+	    out.write(pem_array[(a >>> 2) & 0x3F]);
-+	    out.write(pem_array[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
-+	    out.write(pem_array[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
-+	    out.write(pem_array[c & 0x3F]);
-+
-+	    // copy back the extra byte
-+	    if (bufsize == 4)
-+		buffer[0] = buffer[3];
-+        }
-+    }
-+
-+    private final static char pem_array[] = {
-+	'A','B','C','D','E','F','G','H', // 0
-+	'I','J','K','L','M','N','O','P', // 1
-+	'Q','R','S','T','U','V','W','X', // 2
-+	'Y','Z','a','b','c','d','e','f', // 3
-+	'g','h','i','j','k','l','m','n', // 4
-+	'o','p','q','r','s','t','u','v', // 5
-+	'w','x','y','z','0','1','2','3', // 6
-+	'4','5','6','7','8','9','+',','  // 7
-+    };
-+}
diff --git a/debian/patches/fix-build b/debian/patches/fix-build
deleted file mode 100644
index d2fb4a9..0000000
--- a/debian/patches/fix-build
+++ /dev/null
@@ -1,99 +0,0 @@
-Description: Use system libraries in the build system and disable windows dist
-Forwarded: no
-Author: Alexandre Rossi <alexandre.rossi at gmail.com>
-
-Index: davmail-src-3.9.9-1976/build.xml
-===================================================================
---- davmail-src-3.9.9-1976.orig/build.xml	2012-07-10 23:13:26.000000000 +0200
-+++ davmail-src-3.9.9-1976/build.xml	2012-08-23 17:57:56.353182085 +0200
-@@ -3,12 +3,22 @@
-     <property name="version" value="3.9.9"/>
- 
-     <path id="classpath">
--        <pathelement location="classes"/>
--        <fileset dir="lib">
--            <include name="*.jar"/>
--        </fileset>
-+        <pathelement location="${commons-codec}" />
-+        <pathelement location="${servlet-api}" />
-+        <pathelement location="${commons-httpclient}" />
-+        <pathelement location="${htmlcleaner}" />
-+        <pathelement location="${gnumail}" />
-+        <pathelement location="${jcifs}" />
-+        <pathelement location="${jackrabbit-webdav}" />
-+        <pathelement location="${woodstox-core}" />
-+        <pathelement location="${stax2-api}" />
-+        <pathelement location="${swt}" />
-     </path>
- 
-+    <condition property="windows-dist-run">
-+        <equals arg1="${windows-dist}" arg2="true" />
-+    </condition>
-+
-     <target name="clean">
-         <delete dir="target"/>
-         <delete dir="dist"/>
-@@ -230,8 +240,20 @@
- 
-     </target>
- 
-+    <target name="windows-dist" depends="compile" if="windows-dist-run">
-+
-+        <taskdef name="jsmoothgen"
-+                 classname="net.charabia.jsmoothgen.ant.JSmoothGen"
-+                 classpathref="classpath"/>
-+        <jsmoothgen project="davmail.jsmooth" skeletonroot="src/jsmooth/skeletons"/>
-+        <jsmoothgen project="davmailconsole.jsmooth" skeletonroot="src/jsmooth/skeletons"/>
-+        <!-- use WinRun4J to generate DavMail service -->
-+        <copy file="src/winrun4j/davmailservice.exe" todir="dist"/>
-+        <jsmoothgen project="davmail64.jsmooth" skeletonroot="src/jsmooth/skeletons"/>
- 
--    <target name="dist" depends="compile">
-+    </target>
-+
-+    <target name="dist" depends="compile, windows-dist">
-         <property name="release-name" value="${release}-trunk"/>
-         <delete dir="dist"/>
-         <mkdir dir="dist"/>
-@@ -242,6 +264,7 @@
-                     <attribute name="Implementation-Title" value="DavMail Gateway"/>
-                     <attribute name="Implementation-Version" value="${release-name}"/>
-                     <attribute name="Implementation-Vendor" value="Mickael Guessant"/>
-+                    <attribute name="Main-Class" value="davmail.DavGateway"/>
-                 </section>
-             </manifest>
-         </jar>
-@@ -259,14 +282,6 @@
-         </copy>
-         <copy file="src/java/tray48.png" tofile="dist/davmail.png"/>
-         <copy file="davmail.sh" todir="dist"/>
--        <taskdef name="jsmoothgen"
--                 classname="net.charabia.jsmoothgen.ant.JSmoothGen"
--                 classpathref="classpath"/>
--        <jsmoothgen project="davmail.jsmooth" skeletonroot="src/jsmooth/skeletons"/>
--        <jsmoothgen project="davmailconsole.jsmooth" skeletonroot="src/jsmooth/skeletons"/>
--        <!-- use WinRun4J to generate DavMail service -->
--        <copy file="src/winrun4j/davmailservice.exe" todir="dist"/>
--        <jsmoothgen project="davmail64.jsmooth" skeletonroot="src/jsmooth/skeletons"/>
-         <zip file="dist/davmail-${release-name}.zip">
-             <fileset dir="dist">
-                 <include name="lib/*.jar"/>
-Index: davmail-src-3.9.9-1976/build.properties
-===================================================================
---- /dev/null	1970-01-01 00:00:00.000000000 +0000
-+++ davmail-src-3.9.9-1976/build.properties	2012-08-23 18:01:27.492683383 +0200
-@@ -0,0 +1,14 @@
-+# Build flags
-+windows-dist = yes
-+
-+# Deps
-+commons-codec      = lib/commons-codec-1.3.jar
-+servlet-api        = lib/servlet-api.jar
-+commons-httpclient = lib/commons-httpclient-3.1.jar
-+htmlcleaner        = lib/htmlcleaner-2.1.jar
-+gnumail            = lib/mail-1.4.3.jar
-+jcifs              = lib/jcifs-1.3.14.jar
-+jackrabbit-webdav  = lib/jackrabbit-webdav-1.4.jar
-+woodstox-core      = lib/woodstox-core-asl-4.1.2.jar
-+stax2-api          = lib/stax2-api-3.1.1.jar
-+swt                = lib/swt-3.7-*.jar
diff --git a/debian/patches/htmlcleaner22 b/debian/patches/htmlcleaner22
deleted file mode 100644
index b7b6c30..0000000
--- a/debian/patches/htmlcleaner22
+++ /dev/null
@@ -1,42 +0,0 @@
-Description: Port to HTMLCleaner 2.2 API
-Forwarded: no
-Author: Alexandre Rossi <alexandre.rossi at gmail.com>
-
-Index: davmail-src-3.9.9-1976/src/java/davmail/exchange/ExchangeSession.java
-===================================================================
---- davmail-src-3.9.9-1976.orig/src/java/davmail/exchange/ExchangeSession.java	2012-07-07 00:55:55.000000000 +0200
-+++ davmail-src-3.9.9-1976/src/java/davmail/exchange/ExchangeSession.java	2012-07-18 11:39:24.887318823 +0200
-@@ -33,8 +33,8 @@
- import org.apache.commons.httpclient.params.HttpClientParams;
- import org.apache.commons.httpclient.util.URIUtil;
- import org.apache.log4j.Logger;
--import org.htmlcleaner.CommentToken;
--import org.htmlcleaner.ContentToken;
-+import org.htmlcleaner.CommentNode;
-+import org.htmlcleaner.ContentNode;
- import org.htmlcleaner.HtmlCleaner;
- import org.htmlcleaner.TagNode;
- 
-@@ -440,8 +440,8 @@
-                     for (Object script : scriptList) {
-                         List contents = ((TagNode) script).getChildren();
-                         for (Object content : contents) {
--                            if (content instanceof CommentToken) {
--                                String scriptValue = ((CommentToken) content).getCommentedContent();
-+                            if (content instanceof CommentNode) {
-+                                String scriptValue = ((CommentNode) content).getCommentedContent();
-                                 String sUrl = StringUtil.getToken(scriptValue, "var a_sUrl = \"", "\"");
-                                 String sLgn = StringUtil.getToken(scriptValue, "var a_sLgnQS = \"", "\"");
-                                 if (sLgn == null) {
-@@ -454,9 +454,9 @@
-                                     logonMethod = buildLogonMethod(httpClient, newInitMethod);
-                                 }
- 
--                            } else if (content instanceof ContentToken) {
-+                            } else if (content instanceof ContentNode) {
-                                 // Microsoft Forefront Unified Access Gateway redirect
--                                String scriptValue = ((ContentToken) content).getContent();
-+                                String scriptValue = ((ContentNode) content).getContent().toString();
-                                 String location = StringUtil.getToken(scriptValue, "window.location.replace(\"", "\"");
-                                 if (location != null) {
-                                     LOGGER.debug("Post logon redirect to: " + location);
diff --git a/debian/patches/jackrabbit2 b/debian/patches/jackrabbit2
deleted file mode 100644
index 08ec65b..0000000
--- a/debian/patches/jackrabbit2
+++ /dev/null
@@ -1,151 +0,0 @@
-Description: Port to jackrabbit 2 API
-Forwarded: no
-Author: Alexandre Rossi <alexandre.rossi at gmail.com>
-
-Index: davmail-src-3.9.9-1976/src/java/davmail/exchange/dav/DavExchangeSession.java
-===================================================================
---- davmail-src-3.9.9-1976.orig/src/java/davmail/exchange/dav/DavExchangeSession.java	2012-07-07 00:55:54.000000000 +0200
-+++ davmail-src-3.9.9-1976/src/java/davmail/exchange/dav/DavExchangeSession.java	2012-07-18 11:31:08.331318904 +0200
-@@ -41,6 +41,7 @@
- import org.apache.jackrabbit.webdav.property.DavProperty;
- import org.apache.jackrabbit.webdav.property.DavPropertyNameSet;
- import org.apache.jackrabbit.webdav.property.DavPropertySet;
-+import org.apache.jackrabbit.webdav.property.PropEntry;
- import org.w3c.dom.Node;
- 
- import javax.mail.MessagingException;
-@@ -1685,7 +1686,7 @@
-                 // trigger activeSync push event, only if davmail.forceActiveSyncUpdate setting is true
-                 if ((status == HttpStatus.SC_OK || status == HttpStatus.SC_CREATED) &&
-                         (Settings.getBooleanProperty("davmail.forceActiveSyncUpdate"))) {
--                    ArrayList<DavConstants> propertyList = new ArrayList<DavConstants>();
-+                    ArrayList<PropEntry> propertyList = new ArrayList<PropEntry>();
-                     // Set contentclass to make ActiveSync happy
-                     propertyList.add(Field.createDavProperty("contentclass", contentClass));
-                     // ... but also set PR_INTERNET_CONTENT to preserve custom properties
-@@ -1884,7 +1885,7 @@
-             } else if (statusCode != HttpStatus.SC_CREATED) {
-                 throw DavGatewayHttpClientFacade.buildHttpException(method);
-             } else if (folderPath.equalsIgnoreCase("/users/" + getEmail() + "/calendar")) {
--                // calendar renamed, need to reload well known folders 
-+                // calendar renamed, need to reload well known folders
-                 getWellKnownFolders();
-             }
-         } finally {
-@@ -2317,7 +2318,7 @@
-     public void processItem(String folderPath, String itemName) throws IOException {
-         String eventPath = URIUtil.encodePath(getFolderPath(folderPath) + '/' + convertItemNameToEML(itemName));
-         // do not delete calendar messages, mark read and processed
--        ArrayList<DavConstants> list = new ArrayList<DavConstants>();
-+        ArrayList<PropEntry> list = new ArrayList<PropEntry>();
-         list.add(Field.createDavProperty("processed", "true"));
-         list.add(Field.createDavProperty("read", "1"));
-         PropPatchMethod patchMethod = new PropPatchMethod(eventPath, list);
-@@ -2359,7 +2360,7 @@
-             }
-             // failover for Exchange 2007, use PROPPATCH with forced timezone
-             if (fakeEventUrl == null) {
--                ArrayList<DavConstants> propertyList = new ArrayList<DavConstants>();
-+                ArrayList<PropEntry> propertyList = new ArrayList<PropEntry>();
-                 propertyList.add(Field.createDavProperty("contentclass", "urn:content-classes:appointment"));
-                 propertyList.add(Field.createDavProperty("outlookmessageclass", "IPM.Appointment"));
-                 propertyList.add(Field.createDavProperty("instancetype", "0"));
-@@ -2460,8 +2461,8 @@
-         return new Contact(getFolderPath(folderPath), itemName, properties, etag, noneMatch).createOrUpdate();
-     }
- 
--    protected List<DavConstants> buildProperties(Map<String, String> properties) {
--        ArrayList<DavConstants> list = new ArrayList<DavConstants>();
-+    protected List<PropEntry> buildProperties(Map<String, String> properties) {
-+        ArrayList<PropEntry> list = new ArrayList<PropEntry>();
-         if (properties != null) {
-             for (Map.Entry<String, String> entry : properties.entrySet()) {
-                 if ("read".equals(entry.getKey())) {
-@@ -2506,7 +2507,7 @@
-     public void createMessage(String folderPath, String messageName, HashMap<String, String> properties, MimeMessage mimeMessage) throws IOException {
-         String messageUrl = URIUtil.encodePathQuery(getFolderPath(folderPath) + '/' + messageName);
-         PropPatchMethod patchMethod;
--        List<DavConstants> davProperties = buildProperties(properties);
-+        List<PropEntry> davProperties = buildProperties(properties);
- 
-         if (properties != null && properties.containsKey("draft")) {
-             // note: draft is readonly after create, create the message first with requested messageFlags
-@@ -2549,7 +2550,7 @@
-             if (code == HttpStatus.SC_NOT_ACCEPTABLE) {
-                 LOGGER.warn("Draft message creation failed, failover to property update. Note: attachments are lost");
- 
--                ArrayList<DavConstants> propertyList = new ArrayList<DavConstants>();
-+                ArrayList<PropEntry> propertyList = new ArrayList<PropEntry>();
-                 propertyList.add(Field.createDavProperty("to", mimeMessage.getHeader("to", ",")));
-                 propertyList.add(Field.createDavProperty("cc", mimeMessage.getHeader("cc", ",")));
-                 propertyList.add(Field.createDavProperty("message-id", mimeMessage.getHeader("message-id", ",")));
-@@ -2613,7 +2614,7 @@
-         try {
-             // need to update bcc after put
-             if (mimeMessage.getHeader("Bcc") != null) {
--                davProperties = new ArrayList<DavConstants>();
-+                davProperties = new ArrayList<PropEntry>();
-                 davProperties.add(Field.createDavProperty("bcc", mimeMessage.getHeader("Bcc", ",")));
-                 patchMethod = new PropPatchMethod(messageUrl, davProperties);
-                 try {
-@@ -2705,7 +2706,7 @@
-             createMessage(DRAFTS, itemName, properties, mimeMessage);
-             MoveMethod method = new MoveMethod(URIUtil.encodePath(getFolderPath(DRAFTS + '/' + itemName)),
-                     URIUtil.encodePath(getFolderPath(SENDMSG)), false);
--            // set header if saveInSent is disabled 
-+            // set header if saveInSent is disabled
-             if (!Settings.getBooleanProperty("davmail.smtpSaveInSent", true)) {
-                 method.setRequestHeader("Saveinsent", "f");
-             }
-Index: davmail-src-3.9.9-1976/src/java/davmail/exchange/dav/Field.java
-===================================================================
---- davmail-src-3.9.9-1976.orig/src/java/davmail/exchange/dav/Field.java	2012-06-12 00:01:41.000000000 +0200
-+++ davmail-src-3.9.9-1976/src/java/davmail/exchange/dav/Field.java	2012-07-18 11:25:07.520653273 +0200
-@@ -19,7 +19,7 @@
- package davmail.exchange.dav;
- 
- import davmail.util.StringUtil;
--import org.apache.jackrabbit.webdav.DavConstants;
-+import org.apache.jackrabbit.webdav.property.PropEntry;
- import org.apache.jackrabbit.webdav.property.DavPropertyName;
- import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
- import org.apache.jackrabbit.webdav.xml.DomUtil;
-@@ -183,7 +183,7 @@
-         createField(URN_SCHEMAS_CALENDAR, "exdate"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:exdate/PtypMultipleTime
- 
-         createField(SCHEMAS_MAPI, "reminderset"); // PidLidReminderSet
--        createField(SCHEMAS_MAPI, "reminderdelta"); // PidLidReminderDelta         
-+        createField(SCHEMAS_MAPI, "reminderdelta"); // PidLidReminderDelta
- 
-         // TODO
-         createField(SCHEMAS_MAPI, "allattendeesstring"); // PidLidAllAttendeesString
-@@ -197,7 +197,7 @@
-         createField(URN_SCHEMAS_CALENDAR, "busystatus"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:busystatus/String
-         createField(URN_SCHEMAS_CALENDAR, "exrule"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:exrule/PtypMultipleString
-         createField(URN_SCHEMAS_CALENDAR, "recurrenceidrange"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:recurrenceidrange/String
--        createField(URN_SCHEMAS_CALENDAR, "rdate"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:rdate/PtypMultipleTime        
-+        createField(URN_SCHEMAS_CALENDAR, "rdate"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:rdate/PtypMultipleTime
-         createField(URN_SCHEMAS_CALENDAR, "reminderoffset"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:reminderoffset/Integer
-         createField(URN_SCHEMAS_CALENDAR, "timezone"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:timezone/String
- 
-@@ -286,7 +286,7 @@
-         createField("description", URN_SCHEMAS_HTTPMAIL, "textdescription"); // PR_BODY 0x1000 String
-         createField("im", SCHEMAS_MAPI, "InstMsg"); // InstantMessagingAddress DistinguishedPropertySetType.Address/0x00008062/String
-         createField(URN_SCHEMAS_CONTACTS, "othermobile"); // PR_CAR_TELEPHONE_NUMBER 0x3A1E String
--        createField(URN_SCHEMAS_CONTACTS, "internationalisdnnumber"); // PR_ISDN_NUMBER 0x3A2D String        
-+        createField(URN_SCHEMAS_CONTACTS, "internationalisdnnumber"); // PR_ISDN_NUMBER 0x3A2D String
- 
-         createField(URN_SCHEMAS_CONTACTS, "otherTelephone"); // PR_OTHER_TELEPHONE_NUMBER 0x3A21 String
-         createField(URN_SCHEMAS_CONTACTS, "homefax"); // PR_HOME_FAX_NUMBER 0x3A25 String
-@@ -527,9 +527,9 @@
-      *
-      * @param alias DavMail field alias
-      * @param value field value
--     * @return DavProperty with value or DavPropertyName for null values
-+     * @return PropEntry with value or DavPropertyName for null values
-      */
--    public static DavConstants createDavProperty(String alias, String value) {
-+    public static PropEntry createDavProperty(String alias, String value) {
-         Field field = Field.get(alias);
-         if (value == null) {
-             // return DavPropertyName to remove property
diff --git a/debian/patches/no-osx-tray b/debian/patches/no-osx-tray
deleted file mode 100644
index 3921c6e..0000000
--- a/debian/patches/no-osx-tray
+++ /dev/null
@@ -1,259 +0,0 @@
-Description: Remove OSX specific code.
-Forwarded: not-needed
-Author: Alexandre Rossi <alexandre.rossi at gmail.com>
-
-Index: davmail-src-3.9.9-1976/src/java/davmail/ui/tray/OSXAwtGatewayTray.java
-===================================================================
---- davmail-src-3.9.9-1976.orig/src/java/davmail/ui/tray/OSXAwtGatewayTray.java	2011-04-06 22:01:14.000000000 +0200
-+++ /dev/null	1970-01-01 00:00:00.000000000 +0000
-@@ -1,133 +0,0 @@
--/*
-- * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
-- * Copyright (C) 2009  Mickael Guessant
-- *
-- * This program is free software; you can redistribute it and/or
-- * modify it under the terms of the GNU General Public License
-- * as published by the Free Software Foundation; either version 2
-- * of the License, or (at your option) any later version.
-- *
-- * This program is distributed in the hope that it will be useful,
-- * but WITHOUT ANY WARRANTY; without even the implied warranty of
-- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- * GNU General Public License for more details.
-- *
-- * You should have received a copy of the GNU General Public License
-- * along with this program; if not, write to the Free Software
-- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-- */
--package davmail.ui.tray;
--
--import davmail.BundleMessage;
--import davmail.DavGateway;
--import davmail.ui.OSXAdapter;
--import info.growl.Growl;
--import info.growl.GrowlException;
--import info.growl.GrowlUtils;
--import org.apache.log4j.Level;
--import org.apache.log4j.Logger;
--
--import javax.swing.*;
--import java.awt.*;
--import java.awt.image.BufferedImage;
--import java.awt.image.RenderedImage;
--
--/**
-- * Extended Awt tray with OSX extensions.
-- */
--public class OSXAwtGatewayTray extends AwtGatewayTray {
--    protected static final String OSX_TRAY_ACTIVE_PNG = "osxtray2.png";
--    protected static final String OSX_TRAY_PNG = "osxtray.png";
--    protected static final String OSX_TRAY_INACTIVE_PNG = "osxtrayinactive.png";
--
--    private static final Logger LOGGER = Logger.getLogger(OSXAwtGatewayTray.class);
--
--    /**
--     * Exit DavMail Gateway.
--     *
--     * @return true
--     */
--    @SuppressWarnings({"SameReturnValue", "UnusedDeclaration"})
--    public boolean quit() {
--        DavGateway.stop();
--        // dispose frames
--        settingsFrame.dispose();
--        aboutFrame.dispose();
--        if (logBrokerMonitor != null) {
--            logBrokerMonitor.dispose();
--        }
--        return true;
--    }
--
--    @Override
--    protected void createAndShowGUI() {
--        System.setProperty("apple.laf.useScreenMenuBar", "true");
--        super.createAndShowGUI();
--        trayIcon.removeActionListener(settingsListener);
--        try {
--            OSXAdapter.setAboutHandler(this, AwtGatewayTray.class.getDeclaredMethod("about", (Class[]) null));
--            OSXAdapter.setPreferencesHandler(this, AwtGatewayTray.class.getDeclaredMethod("preferences", (Class[]) null));
--            OSXAdapter.setQuitHandler(this, OSXAwtGatewayTray.class.getDeclaredMethod("quit", (Class[]) null));
--        } catch (Exception e) {
--            DavGatewayTray.error(new BundleMessage("LOG_ERROR_LOADING_OSXADAPTER"), e);
--        }
--    }
--
--    @Override
--    protected String getTrayIconPath() {
--        return OSXAwtGatewayTray.OSX_TRAY_PNG;
--    }
--
--    @Override
--    protected String getTrayIconActivePath() {
--        return OSXAwtGatewayTray.OSX_TRAY_ACTIVE_PNG;
--    }
--
--    @Override
--    protected String getTrayIconInactivePath() {
--        return OSXAwtGatewayTray.OSX_TRAY_INACTIVE_PNG;
--    }
--
--    @Override
--    public void displayMessage(final String message, final Level level) {
--        if (!GrowlUtils.isGrowlLoaded()) {
--            super.displayMessage(message, level);
--        } else {
--            SwingUtilities.invokeLater(new Runnable() {
--                public void run() {
--                    if (trayIcon != null) {
--                        Icon icon = null;
--                        if (level.equals(Level.INFO)) {
--                            icon = UIManager.getIcon("OptionPane.informationIcon");
--                        } else if (level.equals(Level.WARN)) {
--                            icon = UIManager.getIcon("OptionPane.warningIcon");
--                        } else if (level.equals(Level.ERROR)) {
--                            icon = UIManager.getIcon("OptionPane.errorIcon");
--                        }
--
--                        if (icon != null && message != null && message.length() > 0) {
--                            try {
--                                String title = BundleMessage.format("UI_DAVMAIL_GATEWAY");
--                                Growl growl = GrowlUtils.getGrowlInstance("DavMail");
--                                growl.addNotification(title, true);
--                                growl.register();
--                                growl.sendNotification(title, title, message, (RenderedImage) getImageForIcon(icon));
--                            } catch (GrowlException growlException) {
--                                LOGGER.error(growlException);
--                            }
--                        }
--                        trayIcon.setToolTip(BundleMessage.format("UI_DAVMAIL_GATEWAY") + '\n' + message);
--                    }
--                }
--            });
--        }
--    }
--
--    protected Image getImageForIcon(Icon icon) {
--        BufferedImage bufferedimage = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);
--        Graphics g = bufferedimage.getGraphics();
--        icon.paintIcon(null, g, 0, 0);
--        g.dispose();
--        return bufferedimage;
--    }
--}
-Index: davmail-src-3.9.9-1976/src/java/davmail/ui/tray/OSXFrameGatewayTray.java
-===================================================================
---- davmail-src-3.9.9-1976.orig/src/java/davmail/ui/tray/OSXFrameGatewayTray.java	2011-07-31 21:26:52.000000000 +0200
-+++ /dev/null	1970-01-01 00:00:00.000000000 +0000
-@@ -1,81 +0,0 @@
--/*
-- * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
-- * Copyright (C) 2009  Mickael Guessant
-- *
-- * This program is free software; you can redistribute it and/or
-- * modify it under the terms of the GNU General Public License
-- * as published by the Free Software Foundation; either version 2
-- * of the License, or (at your option) any later version.
-- *
-- * This program is distributed in the hope that it will be useful,
-- * but WITHOUT ANY WARRANTY; without even the implied warranty of
-- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- * GNU General Public License for more details.
-- *
-- * You should have received a copy of the GNU General Public License
-- * along with this program; if not, write to the Free Software
-- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-- */
--package davmail.ui.tray;
--
--import davmail.BundleMessage;
--import davmail.DavGateway;
--import davmail.ui.OSXAdapter;
--
--import javax.swing.*;
--import java.awt.event.ActionEvent;
--import java.awt.event.ActionListener;
--
--/**
-- * MacOSX specific frame to handle menu
-- */
--public class OSXFrameGatewayTray extends FrameGatewayTray {
--
--    /**
--     * Exit DavMail Gateway.
--     *
--     * @return true
--     */
--    @SuppressWarnings({"SameReturnValue", "UnusedDeclaration"})
--    public boolean quit() {
--        DavGateway.stop();
--        // dispose frames
--        settingsFrame.dispose();
--        aboutFrame.dispose();
--        if (logBrokerMonitor != null) {
--            logBrokerMonitor.dispose();
--        }
--        return true;
--    }
--
--    @Override
--    protected void buildMenu() {
--        // create a popup menu
--        JMenu menu = new JMenu(BundleMessage.format("UI_LOGS"));
--        JMenuBar menuBar = new JMenuBar();
--        menuBar.add(menu);
--        mainFrame.setJMenuBar(menuBar);
--
--        JMenuItem logItem = new JMenuItem(BundleMessage.format("UI_SHOW_LOGS"));
--        logItem.addActionListener(new ActionListener() {
--            public void actionPerformed(ActionEvent e) {
--                showLogs();
--            }
--        });
--        menu.add(logItem);
--    }
--
--
--    @Override
--    protected void createAndShowGUI() {
--        System.setProperty("apple.laf.useScreenMenuBar", "true");
--        super.createAndShowGUI();
--        try {
--            OSXAdapter.setAboutHandler(this, FrameGatewayTray.class.getDeclaredMethod("about", (Class[]) null));
--            OSXAdapter.setPreferencesHandler(this, FrameGatewayTray.class.getDeclaredMethod("preferences", (Class[]) null));
--            OSXAdapter.setQuitHandler(this, OSXFrameGatewayTray.class.getDeclaredMethod("quit", (Class[]) null));
--        } catch (Exception e) {
--            DavGatewayTray.error(new BundleMessage("LOG_ERROR_LOADING_OSXADAPTER"), e);
--        }
--    }
--}
-Index: davmail-src-3.9.9-1976/src/java/davmail/ui/tray/DavGatewayTray.java
-===================================================================
---- davmail-src-3.9.9-1976.orig/src/java/davmail/ui/tray/DavGatewayTray.java	2011-10-29 23:29:19.000000000 +0200
-+++ davmail-src-3.9.9-1976/src/java/davmail/ui/tray/DavGatewayTray.java	2012-07-18 10:36:06.439321059 +0200
-@@ -240,11 +240,7 @@
-             if (davGatewayTray == null) {
-                 try {
-                     if (SystemTray.isSupported()) {
--                        if (isOSX()) {
--                            davGatewayTray = new OSXAwtGatewayTray();
--                        } else {
--                            davGatewayTray = new AwtGatewayTray();
--                        }
-+                        davGatewayTray = new AwtGatewayTray();
-                         davGatewayTray.init();
-                     }
-                 } catch (NoClassDefFoundError e) {
-@@ -252,12 +248,7 @@
-                 }
-             }
-             if (davGatewayTray == null) {
--                if (isOSX()) {
--                    // MacOS
--                    davGatewayTray = new OSXFrameGatewayTray();
--                } else {
--                    davGatewayTray = new FrameGatewayTray();
--                }
-+                davGatewayTray = new FrameGatewayTray();
-                 davGatewayTray.init();
-             }
-         }
diff --git a/debian/patches/no-windows-service b/debian/patches/no-windows-service
deleted file mode 100644
index 58cf84a..0000000
--- a/debian/patches/no-windows-service
+++ /dev/null
@@ -1,16 +0,0 @@
-Description: Avoid winrun4j build dependency
-Forwarded: not-needed
-Author: Alexandre Rossi <alexandre.rossi at gmail.com>
-
-Index: davmail-src-3.9.9-1976/build.xml
-===================================================================
---- davmail-src-3.9.9-1976.orig/build.xml	2012-09-05 10:26:05.632581656 +0200
-+++ davmail-src-3.9.9-1976/build.xml	2012-09-05 10:26:47.276583673 +0200
-@@ -58,6 +58,7 @@
-     <target name="compile" depends="init">
-         <mkdir dir="target/classes"/>
-         <javac srcdir="src/java" destdir="target/classes" source="1.5" target="1.5" debug="on" encoding="UTF-8"
-+               excludes="davmail/service/DavService.java"
-                includeantruntime="false">
-             <classpath>
-                 <path refid="classpath"/>
diff --git a/debian/patches/series b/debian/patches/series
deleted file mode 100644
index fb35001..0000000
--- a/debian/patches/series
+++ /dev/null
@@ -1,6 +0,0 @@
-htmlcleaner22
-no-windows-service
-no-osx-tray
-base64-enc-dec
-jackrabbit2
-fix-build
diff --git a/debian/postinst b/debian/postinst
deleted file mode 100644
index 3e725ec..0000000
--- a/debian/postinst
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/bin/sh -e
-USER=davmail
-
-if ! getent passwd $USER >/dev/null; then
-    adduser --quiet --system --no-create-home --home /var/lib/$USER $USER
-fi
-
-for i in /var/log/$USER.log /var/lib/$USER;
-do
-    if ! dpkg-statoverride --list --quiet "$i" >/dev/null; then
-        dpkg-statoverride --force --quiet --update --add davmail adm 0755 "$i"
-    fi
-done
-
-#DEBHELPER#
diff --git a/debian/rules b/debian/rules
deleted file mode 100755
index 586b7eb..0000000
--- a/debian/rules
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/usr/bin/make -f
-# -*- makefile -*-
-#
-# Uncomment this to turn on verbose mode.
-export DH_VERBOSE=1
-
-# This has to be exported to make some magic below work.
-export DH_OPTIONS
-
-%:
-	dh $@ --with javahelper
-
-override_dh_auto_build:
-	ant -propertyfile debian/ant.properties
diff --git a/debian/source/format b/debian/source/format
deleted file mode 100644
index 163aaf8..0000000
--- a/debian/source/format
+++ /dev/null
@@ -1 +0,0 @@
-3.0 (quilt)
diff --git a/debian/watch b/debian/watch
deleted file mode 100644
index 5bf235d..0000000
--- a/debian/watch
+++ /dev/null
@@ -1,3 +0,0 @@
-version=3
-
-http://sf.net/davmail/davmail-src-(.+)\.tgz
diff --git a/jsmooth-0.9.9-7-patch/build.xml b/jsmooth-0.9.9-7-patch/build.xml
new file mode 100644
index 0000000..091158c
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/build.xml
@@ -0,0 +1,607 @@
+<project name="jsmoothgen" default="jar" basedir=".">
+
+  <!-- set here the properties specific to your computer      -->
+  <!-- see the README.txt file for additional information     -->
+  <!-- DO NOT SKIP THIS PART! REALLY! IT WON'T WORK OTHERWISE -->
+  <property name="JDKDIR" value="c:\Program Files\Java\jdk1.5.0_11"/>
+  <property name="RM" value="CMD /C DEL"/>
+
+  <!-- the autodownload skeleton needs the fltk lib to be available  -->
+  <!-- Unfortunately, I couldn't find a way to call fltk-config      -->
+  <!-- directly from ANT or from the makefile. To find the correct   -->
+  <!-- values for your system, run the msys/mingw shell and type     -->
+  <!-- "fltk-config __ldflags" and "fltk-config __cxxflags"          -->
+  <!-- (replace each _ with a - above, due to xml syntax constraints -->
+  <!-- Then, add the windows path so that the makefile gets it right -->
+  <!-- Below are the default values when you ./configure, make and   -->
+  <!-- make install fltk under an msys environement                  -->
+  <property name="fltk-ldflags" value="-LC:/msys/1.0/local/lib -mwindows -mno-cygwin -lfltk -lole32 -luuid -lcomctl32 -lwsock32"/>
+  <property name="fltk-cxxflags" value="-IC:/msys/1.0/local/include -IC:/msys/1.0/local/include/FL/images -mwindows -DWIN32 -mno-cygwin"/>
+
+  <!-- What's below is  optional (needed only if you build a dist) -->
+  <property name="FOP" value="c:/Programs/fop-0.93\fop.bat"/>
+  <property name="DOCBOOKBASE" value="c:\programs\docbook-xsl-1.72.0"/>
+
+ <!-- set here the properties specific to the release --> 
+
+  <property name="VERSION" value="0.9.9-7"/>
+
+  <!-- You don't need to modify the properties below -->
+
+  <property environment="env"/>
+  <property name="src" value="src"/>
+  <property name="tmp" value="tmp"/>
+  <property name="docs" value="docs"/>
+
+  <property name="anttask-src" value="ant"/>
+  <property name="classes" value="classes"/>
+  <property name="jsmoothjar" value="jsmoothgen.jar"/>
+  <property name="jsmoothjar-ant" value="jsmoothgen-ant.jar"/>
+  <property name="ant.jar" value="${env.ANT_HOME}/lib/ant.jar"/>
+
+  <property name="distbase" value="dist"/>
+  <property name="dist" value="${distbase}/jsmooth-${VERSION}"/>
+
+  <path id="anttask-compile.class.path">
+    <pathelement path="${ant.jar}"/>
+    <pathelement path="${jsmoothjar}/"/>
+    <pathelement path="lib/JimiProClasses.zip"/>
+    <pathelement path="lib/jox116.jar"/>
+    <pathelement path="lib/dtdparser113.jar"/>
+  </path>
+
+  <path id="anttask.class.path">
+    <pathelement path="${ant.jar}"/>
+    <pathelement path="${jsmoothjar-ant}/"/>
+    <pathelement path="lib/JimiProClasses.zip"/>
+    <pathelement path="lib/jox116.jar"/>
+    <pathelement path="lib/dtdparser113.jar"/>
+  </path>
+
+  <path id="jsmooth.class.path">
+      <fileset dir="lib">
+        <include name="**/*.jar"/>
+      </fileset>
+      <fileset dir="skeletons/jnismooth">
+        <include name="*.jar"/>
+      </fileset>
+  </path>
+
+  <target name="init">
+    <!-- Create the time stamp -->
+    <tstamp/>
+    <!-- Create the build directory structure used by compile -->
+    <mkdir dir="${classes}"/>
+  </target>
+
+  <target depends="init" name="compile">
+    <!-- Compile the java code from ${src} into ${classes} -->
+    <echo>Compiling the application from ${src}</echo>
+    <javac target="1.4" source="1.4" deprecation="yes" destdir="${classes}" srcdir="${src}" debug="true">
+       <classpath refid="jsmooth.class.path"/>
+    </javac>
+  </target>
+
+  <target name="docs" depends="init">
+    <!-- Compile the java code from ${src} into ${classes} -->
+
+    <xslt basedir="${docs}" destdir="${docs}"
+       in="${docs}/jsmooth-doc.xml"
+       out="${docs}/jsmooth-doc.html"
+       style="${DOCBOOKBASE}/html/docbook.xsl" >
+ 
+        <param name="admon.graphics" expression="1"/>
+        <param name="section.autolabel" expression="1"/>
+        <param name="section.label.includes.component.label" expression="1"/>
+        <param name="chunk.section.depth" expression="2"/>
+        <param name="html.stylesheet" expression="jsmooth.css"/>
+  
+    </xslt>
+
+    <xslt basedir="${docs}" destdir="${docs}"
+       in="${docs}/jsmooth-doc.xml"
+       out="${docs}/jsmooth-doc.fo"
+       style="${DOCBOOKBASE}/fo/docbook.xsl" >
+          <param name="admon.graphics" expression="1"/>
+          <param name="section.autolabel" expression="1"/>
+         <param name="section.label.includes.component.label" expression="1"/>
+    </xslt>
+
+    <exec executable="${FOP}" dir="${docs}">
+        <arg value="jsmooth-doc.fo"/>
+        <arg value="jsmooth-doc.pdf"/>
+    </exec>
+
+  </target>
+
+  <target depends="compile" name="jar">
+    <!-- Compile the java code from ${src} into ${classes} -->
+    <copy todir="classes/icons">
+      <fileset dir="${src}/icons"/>
+    </copy>
+    <copy todir="classes/locale">
+      <fileset dir="${src}/locale"/>
+    </copy>
+    <jar basedir="classes" jarfile="${jsmoothjar}" manifest="src\MANIFEST.txt"/>
+  </target>
+
+  <target depends="compile" name="anttask">
+    <!-- Compile the java code from ${src} into ${classes} -->
+    <javac deprecation="yes" destdir="${classes}" srcdir="${anttask-src}" debug="true">
+       <classpath refid="anttask-compile.class.path"/>
+    </javac>
+    <unjar src="lib/jox116.jar" dest="${classes}"/>
+    <unjar src="lib/dtdparser113.jar" dest="${classes}"/>
+    <jar basedir="classes" jarfile="${jsmoothjar-ant}" manifest="src\MANIFEST.txt"/>
+  </target>
+
+    <!-- 
+         Builds the distribution file for jsmooth.
+      -->
+
+  <target depends="jar" name="test">
+    <echo>Running test class... ${jsmoothjar}</echo>    
+     <java classname="net.charabia.jsmoothgen.application.gui.beaneditors.BeanPanel" fork="yes" >
+       <classpath refid="jsmooth.class.path"/>
+       <classpath>
+          <pathelement path="${jsmoothjar}"/>
+       </classpath>
+     </java>
+  </target>
+
+  <target depends="" name="dist">
+    
+    <tstamp/>
+    <buildnumber/>
+    <property name="RELEASEINFO" value="Build ${DSTAMP}-${build.number}"/>
+
+    <echo>Building release ${VERSION} - ${RELEASEINFO}</echo>
+
+    <!-- Clean up the directories -->
+    <antcall target="clean"/>
+
+    <!-- Clean up the distribution directory  -->
+
+    <delete dir="${distbase}"/>
+    <mkdir dir="${dist}"/>
+    <mkdir dir="${dist}/docs"/>
+    <mkdir dir="${dist}/lib"/>
+    <mkdir dir="${dist}/sample"/>
+    <mkdir dir="${dist}/skeletons"/>
+
+    
+    <!-- Compile the classes and create the jars           -->
+    <!-- The sources are copied in a temp dir, so that     -->
+    <!-- the VERSION and RELEASEINFO data can be replaced  -->
+
+    <delete dir="${tmp}"/>
+    <mkdir dir="${tmp}"/>
+    <copy todir="${tmp}/src">
+      <fileset dir="src"/>
+    </copy>
+
+    <replace token="@{VERSION}@" dir="${tmp}/src" value="${VERSION}">
+      <include name="**/*.java"/>
+    </replace>
+    <replace token="@{RELEASEINFO}@" dir="${tmp}/src" value="${RELEASEINFO}">
+      <include name="**/*.java"/>
+    </replace>
+
+    <replace token="@{VERSION}@" dir="${tmp}/src" value="${VERSION}">
+      <include name="**/*.properties"/>
+    </replace>
+    <replace token="@{RELEASEINFO}@" dir="${tmp}/src" value="${RELEASEINFO}">
+      <include name="**/*.properties"/>
+    </replace>
+
+    <ant antfile="build.xml" dir="skeletons/jnismooth/" target="dist"/>
+
+    <antcall target="jar">
+      <param name="src" value="${tmp}/src"/>
+    </antcall>
+
+    <antcall target="anttask">
+      <param name="src" value="${tmp}/src"/>
+    </antcall>
+
+    <!-- Copy the jsmooth ant library -->
+    <copy todir="${dist}/lib" file="jsmoothgen.jar"/>
+    <copy todir="${dist}/lib" file="jsmoothgen-ant.jar"/>
+    <copy todir="${dist}/lib" file="lib/jox116.jar"/>
+    <copy todir="${dist}/lib" file="lib/dtdparser113.jar"/>
+    <copy todir="${dist}/lib" file="lib/l2fprod-common-all.jar"/>
+    <copy todir="${dist}/lib" file="lib/riverlayout.jar"/>
+    <copy todir="${dist}/lib" file="lib/BrowserLauncher2-10.jar"/>
+
+    <!-- Now that the ANT task is available, register it  -->
+
+    <taskdef name="jsmoothgen"
+        classname="net.charabia.jsmoothgen.ant.JSmoothGen"
+        classpathref="anttask.class.path"/>
+
+    <!-- Builds the documentation -->
+    <copy todir="${tmp}/docs">
+      <fileset dir="docs"/>
+    </copy>
+    <replace token="@{VERSION}@" dir="${tmp}/docs/" value="${VERSION}">
+      <include name="**/*.xml"/>
+      <include name="**/*.txt"/>
+      <include name="**/*.properties"/>
+    </replace>
+    <replace token="@{RELEASEINFO}@" dir="${tmp}/docs/" value="${RELEASEINFO}">
+      <include name="**/*.xml"/>
+      <include name="**/*.txt"/>
+      <include name="**/*.properties"/>
+    </replace>
+
+    <antcall target="docs">
+      <param name="docs" value="${tmp}/docs"/>
+    </antcall>
+
+    <copy todir="${dist}/docs">
+      <fileset dir="${tmp}/docs">
+        <include name="**/*.pdf"/>
+      </fileset>
+      <fileset dir="${tmp}/docs">
+        <include name="**/*.html"/>
+      </fileset>
+      <fileset dir="${tmp}/docs">
+        <include name="**/*.css"/>
+      </fileset>
+    </copy>
+    <copy todir="${dist}/docs/images">
+      <fileset dir="${tmp}/docs/images"/>
+    </copy>
+
+    <copy todir="${dist}" file="readme.txt"/>
+    <copy todir="${dist}" file="LICENSE.txt"/>
+    <copy todir="${dist}" file="LGPL-LICENSE.txt"/>
+    <copy todir="${dist}" file="GPL-LICENSE.txt"/>
+    <copy tofile="${dist}/Changelog.txt" file="Changelog"/>
+
+    <!-- Compile the wrappers -->
+    <ant antfile="build.xml" dir="skeletons/jnismooth/" target="dist"/>
+    <ant antfile="build.xml" dir="skeletons/samplejar/" target="dist"/>
+    <ant antfile="build.xml" dir="skeletons/consolewrapper/samplejar/" target="dist"/>
+    
+    <mkdir dir="${dist}/jni"/>
+    <copy todir="${dist}/jni" file="skeletons/jnismooth/jnismooth.jar"/>
+    <javadoc packagenames="jsmooth.*"
+	     sourcepath="skeletons/jnismooth/src"
+             destdir="${dist}/docs/jniapi"
+             author="true"
+             version="true"
+             use="true"
+             windowtitle="JSmooth JNI API" />
+
+    <!-- build all 32bit wrappers -->
+    <mkdir dir="${dist}/skeletons/windowed-wrapper"/>
+    <mkdir dir="${dist}/skeletons/console-wrapper"/>
+    <mkdir dir="${dist}/skeletons/autodownload-wrapper"/>
+    <mkdir dir="${dist}/skeletons/winservice-wrapper"/>
+    <antcall target="compileskels">
+      <param name="skelflags" value="-static-libgcc"/>
+    </antcall>
+    <copy todir="${dist}/skeletons/windowed-wrapper" file="skeletons/simplewrap/jwrap.exe"/>
+    <copy todir="${dist}/skeletons/windowed-wrapper" file="skeletons/simplewrap/description.skel"/>
+    <copy todir="${dist}/skeletons/console-wrapper" file="skeletons/consolewrapper/consolewrapper.exe"/>
+    <copy todir="${dist}/skeletons/console-wrapper" file="skeletons/consolewrapper/description.skel"/>
+    <copy todir="${dist}/skeletons/autodownload-wrapper" file="skeletons/autodownload/autodownload.skel"/>
+    <copy todir="${dist}/skeletons/autodownload-wrapper" file="skeletons/autodownload/customdownload.skel"/>
+    <copy todir="${dist}/skeletons/autodownload-wrapper" file="skeletons/autodownload/autodownload.exe"/>
+    <copy todir="${dist}/skeletons/winservice-wrapper" file="skeletons/winservice/description.skel"/>
+    <copy todir="${dist}/skeletons/winservice-wrapper" file="skeletons/winservice/winservice.exe"/>
+    <exec executable="c:\mingw-w32\bin\strip.exe" dir="${dist}/skeletons/console-wrapper">
+        <arg value="consolewrapper.exe"/>
+    </exec>
+    <exec executable="c:\mingw-w32\bin\strip.exe" dir="${dist}/skeletons/windowed-wrapper">
+        <arg value="jwrap.exe"/>
+    </exec>
+    <exec executable="c:\mingw-w32\bin\strip.exe" dir="${dist}/skeletons/autodownload-wrapper">
+        <arg value="autodownload.exe"/>
+    </exec>
+    <exec executable="c:\mingw-w32\bin\strip.exe" dir="${dist}/skeletons/winservice-wrapper">
+        <arg value="winservice.exe"/>
+    </exec>
+    
+    <!-- build all 64bits wrappers -->
+    <antcall target="clean64"/>
+    <mkdir dir="${dist}/skeletons/windowed-wrapper64"/>
+    <mkdir dir="${dist}/skeletons/console-wrapper64"/>
+    <!-- not available for x64
+    <mkdir dir="${dist}/skeletons/autodownload-wrapper64"/>
+		-->
+    <mkdir dir="${dist}/skeletons/winservice-wrapper64"/>
+    <antcall target="compileskels64">
+      <param name="skelflags" value="-static-libgcc"/>
+    </antcall>
+    <copy todir="${dist}/skeletons/windowed-wrapper64" file="skeletons/simplewrap/jwrap.exe"/>
+    <copy todir="${dist}/skeletons/windowed-wrapper64" file="skeletons/simplewrap/description64.skel"/>
+    <copy todir="${dist}/skeletons/console-wrapper64" file="skeletons/consolewrapper/consolewrapper.exe"/>
+    <copy todir="${dist}/skeletons/console-wrapper64" file="skeletons/consolewrapper/description64.skel"/>
+    <!-- not available for x64
+    <copy todir="${dist}/skeletons/autodownload-wrapper64" file="skeletons/autodownload/autodownload64.skel"/>
+    <copy todir="${dist}/skeletons/autodownload-wrapper64" file="skeletons/autodownload/customdownload64.skel"/>
+    <copy todir="${dist}/skeletons/autodownload-wrapper64" file="skeletons/autodownload/autodownload.exe"/>
+		-->
+    <copy todir="${dist}/skeletons/winservice-wrapper64" file="skeletons/winservice/description64.skel"/>
+    <copy todir="${dist}/skeletons/winservice-wrapper64" file="skeletons/winservice/winservice.exe"/>
+    <exec executable="c:\mingw-w64\bin\strip.exe" dir="${dist}/skeletons/console-wrapper64">
+      <arg value="consolewrapper.exe"/>
+    </exec>
+    <exec executable="c:\mingw-w64\bin\strip.exe" dir="${dist}/skeletons/windowed-wrapper64">
+      <arg value="jwrap.exe"/>
+    </exec>
+    <!-- not available for x64
+    <exec executable="c:\mingw-w64\bin\strip.exe" dir="${dist}/skeletons/autodownload-wrapper64">
+      <arg value="autodownload.exe"/>
+    </exec>
+    -->
+    <exec executable="c:\mingw-w64\bin\strip.exe" dir="${dist}/skeletons/winservice-wrapper64">
+      <arg value="winservice.exe"/>
+    </exec>
+
+    <!-- Builds the jsmooth executable -->
+    <jsmoothgen project="jsprj/jsmoothgen.jsmooth" skeletonroot="${dist}/skeletons"/>
+    <jsmoothgen project="jsprj/jsmoothcmd.jsmooth" skeletonroot="${dist}/skeletons"/>
+    <jsmoothgen project="jsprj/jsmoothcmd64.jsmooth" skeletonroot="${dist}/skeletons"/>
+    <ant antfile="build.xml" dir="sample" target="dist"/>
+    <jsmoothgen project="jsprj/prooftest.jsmooth" skeletonroot="${dist}/skeletons"/>
+    <jsmoothgen project="jsprj/prooftest64.jsmooth" skeletonroot="${dist}/skeletons"/>
+    
+    <copy todir="${dist}" file="jsmoothgen.exe"/>
+    <copy todir="${dist}" file="jsmoothcmd.exe"/>
+    <copy todir="${dist}" file="jsmoothcmd64.exe"/>
+    <copy todir="${dist}" file="proof-test.exe"/>
+    <copy todir="${dist}" file="proof-test64.exe"/>
+    <copy todir="${dist}/sample">
+      <fileset dir="sample"/>
+    </copy>
+
+  </target>
+
+   <target name="compileskel" depends="">
+    <echo>Making ${skelname} ${target}</echo>
+     <exec executable="c:\mingw-w32\bin\make.exe" dir="skeletons/${skelname}">
+       <env key="PATH" value="C:\mingw-w32\bin"/>
+       <arg value="-f"/>
+       <arg value="Makefile.win"/>
+       <arg value="${target}"/>
+       <arg value='CUSTOMFLAGS=${skelflags}'/>
+       <arg value='"MINGW=c:/mingw-w32"'/>
+       <arg value='"TARGET=i686-w64-mingw32"'/>
+       <arg value='"JDK=${JDKDIR}"'/>
+       <arg value='"RM=${RM}"'/>
+       <arg value='"FLTK-LDFLAGS=${fltk-ldflags}"'/>
+       <arg value='"FLTK-CXXFLAGS=${fltk-cxxflags}"'/>
+     </exec>
+   </target>
+  
+  <target name="compileskel64" depends="">
+   <echo>Making ${skelname} ${target}</echo>
+    <exec executable="c:\mingw-w64\bin\make.exe" dir="skeletons/${skelname}">
+      <env key="PATH" value="C:\mingw-w64\bin"/>
+      <arg value="-f"/>
+      <arg value="Makefile.win"/>
+      <arg value="${target}"/>
+      <arg value='CUSTOMFLAGS=${skelflags}'/>
+      <arg value='"MINGW=c:/mingw-w64"'/>
+      <arg value='"TARGET=x86_64-w64-mingw32"'/>
+      <arg value='"JDK=${JDKDIR}"'/>
+      <arg value='"RM=${RM}"'/>
+      <arg value='"FLTK-LDFLAGS=${fltk-ldflags}"'/>
+      <arg value='"FLTK-CXXFLAGS=${fltk-cxxflags}"'/>
+    </exec>
+  </target>
+
+   <target name="compileskels" depends="">
+     <ant dir="skeletons/simplewrap/samplejar" /> 
+     <ant dir="skeletons/consolewrapper/samplejar" /> 
+
+    <antcall target="compileskel">
+      <param name="skelname" value="util-core"/>
+      <param name="skelflags" value="${skelflags}"/>
+      <param name="target" value="all"/>
+    </antcall>
+    <antcall target="compileskel">
+      <param name="skelname" value="util-net"/>
+      <param name="skelflags" value="${skelflags}"/>
+      <param name="target" value="all"/>
+    </antcall>
+    <antcall target="compileskel">
+      <param name="skelname" value="commonjava"/>
+      <param name="skelflags" value="${skelflags}"/>
+      <param name="target" value="all"/>
+    </antcall>
+    <antcall target="compileskel">
+      <param name="skelname" value="consolewrapper"/>
+      <param name="skelflags" value="${skelflags}"/>
+      <param name="target" value="all"/>
+    </antcall>
+    <antcall target="compileskel">
+      <param name="skelname" value="simplewrap"/>
+      <param name="skelflags" value="${skelflags}"/>
+      <param name="target" value="all"/>
+    </antcall>
+    <antcall target="compileskel">
+      <param name="skelname" value="autodownload"/>
+      <param name="skelflags" value="${skelflags}"/>
+      <param name="target" value="all"/>
+    </antcall>
+    <antcall target="compileskel">
+      <param name="skelname" value="winservice"/>
+      <param name="skelflags" value="${skelflags}"/>
+      <param name="target" value="all"/>
+    </antcall>
+   </target>
+
+  <target name="clean">
+    <delete dir="${classes}"/>
+    <delete dir="${distbase}"/>
+    <delete dir="${tmp}"/>
+    <delete file="${jsmoothjar}"/>
+    <delete file="${jsmoothjar-ant}"/>
+
+    <ant antfile="build.xml" dir="sample" target="clean"/>
+
+    <antcall target="compileskel">
+      <param name="skelname" value="util-core"/>
+      <param name="skelflags" value="${skelflags}"/>
+      <param name="target" value="clean"/>
+    </antcall>
+    <antcall target="compileskel">
+      <param name="skelname" value="util-net"/>
+      <param name="skelflags" value="${skelflags}"/>
+      <param name="target" value="clean"/>
+    </antcall>
+    <antcall target="compileskel">
+      <param name="skelname" value="commonjava"/>
+      <param name="skelflags" value="${skelflags}"/>
+      <param name="target" value="clean"/>
+    </antcall>
+    <antcall target="compileskel">
+      <param name="skelname" value="consolewrapper"/>
+      <param name="skelflags" value="${skelflags}"/>
+      <param name="target" value="clean"/>
+    </antcall>
+    <antcall target="compileskel">
+      <param name="skelname" value="simplewrap"/>
+      <param name="skelflags" value="${skelflags}"/>
+      <param name="target" value="clean"/>
+    </antcall>
+    <antcall target="compileskel">
+      <param name="skelname" value="autodownload"/>
+      <param name="skelflags" value="${skelflags}"/>
+      <param name="target" value="clean"/>
+    </antcall>
+    <antcall target="compileskel">
+      <param name="skelname" value="winservice"/>
+      <param name="skelflags" value="${skelflags}"/>
+      <param name="target" value="clean"/>
+    </antcall>
+
+    <delete file="jsmoothgen.exe"/>
+    <delete file="jsmoothcmd.exe"/>
+    <delete file="proof-test.exe"/>
+  </target>
+
+    <target name="compileskels64" depends="">
+      <ant dir="skeletons/simplewrap/samplejar" /> 
+      <ant dir="skeletons/consolewrapper/samplejar" /> 
+
+     <antcall target="compileskel64">
+       <param name="skelname" value="util-core"/>
+       <param name="skelflags" value="${skelflags}"/>
+       <param name="target" value="all"/>
+     </antcall>
+      <!-- not available for x64
+     <antcall target="compileskel64">
+       <param name="skelname" value="util-net"/>
+       <param name="skelflags" value="${skelflags}"/>
+       <param name="target" value="all"/>
+     </antcall>
+		-->
+     <antcall target="compileskel64">
+       <param name="skelname" value="commonjava"/>
+       <param name="skelflags" value="${skelflags}"/>
+       <param name="target" value="all"/>
+     </antcall>
+     <antcall target="compileskel64">
+       <param name="skelname" value="consolewrapper"/>
+       <param name="skelflags" value="${skelflags}"/>
+       <param name="target" value="all"/>
+     </antcall>
+     <antcall target="compileskel64">
+       <param name="skelname" value="simplewrap"/>
+       <param name="skelflags" value="${skelflags}"/>
+       <param name="target" value="all"/>
+     </antcall>
+      <!-- not available for x64
+     <antcall target="compileskel64">
+       <param name="skelname" value="autodownload"/>
+       <param name="skelflags" value="${skelflags}"/>
+       <param name="target" value="all"/>
+     </antcall>
+		-->
+     <antcall target="compileskel64">
+       <param name="skelname" value="winservice"/>
+       <param name="skelflags" value="${skelflags}"/>
+       <param name="target" value="all"/>
+     </antcall>
+    </target>
+
+   <target name="clean64">
+     <antcall target="compileskel64">
+       <param name="skelname" value="util-core"/>
+       <param name="skelflags" value="${skelflags}"/>
+       <param name="target" value="clean"/>
+     </antcall>
+     <antcall target="compileskel64">
+       <param name="skelname" value="util-net"/>
+       <param name="skelflags" value="${skelflags}"/>
+       <param name="target" value="clean"/>
+     </antcall>
+     <antcall target="compileskel64">
+       <param name="skelname" value="commonjava"/>
+       <param name="skelflags" value="${skelflags}"/>
+       <param name="target" value="clean"/>
+     </antcall>
+     <antcall target="compileskel64">
+       <param name="skelname" value="consolewrapper"/>
+       <param name="skelflags" value="${skelflags}"/>
+       <param name="target" value="clean"/>
+     </antcall>
+     <antcall target="compileskel64">
+       <param name="skelname" value="simplewrap"/>
+       <param name="skelflags" value="${skelflags}"/>
+       <param name="target" value="clean"/>
+     </antcall>
+     <antcall target="compileskel64">
+       <param name="skelname" value="autodownload"/>
+       <param name="skelflags" value="${skelflags}"/>
+       <param name="target" value="clean"/>
+     </antcall>
+     <antcall target="compileskel64">
+       <param name="skelname" value="winservice"/>
+       <param name="skelflags" value="${skelflags}"/>
+       <param name="target" value="clean"/>
+     </antcall>
+
+     <delete file="jsmoothgen.exe"/>
+     <delete file="jsmoothcmd.exe"/>
+     <delete file="proof-test.exe"/>
+   </target>
+  
+  <target name="run" depends="jar">
+     <java classname="net.charabia.jsmoothgen.application.gui.MainFrame" fork="yes" >
+       <classpath refid="jsmooth.class.path"/>
+       <classpath>
+          <pathelement path="${jsmoothjar}"/>
+       </classpath>
+        <arg value="c:/temp/test/testservice.jsmooth"/>
+     </java>
+  </target>
+
+  <target name="runskeletoneditor" depends="jar">
+     <java classname="net.charabia.jsmoothgen.skeleton.SkeletonEditor" fork="yes" >
+       <classpath refid="jsmooth.class.path"/>
+       <classpath>
+          <pathelement path="${jsmoothjar}"/>
+       </classpath>
+     </java>
+  </target>
+
+    <target name="swt-run" depends="jar">
+		<java classname="net.charabia.jsmoothgen.application.swtgui.JSmoothApplication"
+		 	  fork="yes" >
+		 	
+			<classpath refid="jsmooth.class.path"/>
+			<classpath>
+				<pathelement path="${jsmoothjar}"/>
+			</classpath>
+            <jvmarg value="-Djava.library.path=lib/os"/>
+		</java>
+	</target>
+
+</project>
+
diff --git a/jsmooth-0.9.9-7-patch/jsprj/jsmoothcmd.jsmooth b/jsmooth-0.9.9-7-patch/jsprj/jsmoothcmd.jsmooth
new file mode 100644
index 0000000..b9b4ec0
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/jsprj/jsmoothcmd.jsmooth
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<jsmoothproject>
+<JVMSearchPath>registry</JVMSearchPath>
+<JVMSearchPath>javahome</JVMSearchPath>
+<JVMSearchPath>jrepath</JVMSearchPath>
+<JVMSearchPath>jdkpath</JVMSearchPath>
+<JVMSearchPath>exepath</JVMSearchPath>
+<JVMSearchPath>jview</JVMSearchPath>
+<classPath>..\lib\jox116.jar</classPath>
+<classPath>..\lib\JimiProClasses.zip</classPath>
+<classPath>..\lib\dtdparser113.jar</classPath>
+<classPath>..\lib\jsmoothgen.jar</classPath>
+<embeddedJar>false</embeddedJar>
+<executableName>..\jsmoothcmd.exe</executableName>
+<iconLocation>..\src\icons\gnome-application-x-jar-bm.gif</iconLocation>
+<initialMemoryHeap>-1</initialMemoryHeap>
+<javaProperties>
+<name>jsmooth.basedir</name>
+<value>${EXECUTABLEPATH} </value>
+</javaProperties>
+<mainClassName>net.charabia.jsmoothgen.application.cmdline.CommandLine</mainClassName>
+<maximumMemoryHeap>-1</maximumMemoryHeap>
+<maximumVersion></maximumVersion>
+<minimumVersion>1.4</minimumVersion>
+<uacRequireAdministrator>0</uacRequireAdministrator>
+<skeletonName>Console Wrapper</skeletonName>
+<skeletonProperties>
+<key>Message</key>
+<value>This program needs Java 1.4 or higher to run.
+Please download it at http://www.java.com</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>PressKey</key>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>Debug</key>
+<value>0</value>
+</skeletonProperties>
+</jsmoothproject>
diff --git a/jsmooth-0.9.9-7-patch/jsprj/jsmoothcmd64.jsmooth b/jsmooth-0.9.9-7-patch/jsprj/jsmoothcmd64.jsmooth
new file mode 100644
index 0000000..acde98e
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/jsprj/jsmoothcmd64.jsmooth
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<jsmoothproject>
+<JVMSearchPath>registry</JVMSearchPath>
+<JVMSearchPath>javahome</JVMSearchPath>
+<JVMSearchPath>jrepath</JVMSearchPath>
+<JVMSearchPath>jdkpath</JVMSearchPath>
+<JVMSearchPath>exepath</JVMSearchPath>
+<JVMSearchPath>jview</JVMSearchPath>
+<classPath>..\lib\jox116.jar</classPath>
+<classPath>..\lib\JimiProClasses.zip</classPath>
+<classPath>..\lib\dtdparser113.jar</classPath>
+<classPath>..\lib\jsmoothgen.jar</classPath>
+<embeddedJar>false</embeddedJar>
+<executableName>..\jsmoothcmd64.exe</executableName>
+<iconLocation>..\src\icons\gnome-application-x-jar-bm.gif</iconLocation>
+<initialMemoryHeap>-1</initialMemoryHeap>
+<javaProperties>
+<name>jsmooth.basedir</name>
+<value>${EXECUTABLEPATH} </value>
+</javaProperties>
+<mainClassName>net.charabia.jsmoothgen.application.cmdline.CommandLine</mainClassName>
+<maximumMemoryHeap>-1</maximumMemoryHeap>
+<maximumVersion></maximumVersion>
+<minimumVersion>1.4</minimumVersion>
+<uacRequireAdministrator>0</uacRequireAdministrator>
+<skeletonName>Console Wrapper x64</skeletonName>
+<skeletonProperties>
+<key>Message</key>
+<value>This program needs Java 1.4 or higher to run.
+Please download it at http://www.java.com</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>PressKey</key>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>Debug</key>
+<value>0</value>
+</skeletonProperties>
+</jsmoothproject>
diff --git a/jsmooth-0.9.9-7-patch/jsprj/jsmoothgen.jsmooth b/jsmooth-0.9.9-7-patch/jsprj/jsmoothgen.jsmooth
new file mode 100644
index 0000000..ec86efc
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/jsprj/jsmoothgen.jsmooth
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<jsmoothproject>
+<JVMSearchPath>registry</JVMSearchPath>
+<JVMSearchPath>exepath</JVMSearchPath>
+<JVMSearchPath>javahome</JVMSearchPath>
+<JVMSearchPath>jrepath</JVMSearchPath>
+<JVMSearchPath>jdkpath</JVMSearchPath>
+<JVMSearchPath>jview</JVMSearchPath>
+<classPath>..\lib\jox116.jar</classPath>
+<classPath>..\lib\dtdparser113.jar</classPath>
+<classPath>..\lib\jsmoothgen.jar</classPath>
+<classPath>..\lib\l2fprod-common-all.jar</classPath>
+<classPath>..\jsmoothgen.jar</classPath>
+<classPath>..\lib\riverlayout.jar</classPath>
+<classPath>..\lib\BrowserLauncher2-10.jar</classPath>
+<currentDirectory>${EXECUTABLEPATH}</currentDirectory>
+<embeddedJar>false</embeddedJar>
+<executableName>..\JSmoothGen.exe</executableName>
+<iconLocation>..\src\icons\gnome-application-x-jar-bm.gif</iconLocation>
+<initialMemoryHeap>-1</initialMemoryHeap>
+<mainClassName>net.charabia.jsmoothgen.application.gui.MainFrame</mainClassName>
+<maximumMemoryHeap>-1</maximumMemoryHeap>
+<maximumVersion></maximumVersion>
+<minimumVersion>1.4</minimumVersion>
+<uacRequireAdministrator>0</uacRequireAdministrator>
+<skeletonName>Autodownload Wrapper</skeletonName>
+<skeletonProperties>
+<key>Message</key>
+<value>Java 1.4 or above has not been found on your computer. Do you want to download a compatible Java Environment?</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>DownloadURL</key>
+<value>http://java.sun.com/update/1.6.0/jinstall-6-windows-i586.cab</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>SingleProcess</key>
+<value>1</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>SingleInstance</key>
+<value>1</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>JniSmooth</key>
+<value>1</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>Debug</key>
+<value>0</value>
+</skeletonProperties>
+</jsmoothproject>
diff --git a/jsmooth-0.9.9-7-patch/jsprj/prooftest.jsmooth b/jsmooth-0.9.9-7-patch/jsprj/prooftest.jsmooth
new file mode 100644
index 0000000..fd56942
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/jsprj/prooftest.jsmooth
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<jsmoothproject>
+<JVMSearchPath>registry</JVMSearchPath>
+<JVMSearchPath>javahome</JVMSearchPath>
+<JVMSearchPath>jrepath</JVMSearchPath>
+<JVMSearchPath>jdkpath</JVMSearchPath>
+<JVMSearchPath>exepath</JVMSearchPath>
+<JVMSearchPath>jview</JVMSearchPath>
+<arguments>simpleargument "multiple tokens" c:\my\test\here</arguments>
+<embeddedJar>true</embeddedJar>
+<executableName>..\proof-test.exe</executableName>
+<iconLocation>..\src\icons\stock_web-support.png</iconLocation>
+<initialMemoryHeap>-1</initialMemoryHeap>
+<jarLocation>..\sample\sample.jar</jarLocation>
+<mainClassName>JSmoothPropertiesDisplayer</mainClassName>
+<maximumMemoryHeap>-1</maximumMemoryHeap>
+<maximumVersion></maximumVersion>
+<minimumVersion>1.1</minimumVersion>
+<uacRequireAdministrator>0</uacRequireAdministrator>
+<skeletonName>Console Wrapper</skeletonName>
+<skeletonProperties>
+<key>Message</key>
+<value>This program needs Java 1.4 or higher to run.
+Please download it at http://www.java.com</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>PressKey</key>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>Debug</key>
+<value>1</value>
+</skeletonProperties>
+</jsmoothproject>
diff --git a/jsmooth-0.9.9-7-patch/jsprj/prooftest64.jsmooth b/jsmooth-0.9.9-7-patch/jsprj/prooftest64.jsmooth
new file mode 100644
index 0000000..9b50eca
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/jsprj/prooftest64.jsmooth
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<jsmoothproject>
+<JVMSearchPath>registry</JVMSearchPath>
+<JVMSearchPath>javahome</JVMSearchPath>
+<JVMSearchPath>jrepath</JVMSearchPath>
+<JVMSearchPath>jdkpath</JVMSearchPath>
+<JVMSearchPath>exepath</JVMSearchPath>
+<JVMSearchPath>jview</JVMSearchPath>
+<arguments>simpleargument "multiple tokens" c:\my\test\here</arguments>
+<embeddedJar>true</embeddedJar>
+<executableName>..\proof-test64.exe</executableName>
+<iconLocation>..\src\icons\stock_web-support.png</iconLocation>
+<initialMemoryHeap>-1</initialMemoryHeap>
+<jarLocation>..\sample\sample.jar</jarLocation>
+<mainClassName>JSmoothPropertiesDisplayer</mainClassName>
+<maximumMemoryHeap>-1</maximumMemoryHeap>
+<maximumVersion></maximumVersion>
+<minimumVersion>1.1</minimumVersion>
+<uacRequireAdministrator>0</uacRequireAdministrator>
+<skeletonName>Console Wrapper x64</skeletonName>
+<skeletonProperties>
+<key>Message</key>
+<value>This program needs Java 1.4 or higher to run.
+Please download it at http://www.java.com</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>PressKey</key>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>Debug</key>
+<value>1</value>
+</skeletonProperties>
+</jsmoothproject>
diff --git a/jsmooth-0.9.9-7-patch/sample/build.xml b/jsmooth-0.9.9-7-patch/sample/build.xml
new file mode 100644
index 0000000..47c5390
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/sample/build.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project basedir="." default="dist" name="jsmooth-sample">
+
+  <!-- set global properties for this build -->
+  <property name="src" value="src"/>
+  <property name="classes" value="classes"/>
+
+  <path id="build.class.path">
+    <pathelement path="../skeletons/jnismooth/jnismooth.jar"/>
+  </path>
+
+  <target name="init">
+    <!-- Create the time stamp -->
+    <tstamp/>
+    <!-- Create the build directory structure used by compile -->
+    <mkdir dir="${classes}"/>
+  </target>
+
+  <target depends="init" name="compile">
+    <!-- Compile the java code from ${src} into ${classes} -->
+    <javac deprecation="yes" destdir="${classes}" srcdir="${src}" source="1.4" target="1.4">
+       <classpath refid="build.class.path"/>
+    </javac>
+  </target>
+
+  <target depends="compile" name="dist">
+    <!-- Compile the java code from ${src} into ${classes} -->
+    <jar basedir="classes" jarfile="sample.jar" manifest="src\MANIFEST.txt"/>    
+    </target>
+
+  <target name="run" depends="dist">
+     <java classname="JSmoothPropertiesDisplayer" fork="yes" >
+       <classpath refid="build.class.path"/>
+       <classpath>
+          <pathelement path="sample.jar"/>
+       </classpath>
+     </java>
+  </target>
+
+  <target name="clean">
+    <delete dir="${classes}"/>
+    <delete file="sample.jar"/>
+  </target>
+</project>
diff --git a/jsmooth-0.9.9-7-patch/skeletons/autodownload/Makefile.win b/jsmooth-0.9.9-7-patch/skeletons/autodownload/Makefile.win
new file mode 100644
index 0000000..eb21df7
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/skeletons/autodownload/Makefile.win
@@ -0,0 +1,46 @@
+# Project: util-core
+
+PROJECTNAME=autodownload
+##libmscabd_la_SOURCES = mspack/system.c mspack/cabd.c mspack/lzxd.c mspack/mszipd.c mspack/qtmd.c
+##libmscabd_la_OBJS = system.o cabd.o lzxd.o mszipd.o qtmd.o
+
+SHELL = /bin/sh
+RM   = rm -f
+CPP  = ${MINGW}/bin/${TARGET}-g++.exe
+CC   = ${MINGW}/bin/${TARGET}-gcc.exe
+WINDRES = windres.exe
+MINGW =
+RES  = JWrap_private.res
+LINKOBJ  = main.o execcab.o execexe.o $(RES)
+OBJ  = main.o execcab.o execexe.o $(libmscabd_la_OBJS) $(RES)
+FLTK-LDFLAGS = $(shell fltk-config --ldflags --use-images)
+FLTK-CXXFLAGS = $(shell fltk-config --cxxflags --use-images)
+##LINKLIBS = ../commonjava/CommonJava.a ../util-core/util-core.a mspack.a ../util-net/util-net.a
+LINKLIBS = ../commonjava/CommonJava.a ../util-net/util-net.a ../util-core/util-core.a 
+LIBS =  -static-libgcc -L. -L"$(MINGW)/lib" -lws2_32 -L"${MINGW}/bin/libgcc_s_sjlj-1.dll" -L"/lib" -L"../commonjava" -mwindows -L"../util-core" -L"../util-net" $(LINKLIBS) $(FLTK-LDFLAGS) -lsetupapi -lwininet
+INCS =  -I"$(MINGW)/include"  -I"$(JDK)/include"  -I"$(JDK)/include/win32" 
+CXXINCS = -I"$(MINGW)/include/c++"  -I"$(MINGW)/include/c++/mingw32"  -I"$(MINGW)/include/c++/backward"  -I"$(MINGW)/include"  -I"$(JDK)/include"  -I"$(JDK)/include/win32" -I"../commonjava" -I"../util-core" -I"../util-net" $(FLTK-CXXFLAGS)
+BIN  = autodownload.exe
+CXXFLAGS = $(CUSTOMFLAGS) $(CXXINCS) -DJDK="$(JDK)"
+CFLAGS = $(INCS) 
+
+.PHONY: all all-before all-after clean clean-custom $(PROJECTNAME)
+
+all: all-before $(BIN) all-after
+
+clean: 	clean-custom
+	$(RM) $(OBJ) $(BIN) JWrap_private.res
+
+$(BIN): $(LINKOBJ) $(LINKLIBS)
+	$(CPP) $(CXXFLAGS) $(LINKOBJ) $(LIBS) -o $(BIN)
+
+#mspack.a: $(libmscabd_la_SOURCES)
+#	$(CC) -c -Imspack $(libmscabd_la_SOURCES)
+#	ar r mspack.a $(libmscabd_la_OBJS)
+#	ranlib mspack.a
+
+JWrap_private.res: JWrap_private.rc mainres.rc 
+	$(WINDRES) -i JWrap_private.rc -J rc -o JWrap_private.res -O coff 
+
+main.o:	main.cpp
+	$(CPP) -c main.cpp $(CXXFLAGS)
diff --git a/jsmooth-0.9.9-7-patch/skeletons/autodownload/autodownload64.skel b/jsmooth-0.9.9-7-patch/skeletons/autodownload/autodownload64.skel
new file mode 100644
index 0000000..4841107
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/skeletons/autodownload/autodownload64.skel
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<jsmoothskeleton>
+<debug>false</debug>
+<description>SKEL_AUTODOWNLOAD_PROPERTY_DESCRIPTION</description>
+<executableName>autodownload.exe</executableName>
+<resourceCategory>JAVA</resourceCategory>
+<resourceJarId>102</resourceJarId>
+<resourcePropsId>103</resourcePropsId>
+<shortName>Autodownload Wrapper x64</shortName>
+<skeletonProperties>
+<description>SKEL_AUTODOWNLOAD_PROPERTY_MESSAGE_DESCRIPTION</description>
+<idName>Message</idName>
+<label>SKEL_AUTODOWNLOAD_PROPERTY_MESSAGE_LABEL</label>
+<type>textarea</type>
+<value>Java has not been found on your computer. Do you want to download it?</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_AUTODOWNLOAD_PROPERTY_DLURL_DESCRIPTION</description>
+<idName>DownloadURL</idName>
+<label>SKEL_AUTODOWNLOAD_PROPERTY_DLURL_LABEL</label>
+<type>autodownloadurl</type>
+<value>http://java.sun.com/update/1.5.0/jinstall-1_5_0_11-windows-i586.cab</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_PROPERTY_SINGLEPROCESS_DESCRIPTION</description>
+<idName>SingleProcess</idName>
+<label>SKEL_GENERIC_PROPERTY_SINGLEPROCESS_LABEL</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_SINGLEINSTANCE_DESCRIPTION</description>
+<idName>SingleInstance</idName>
+<label>SKEL_GENERIC_SINGLEINSTANCE</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_JNISMOOTH_DESCRIPTION</description>
+<idName>JniSmooth</idName>
+<label>SKEL_GENERIC_JNISMOOTH</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_PROPERTY_DEBUG_DESCRIPTION</description>
+<idName>Debug</idName>
+<label>SKEL_GENERIC_PROPERTY_DEBUG_LABEL</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+</jsmoothskeleton>
diff --git a/jsmooth-0.9.9-7-patch/skeletons/autodownload/customdownload64.skel b/jsmooth-0.9.9-7-patch/skeletons/autodownload/customdownload64.skel
new file mode 100644
index 0000000..5c81ff2
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/skeletons/autodownload/customdownload64.skel
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<jsmoothskeleton>
+<debug>false</debug>
+<description>SKEL_CUSTOMDOWNLOAD_DESCRIPTION</description>
+<executableName>autodownload.exe</executableName>
+<resourceCategory>JAVA</resourceCategory>
+<resourceJarId>102</resourceJarId>
+<resourcePropsId>103</resourcePropsId>
+<shortName>Custom Web Download Wrapper x64</shortName>
+<skeletonProperties>
+<description>SKEL_CUSTOMDOWNLOAD_PROPERTY_MESSAGE_DESCRIPTION</description>
+<idName>Message</idName>
+<label>SKEL_CUSTOMDOWNLOAD_PROPERTY_MESSAGE_LABEL</label>
+<type>textarea</type>
+<value>Java has not been found on your computer. Do you want to download it?</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_CUSTOMDOWNLOAD_PROPERTY_URL_DESCRIPTION</description>
+<idName>DownloadURL</idName>
+<label>SKEL_CUSTOMDOWNLOAD_PROPERTY_URL_LABEL</label>
+<type>string</type>
+<value>http://</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_PROPERTY_SINGLEPROCESS_DESCRIPTION</description>
+<idName>SingleProcess</idName>
+<label>SKEL_GENERIC_PROPERTY_SINGLEPROCESS_LABEL</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_SINGLEINSTANCE_DESCRIPTION</description>
+<idName>SingleInstance</idName>
+<label>SKEL_GENERIC_SINGLEINSTANCE</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_JNISMOOTH_DESCRIPTION</description>
+<idName>JniSmooth</idName>
+<label>SKEL_GENERIC_JNISMOOTH</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_PROPERTY_DEBUG_DESCRIPTION</description>
+<idName>Debug</idName>
+<label>SKEL_GENERIC_PROPERTY_DEBUG_LABEL</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+</jsmoothskeleton>
diff --git a/jsmooth-0.9.9-7-patch/skeletons/autodownload/mainres.rc b/jsmooth-0.9.9-7-patch/skeletons/autodownload/mainres.rc
new file mode 100644
index 0000000..f2634f1
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/skeletons/autodownload/mainres.rc
@@ -0,0 +1,9 @@
+
+#include "resource.h"
+
+JARID JAVA "../samplejar/sample.jar"
+PROPID JAVA "../samplejar/sample.props"
+JNISMOOTHID JAVA "../jnismooth/jnismooth.jar"
+
+A2 ICON MOVEABLE PURE LOADONCALL DISCARDABLE "JWrap.ico"
+1 MANIFEST "../samplejar/sample.manifest"
diff --git a/jsmooth-0.9.9-7-patch/skeletons/commonjava/JVMBase.cpp b/jsmooth-0.9.9-7-patch/skeletons/commonjava/JVMBase.cpp
new file mode 100644
index 0000000..14072b8
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/skeletons/commonjava/JVMBase.cpp
@@ -0,0 +1,74 @@
+/*
+  JSmooth: a VM wrapper toolkit for Windows
+  Copyright (C) 2003-2007 Rodrigo Reyes <reyes at charabia.net>
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Library General Public
+  License as published by the Free Software Foundation; either
+  version 2 of the License, or (at your option) any later version.
+  
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Library General Public License for more details.
+  
+  You should have received a copy of the GNU Library General Public
+  License along with this library; if not, write to the Free
+  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+  
+*/
+
+#include "JVMBase.h"
+
+JVMBase::JVMBase()
+{
+  m_maxHeap = -1;
+  m_initialHeap = -1;
+  m_vmParameter = "";
+}
+
+void JVMBase::addPathElement(const std::string& element)
+{
+  m_pathElements.push_back(element);
+}
+
+void JVMBase::addProperty(const JavaProperty& prop)
+{
+  m_properties.push_back(prop);
+}
+
+void JVMBase::setMaxHeap(long size)
+{
+  m_maxHeap = size;
+}
+
+void JVMBase::setInitialHeap(long size)
+{
+  m_initialHeap = size;
+}
+
+void JVMBase::addArgument(const std::string& arg)
+{
+  //  m_arguments.push_back(StringUtils::requoteForCommandLine(arg));
+  m_arguments.push_back(arg);
+}
+
+
+void JVMBase::setArguments(const std::string& args)
+{
+  m_arguments.clear();
+  DEBUG("arguments:<" + args + ">");
+  //   std::string ua = StringUtils::unescape(args);
+  //   DEBUG("arguments unescaped:<" + ua + ">");
+  vector<string> splitted = StringUtils::split(args, " \t\n\r", "\"\'", false);
+  for (int i=0; i<splitted.size(); i++)
+    {
+      DEBUG("SPLITTED-ARG[" + StringUtils::toString(i)+"]="+ splitted[i]);
+      this->addArgument(splitted[i]);
+    }
+}
+
+void JVMBase::setVmParameter(std::string parameter)
+{
+  m_vmParameter = parameter;
+}
diff --git a/jsmooth-0.9.9-7-patch/skeletons/commonjava/JVMBase.h b/jsmooth-0.9.9-7-patch/skeletons/commonjava/JVMBase.h
new file mode 100644
index 0000000..5ad2cd0
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/skeletons/commonjava/JVMBase.h
@@ -0,0 +1,61 @@
+/*
+  JSmooth: a VM wrapper toolkit for Windows
+  Copyright (C) 2003-2007 Rodrigo Reyes <reyes at charabia.net>
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Library General Public
+  License as published by the Free Software Foundation; either
+  version 2 of the License, or (at your option) any later version.
+  
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Library General Public License for more details.
+  
+  You should have received a copy of the GNU Library General Public
+  License along with this library; if not, write to the Free
+  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+  
+*/
+
+#ifndef __SUNJVMBASE_H_
+#define __SUNJVMBASE_H_
+
+#include <string>
+#include "jni.h"
+
+#include "Version.h"
+#include "StringUtils.h"
+#include "FileUtils.h"
+#include "ResourceManager.h"
+#include "JavaProperty.h"
+
+/**
+ * @author Rodrigo Reyes <reyes at charabia.net>
+ */ 
+
+class JVMBase
+{
+ protected:
+
+  std::vector<std::string>    m_pathElements;
+  std::vector<JavaProperty>   m_properties;
+  int                         m_maxHeap;
+  int                         m_initialHeap;
+  std::vector<std::string>    m_arguments;
+  std::string				  m_vmParameter;
+  
+ public:
+  JVMBase();
+
+  void addPathElement(const std::string& element);
+  void addProperty(const JavaProperty& prop);
+  void setMaxHeap(long size);
+  void setInitialHeap(long size);
+  void addArgument(const std::string& arg);
+  void setArguments(const std::string& args);
+  void setVmParameter(std::string parameter);
+};
+
+
+#endif
diff --git a/jsmooth-0.9.9-7-patch/skeletons/commonjava/JVMRegistryLookup.cpp b/jsmooth-0.9.9-7-patch/skeletons/commonjava/JVMRegistryLookup.cpp
new file mode 100644
index 0000000..c495ce8
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/skeletons/commonjava/JVMRegistryLookup.cpp
@@ -0,0 +1,115 @@
+/*
+  JSmooth: a VM wrapper toolkit for Windows
+  Copyright (C) 2003-2007 Rodrigo Reyes <reyes at charabia.net>
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Library General Public
+  License as published by the Free Software Foundation; either
+  version 2 of the License, or (at your option) any later version.
+  
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Library General Public License for more details.
+  
+  You should have received a copy of the GNU Library General Public
+  License along with this library; if not, write to the Free
+  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+  
+*/
+
+#include "JVMRegistryLookup.h"
+
+struct jvmsorter_dec : public binary_function<const SunJVMLauncher&, const SunJVMLauncher&, bool>
+{
+  bool operator()(const SunJVMLauncher& s1, const SunJVMLauncher& s2)
+  {
+    return s2 < s1;
+  }
+};
+
+vector<SunJVMLauncher> JVMRegistryLookup::lookupJVM()
+{
+  vector<SunJVMLauncher> res = JVMRegistryLookup::lookup(HKEY_LOCAL_MACHINE, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
+
+  vector<SunJVMLauncher> res2 = JVMRegistryLookup::lookup(HKEY_LOCAL_MACHINE, "SOFTWARE\\JavaSoft\\Java Development Kit");
+
+  for (vector<SunJVMLauncher>::iterator i = res2.begin(); i != res2.end(); i++)
+    {
+      res.insert(res.end(), *i);
+    }
+
+  sort(res.begin(), res.end(), jvmsorter_dec() );
+
+  return res;
+}
+
+vector<SunJVMLauncher> JVMRegistryLookup::lookup(HKEY key, const string& path)
+{
+  vector<SunJVMLauncher> result;
+
+  HKEY hKey;
+  LONG error = ERROR_SUCCESS;
+  LONG val = RegOpenKeyEx(key, path.c_str(), 0, KEY_READ, &hKey);
+
+  unsigned long buffersize = 1024;
+  char buffer[1024];
+
+  for (int i=0; RegEnumKey(hKey, i, buffer, buffersize) == ERROR_SUCCESS; i++)
+    {
+      int v = i;
+      HKEY version;
+      int foundver = RegOpenKeyEx(hKey, buffer, 0, KEY_READ, &version);
+      if (foundver == ERROR_SUCCESS)
+	{
+	  std::string versionname(buffer);
+	  HKEY runtimelib;
+	  unsigned long datatype;
+	  std::string runtimelibstr = "";
+	  std::string javahomestr = "";
+
+	  unsigned char *b = (unsigned char*)buffer;
+	  buffersize = 1024;
+	  int foundlib = RegQueryValueEx(version, TEXT("RuntimeLib"), 
+					 NULL, 
+					 &datatype, 
+					 b, 
+					 &buffersize);
+			
+	  if (foundlib == ERROR_SUCCESS)
+            {
+	      runtimelibstr = buffer;
+            }
+
+	  b = (unsigned char*)buffer;
+	  buffersize = 1024;
+	  int foundhome = RegQueryValueEx(version, TEXT("JavaHome"),
+					  NULL, 
+					  &datatype, 
+					  b, 
+					  &buffersize);
+	  if (foundhome == ERROR_SUCCESS)
+            {
+	      javahomestr = buffer;
+            }								
+
+	  if ((runtimelibstr.length()>0) || (javahomestr.length()>0))
+	    {
+	      SunJVMLauncher vm;
+	      vm.RuntimeLibPath = runtimelibstr;
+	      vm.JavaHome = javahomestr;
+	      vm.VmVersion = Version(versionname);
+	      result.push_back(vm);
+				    
+	      char buffer[244];
+	      sprintf(buffer, "V(%d)(%d)(%d)", vm.VmVersion.getMajor(), vm.VmVersion.getMinor(), vm.VmVersion.getSubMinor());
+	      DEBUG(std::string("JVM Lookup: found VM (") + buffer + ") in registry.");
+	    } 
+	}
+
+    }
+    
+  return result;
+}
+
+
diff --git a/jsmooth-0.9.9-7-patch/skeletons/commonjava/JVMRegistryLookup.h b/jsmooth-0.9.9-7-patch/skeletons/commonjava/JVMRegistryLookup.h
new file mode 100644
index 0000000..76ef6f9
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/skeletons/commonjava/JVMRegistryLookup.h
@@ -0,0 +1,52 @@
+/*
+  JSmooth: a VM wrapper toolkit for Windows
+  Copyright (C) 2003-2007 Rodrigo Reyes <reyes at charabia.net>
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Library General Public
+  License as published by the Free Software Foundation; either
+  version 2 of the License, or (at your option) any later version.
+  
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Library General Public License for more details.
+  
+  You should have received a copy of the GNU Library General Public
+  License along with this library; if not, write to the Free
+  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+  
+*/
+
+#ifndef _JVMREGISTRYLOOKUP_H_
+#define _JVMREGISTRYLOOKUP_H_
+
+#include <vector>
+#include <algorithm>
+
+#include "SunJVMLauncher.h"
+#include "JVMRegistryLookup.h"
+
+/** Utility class that scans the windows registry for installed JRE.
+ *
+ * @author Rodrigo Reyes <reyes at charabia.net>
+ */
+
+class JVMRegistryLookup
+{
+ public:
+  /**
+   * Scans the Windows Registry for the Java Runtime Environments. If
+   * no JRE is found, an empty vector is returned.
+   *
+   * @return a vector containing the JRE found.
+   */ 
+  static vector<SunJVMLauncher> lookupJVM();
+
+ private:
+  static vector<SunJVMLauncher> lookup(HKEY key, const string& path);
+
+};
+
+
+#endif
diff --git a/jsmooth-0.9.9-7-patch/skeletons/commonjava/JavaMachineManager.cpp b/jsmooth-0.9.9-7-patch/skeletons/commonjava/JavaMachineManager.cpp
new file mode 100644
index 0000000..5d5ec84
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/skeletons/commonjava/JavaMachineManager.cpp
@@ -0,0 +1,232 @@
+/*
+  JSmooth: a VM wrapper toolkit for Windows
+  Copyright (C) 2003-2007 Rodrigo Reyes <reyes at charabia.net>
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Library General Public
+  License as published by the Free Software Foundation; either
+  version 2 of the License, or (at your option) any later version.
+  
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Library General Public License for more details.
+  
+  You should have received a copy of the GNU Library General Public
+  License along with this library; if not, write to the Free
+  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+  
+*/
+
+#include "JavaMachineManager.h"
+
+
+JavaMachineManager::JavaMachineManager(ResourceManager& resman): m_resman(resman)
+{
+  DEBUG("Now searching the JVM installed on the system...");
+
+    m_registryVms = JVMRegistryLookup::lookupJVM();
+    m_javahomeVm = JVMEnvVarLookup::lookupJVM("JAVA_HOME");
+    m_jrepathVm = JVMEnvVarLookup::lookupJVM("JRE_HOME");
+    m_jdkpathVm = JVMEnvVarLookup::lookupJVM("JDK_HOME");
+    m_exitCode = 0;
+    m_useConsole = true;
+    m_acceptExe = true;
+    m_acceptDLL = true;
+    m_preferDLL = false;
+
+    if (resman.getProperty("bundledvm").length() > 0)
+    {
+        string bjvm = resman.getProperty("bundledvm");
+        DEBUG("Found a vm bundled with the application: (" + bjvm + ")");
+        m_localVMenabled = true;
+	std::string home = FileUtils::concFile(resman.getCurrentDirectory(), bjvm);
+        m_localVM.JavaHome = home;
+    } else
+    {
+        m_localVMenabled = false;
+    }
+    DEBUG("Current directory is " + resman.getCurrentDirectory());
+}
+
+bool JavaMachineManager::run()
+{
+  string vmorder = m_resman.getProperty(ResourceManager::KEY_JVMSEARCH);
+
+  if (m_localVMenabled)
+    {
+      if (internalRun(m_localVM, "bundled"))
+	{
+	  return true;
+	}
+
+//       DEBUG("Trying to use bundled VM " + m_localVM.JavaHome);        
+//       if (m_localVM.runProc(m_resman, m_useConsole, "bundled"))
+// 	{
+// 	  m_exitCode = m_localVM.getExitCode();
+// 	  return true;
+// 	}
+//       if (m_localVM.run(m_resman, "bundled"))
+// 	return true;
+    }
+
+  if (vmorder == "")
+    {
+      vmorder = "registry;jdkpath;jrepath;javahome;jview;exepath";
+    }
+    
+  DEBUG("JSmooth will now try to use the VM in the following order: " + vmorder);
+    
+  vector<string> jvmorder = StringUtils::split(vmorder, ";,", "");
+
+  Version max(m_resman.getProperty(ResourceManager:: KEY_MAXVERSION));
+  Version min(m_resman.getProperty(ResourceManager:: KEY_MINVERSION));
+
+  for (vector<string>::const_iterator i = jvmorder.begin(); i != jvmorder.end(); i++)
+    {
+      DEBUG("------------------------------");
+
+      if (*i == "registry")
+        {
+	  DEBUG("Trying to use a JVM defined in the registry (" + StringUtils::toString(m_registryVms.size()) + " available)");
+	  string vms = "VM will be tried in the following order: ";
+	  for (int i=0; i<m_registryVms.size(); i++)
+	    {
+	      vms += m_registryVms[i].VmVersion.toString();
+	      vms += ";";
+	    }
+	  DEBUG(vms);
+
+	  for (int i=0; i<m_registryVms.size(); i++)
+            {
+	      DEBUG("- Trying registry: " + m_registryVms[i].toString());
+
+	      if (internalRun(m_registryVms[i], "registry") == true)
+		return true;
+
+	      DEBUG("Couldn't use this VM, now trying something else");
+            }
+        } 
+      else if ((*i == "jview") && m_acceptExe)
+	{
+	  DEBUG("- Trying to launch the application with JVIEW");
+	  if (m_jviewVm.runProc(m_resman, ! m_useConsole))
+	    {
+	      return true;
+	    }
+
+	} 
+      else if ((*i == "javahome") && (m_javahomeVm.size()>0))
+	{
+	  DEBUG("- Trying to use JAVAHOME");
+	  if (internalRun(m_javahomeVm[0], "jrehome"))
+	    return true;
+	} 
+      else if ((*i == "jrepath") && (m_jrepathVm.size()>0))
+	{
+	  DEBUG("- Trying to use JRE_HOME");
+	  if (internalRun(m_jrepathVm[0], "jrehome"))
+	    return true;
+	} 
+      else if (*i == "exepath")
+	{
+	  DEBUG("- Trying to use PATH");
+
+	  SunJVMLauncher launcher;
+	  if (launcher.runProc(m_resman, m_useConsole, "path"))
+	    {
+	      m_exitCode = m_localVM.getExitCode();
+	      return true;
+	    }
+	}
+    }
+
+  DEBUG("Couldn't run any suitable JVM!");
+  return false;
+}
+
+
+bool JavaMachineManager::internalRun(SunJVMLauncher& launcher, const string& org)
+{
+  if (m_acceptDLL && m_preferDLL)
+    {
+      if (launcher.run(m_resman, org))
+	return true;
+    }
+
+  if (m_acceptExe)
+    {
+      if (launcher.runProc(m_resman, m_useConsole, org))
+	{
+	  m_exitCode = launcher.getExitCode();
+	  return true;
+	}
+    }
+
+  if (m_acceptDLL && !m_preferDLL)
+    {
+      if (launcher.run(m_resman, org))
+	return true;      
+    }
+
+  return false;
+}
+
+
+
+SunJVMLauncher* JavaMachineManager::runDLLFromRegistry(bool justInstanciate)
+{
+  string vms = "DLL VM will be tried in the following order: ";
+  for (int i=0; i<m_registryVms.size(); i++)
+    {
+      vms += m_registryVms[i].VmVersion.toString();
+      vms += ";";
+    }
+  DEBUG(vms);
+
+  for (int i=0; i<m_registryVms.size(); i++)
+    {
+      DEBUG("- Trying registry: " + m_registryVms[i].toString());
+
+      bool res = m_registryVms[i].run(m_resman, "registry", justInstanciate);
+      
+      if (res)
+	return &m_registryVms[i];
+    }
+    
+  if (m_localVMenabled) {
+    if (m_localVM.run(m_resman, "bundled", justInstanciate)) {
+      return &m_localVM;
+    } else {
+      DEBUG("Bundled VM launch failed");
+    }
+  }
+
+
+  return NULL;
+}
+
+void JavaMachineManager::setUseConsole(bool useConsole)
+{
+  m_useConsole = useConsole;
+}
+
+void JavaMachineManager::setAcceptExe(bool acceptExe)
+{
+  m_acceptExe = acceptExe;
+}
+
+void JavaMachineManager::setAcceptDLL(bool acceptDLL)
+{
+  m_acceptDLL = acceptDLL;;
+}
+
+void JavaMachineManager::setPreferDLL(bool prefDLL)
+{
+  m_preferDLL = prefDLL;
+}
+
+int JavaMachineManager::getExitCode()
+{
+  return m_exitCode;
+}
diff --git a/jsmooth-0.9.9-7-patch/skeletons/commonjava/Makefile.win b/jsmooth-0.9.9-7-patch/skeletons/commonjava/Makefile.win
new file mode 100644
index 0000000..f4d86e7
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/skeletons/commonjava/Makefile.win
@@ -0,0 +1,60 @@
+# Project: CommonJava
+# Makefile created by Dev-C++ 4.9.8.0
+
+RM   = rm -f
+CPP  = ${MINGW}/bin/${TARGET}-g++.exe
+CC   = ${MINGW}/bin/${TARGET}-gcc.exe
+WINDRES = windres.exe
+MINGW =
+RES  = 
+OBJ  = JniSmooth.o JVMBase.o SunJVMExe.o JArgs.o JClassProxy.o SunJVMDLL.o JavaMachineManager.o JVMEnvVarLookup.o JVMRegistryLookup.o MSJViewLauncher.o Properties.o ResourceManager.o SunJVMLauncher.o Version.o global.o JavaProperty.o JMethodCaller.o $(RES)
+LINKOBJ  = JniSmooth.o JVMBase.o SunJVMExe.o JArgs.o JClassProxy.o SunJVMDLL.o JavaMachineManager.o JVMEnvVarLookup.o JVMRegistryLookup.o MSJViewLauncher.o Properties.o ResourceManager.o SunJVMLauncher.o Version.o global.o JavaProperty.o JMethodCaller.o $(RES)
+LIBS =  -static-libgcc -L"$(MINGW)/lib"
+INCS =  -I"../util-core" -I"$(MINGW)/include"  -I"$(JDK)/include"  -I"$(JDK)/include/win32" 
+CXXINCS = -I"$(MINGW)/include/c++"  -I"$(MINGW)/include/c++/mingw32"  -I"$(MINGW)/include/c++/backward"  -I"$(MINGW)/include"  -I"$(JDK)/include"  -I"$(JDK)/include/win32" -I"../commonjava" -I"../util-core" -I"../util-net" $(FLTK-CXXFLAGS) 
+BIN  = CommonJava.a
+CXXFLAGS = $(CUSTOMFLAGS) $(CXXINCS) -DJDK="$(JDK)"
+CFLAGS = $(INCS) 
+
+.PHONY: all all-before all-after clean clean-custom
+
+all: all-before CommonJava.a all-after
+
+
+clean: clean-custom
+	$(RM) $(OBJ) $(BIN)
+
+$(BIN): $(LINKOBJ) test.cpp
+	ar r $(BIN) $(LINKOBJ)
+	ranlib $(BIN)
+	$(CPP) $(CXXFLAGS) test.cpp $(BIN) ../util-core/util-core.a -o test.exe
+
+JavaMachineManager.o: JavaMachineManager.cpp
+	$(CPP) -c JavaMachineManager.cpp -o JavaMachineManager.o $(CXXFLAGS)
+
+JVMEnvVarLookup.o: JVMEnvVarLookup.cpp
+	$(CPP) -c JVMEnvVarLookup.cpp -o JVMEnvVarLookup.o $(CXXFLAGS)
+
+JVMRegistryLookup.o: JVMRegistryLookup.cpp
+	$(CPP) -c JVMRegistryLookup.cpp -o JVMRegistryLookup.o $(CXXFLAGS)
+
+MSJViewLauncher.o: MSJViewLauncher.cpp
+	$(CPP) -c MSJViewLauncher.cpp -o MSJViewLauncher.o $(CXXFLAGS)
+
+Properties.o: Properties.cpp
+	$(CPP) -c Properties.cpp -o Properties.o $(CXXFLAGS)
+
+ResourceManager.o: ResourceManager.cpp
+	$(CPP) -c ResourceManager.cpp -o ResourceManager.o $(CXXFLAGS)
+
+SunJVMLauncher.o: SunJVMLauncher.cpp
+	$(CPP) -c SunJVMLauncher.cpp -o SunJVMLauncher.o $(CXXFLAGS)
+
+Version.o: Version.cpp
+	$(CPP) -c Version.cpp -o Version.o $(CXXFLAGS)
+
+global.o: global.cpp
+	$(CPP) -c global.cpp -o global.o $(CXXFLAGS)
+
+JavaProperty.o: JavaProperty.cpp
+	$(CPP) -c JavaProperty.cpp -o JavaProperty.o $(CXXFLAGS)
diff --git a/jsmooth-0.9.9-7-patch/skeletons/commonjava/ResourceManager.cpp b/jsmooth-0.9.9-7-patch/skeletons/commonjava/ResourceManager.cpp
new file mode 100644
index 0000000..2fb31d2
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/skeletons/commonjava/ResourceManager.cpp
@@ -0,0 +1,376 @@
+/*
+  JSmooth: a VM wrapper toolkit for Windows
+  Copyright (C) 2003-2007 Rodrigo Reyes <reyes at charabia.net>
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Library General Public
+  License as published by the Free Software Foundation; either
+  version 2 of the License, or (at your option) any later version.
+  
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Library General Public License for more details.
+  
+  You should have received a copy of the GNU Library General Public
+  License along with this library; if not, write to the Free
+  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+  
+*/
+
+#include "ResourceManager.h"
+
+char * const ResourceManager::KEY_MAINCLASSNAME = "mainclassname";
+char * const ResourceManager::KEY_ARGUMENTS     = "arguments";
+char * const ResourceManager::KEY_CLASSPATH     = "classpath";
+char * const ResourceManager::KEY_JVMSEARCH     = "jvmsearch";
+char * const ResourceManager::KEY_MINVERSION    = "minversion";
+char * const ResourceManager::KEY_MAXVERSION    = "maxversion";
+char * const ResourceManager::KEY_NOJVMMESSAGE  = "nojvmmessage";
+char * const ResourceManager::KEY_NOJVMURL      = "nojvmurl";
+char * const ResourceManager::KEY_BUNDLEDVM     = "bundledvm";
+char * const ResourceManager::KEY_CURRENTDIR    = "currentdir";
+char * const ResourceManager::KEY_EMBEDJAR      = "embedjar";
+
+ResourceManager::ResourceManager(std::string category, int propsId, int jarId, int jniId)
+{
+  m_resourceCategory = category;
+  m_resourcePropsId = propsId;
+  m_resourceJarId = jarId;
+
+  //
+  // Load the Properties
+  //
+  DEBUG("Initialize properties...");
+  std::string propsidstr = this->idToResourceName(propsId);
+  HRSRC resprop = FindResource(NULL, propsidstr.c_str(), category.c_str());
+  if (resprop != NULL)
+    {
+      int mainsize = 0;
+      mainsize = SizeofResource(NULL, resprop);
+      // char mainbuf[mainsize+1];
+      HGLOBAL main = LoadResource(NULL, resprop);
+      m_props.setData((const char*)main, mainsize);
+    }
+  else
+    {
+      m_lastError = "Can't find resource 'main name'";
+      return;
+    }
+
+  //
+  // Split the arguments
+  //
+  m_arguments = StringUtils::split(getProperty(KEY_ARGUMENTS, ""), " \t\n\r", "\"\'");
+
+  //
+  // loads the jar information
+  // 
+  std::string jaridstr = this->idToResourceName(jarId);
+  HRSRC resjar = FindResource(NULL, jaridstr.c_str(), category.c_str());
+  if (resjar != NULL)
+    {
+      m_jarSize = SizeofResource(NULL, resjar);
+      m_jarHandler =  LoadResource(NULL, resjar);
+    }
+  else
+    {
+      m_lastError = "Can't find JAR resource!";
+      return;
+    }
+
+
+  m_jnismoothSize = this->getResourceSize(jniId);
+  m_jnismoothHandler = this->getResource(jniId);
+
+  //
+  // Extract the java properties from the Property
+  //
+  std::string jpropcountstr = m_props.get("javapropertiescount");
+    
+  string exepath = FileUtils::getExecutablePath();
+  string exename = FileUtils::getExecutableFileName();
+  string computername = FileUtils::getComputerName();
+    
+  int jpropcount = StringUtils::parseInt(jpropcountstr);
+  DEBUG("Number of Java Parameters: "+jpropcountstr);
+  for (int i=0; i<jpropcount; i++)
+    {
+      string namekey = string("javaproperty_name_") + StringUtils::toString(i);
+      string valuekey = string("javaproperty_value_") + StringUtils::toString(i);
+      string name = m_props.get(namekey);
+      string value = m_props.get(valuekey);
+
+      DEBUG("Setting up java properties SOURCE: " + name + "=" + value + " : property if exist: " +getProperty(name,""));
+
+      value = StringUtils::replaceEnvironmentVariable(value);
+      value = StringUtils::replace(value, "${EXECUTABLEPATH}", exepath);
+      value = StringUtils::replace(value, "${EXECUTABLENAME}", exename);
+      value = StringUtils::replace(value, "${COMPUTERNAME}", computername);
+      	        
+      JavaProperty jprop(name, value);
+      m_javaProperties.push_back(jprop);
+
+      DEBUG("Setting up java properties DESTINATION: " + name + "=" + value);
+    }
+
+  std::string curdirmodifier = m_props.get(ResourceManager::KEY_CURRENTDIR);
+  if (curdirmodifier.length()>0)
+    {
+      int pos = string::npos;
+      if ( (pos=curdirmodifier.find("${EXECUTABLEPATH}")) != string::npos)
+	{
+	  m_currentDirectory = FileUtils::concFile(exepath, curdirmodifier.substr(pos + string("${EXECUTABLEPATH}").size()));
+	  //	    m_currentDirectory = StringUtils::replace(curdirmodifier, "${EXECUTABLEPATH}", exepath);
+	}
+      else
+	{
+	  DEBUG(string("Currentdirectory =") + curdirmodifier);
+	  m_currentDirectory = curdirmodifier;
+	  //	    m_currentDirectory = FileUtils::concFile(FileUtils::getExecutablePath(), curdirmodifier);
+	  m_currentDirectory = StringUtils::replaceEnvironmentVariable(m_currentDirectory);
+	}
+    }
+  else
+    {
+      m_currentDirectory = "";
+    }
+  //    printf("CURDIR SET TO: [%s]\n", m_currentDirectory.c_str());
+}
+
+ResourceManager::~ResourceManager()
+{
+    for (std::vector<std::string>::iterator i=m_deleteOnFinalize.begin(); i != m_deleteOnFinalize.end(); i++)
+    {
+        int res = DeleteFile(i->c_str());
+    }
+}
+
+void ResourceManager::setProperty(const std::string& key, const std::string& value)
+{
+     m_props.set(key, value);
+}
+
+void ResourceManager::saveTemp(std::string tempname, HGLOBAL data, int size)
+{
+  if ((data == 0) || (size == 0))
+    return;
+  
+  HANDLE temp = CreateFile(tempname.c_str(),
+			   GENERIC_WRITE,
+			   FILE_SHARE_WRITE,
+			   NULL, 
+			   CREATE_ALWAYS, 
+			   FILE_ATTRIBUTE_NORMAL,
+			   NULL);
+    
+  if (temp != NULL)
+    {    
+      DWORD reallyWritten;
+      WriteFile(temp, data, size, &reallyWritten, NULL);
+        
+      // TODO: check the reallyWritten value for errors
+        
+      CloseHandle(temp);
+      string s = tempname;
+      //      m_deleteOnFinalize.push_back(s);
+      FileUtils::deleteOnReboot(s);
+    }    
+}
+
+std::string ResourceManager::getMainName()  const
+{
+  return getProperty(string("mainclassname"));
+}
+
+std::string ResourceManager::getProperty(const std::string& key)  const
+{
+  return m_props.get(key);
+}
+
+std::string ResourceManager::getProperty(const std::string& key, const std::string& def) const
+{
+  if (m_props.contains(key))
+    return m_props.get(key);
+  else
+    return def;
+}
+
+bool ResourceManager::getBooleanProperty(const std::string& key) const
+{
+  std::string prop = getProperty(key, "0");
+
+  if (StringUtils::parseInt(prop)==1)
+    return true;
+
+  if (StringUtils::toLowerCase(prop) == "true")
+    return true;
+
+  return false;
+}
+
+std::string ResourceManager::saveJarInTempFile()
+{
+  if (useEmbeddedJar() == false)
+    return "";
+
+  std::string tempfilename = FileUtils::createTempFileName(".jar");
+  DEBUG("Created temporary filename to hold the jar (" + tempfilename + ")");
+  saveTemp(tempfilename, m_jarHandler, m_jarSize);
+  return tempfilename;
+}
+
+const vector<JavaProperty>& ResourceManager::getJavaProperties()
+{
+  return m_javaProperties;
+}
+
+std::vector<std::string> ResourceManager::getNormalizedClassPathVector() const
+{
+  std::string basepath = FileUtils::getExecutablePath();
+  std::string curdirmodifier = getCurrentDirectory(); //getProperty(string(ResourceManager::KEY_CURRENTDIR));
+  if (FileUtils::isAbsolute(curdirmodifier)){
+    basepath = curdirmodifier;
+  	DEBUG("DEBUG: (absolut) Basepath is : " + basepath);
+  } else {
+    basepath = FileUtils::concFile(basepath, curdirmodifier);
+  	DEBUG("DEBUG: (not absolut) Basepath is : " + basepath);
+  }
+
+  std::string cp = getProperty(string(ResourceManager::KEY_CLASSPATH));
+  vector<string>cps = StringUtils::split(cp, ";", "", false);
+  for (int i=0; i<cps.size(); i++)
+    {
+      string lib = cps[i];
+      DEBUG("ClassPath element is " + basepath + " + " +cps[i]);
+
+      cps[i] = FileUtils::concFile(basepath, cps[i]);
+      DEBUG("ClassPath element " + StringUtils::toString(i)+ "=" + cps[i]);
+    }
+
+  return cps;
+}
+
+std::string ResourceManager::getNormalizedClassPath() const
+{
+  vector<string> cps = getNormalizedClassPathVector();
+  return StringUtils::join(cps, ";");
+}
+
+std::string ResourceManager::getCurrentDirectory() const
+{
+    return m_currentDirectory;
+}
+
+bool ResourceManager::useEmbeddedJar() const
+{
+  std::string value = m_props.get(ResourceManager::KEY_EMBEDJAR);
+  if (value == "true")
+    return true;
+  return false;
+}
+
+void ResourceManager::printDebug() const
+{
+  DEBUG("ResourceManager resource configuration:");
+  DEBUG(" - Resource category: " + m_resourceCategory);
+  DEBUG(" - Current directory: " + m_currentDirectory);
+
+  map<string, string>  props = m_props.getDataCopy();
+  DEBUG(" - Property count: " + StringUtils::toString(props.size()));
+  for (map<string, string>::iterator i = props.begin(); i != props.end(); i++)
+    {
+      DEBUG(" - Property: "  + i->first + "=<" + i->second+">");
+    }
+}
+
+void ResourceManager::setUserArguments(std::vector<std::string> arguments)
+{
+  m_arguments.clear();
+
+  for (std::vector<std::string>::iterator i=arguments.begin(); i != arguments.end(); i++)
+    {
+      addUserArgument(*i);
+    }
+}
+
+
+void ResourceManager::addUserArgument(std::string argument)
+{
+	bool keyFound = false;
+  if (argument.size()>3)
+    {  
+      int pos = argument.find("=");
+      if (pos != std::string::npos)
+			{
+	  		string key = argument.substr(2, pos-2);
+	  		string value = argument.substr(pos+1);
+				string argumentType = argument.substr(0,2);
+	  		if (argumentType == "-J")
+	  		{
+	  			DEBUG("FOUND USER ARGUMENT for JSMOOTH: [" + key + "]=[" + value + "]");
+	  			keyFound = true;
+	  			setProperty(key, value);
+	  		}
+	  		if (argumentType == "-D")
+	  		{
+	  			DEBUG("FOUND USER ARGUMENT for JAVA: [" + key + "]=[" + value + "]");
+	  			JavaProperty jprop(key, value);
+	  			keyFound = true;
+      		m_javaProperties.push_back(jprop);
+	  		}
+			}
+    }
+  if (!keyFound)
+    {
+      m_arguments.push_back(argument);
+//       setProperty(KEY_ARGUMENTS, getProperty(KEY_ARGUMENTS) + " " + StringUtils::requoteForCommandLine(StringUtils::escape(argument)) );
+    }
+}
+
+
+std::vector<std::string> ResourceManager::getArguments()
+{
+  return m_arguments;
+}
+
+int ResourceManager::getResourceSize(int id)
+{
+  std::string propid = idToResourceName(id);
+  HRSRC resprop = FindResource(NULL, propid.c_str(), m_resourceCategory.c_str());
+  if (resprop != NULL)
+    {
+      return SizeofResource(NULL, resprop);
+    }
+  else
+    return 0;
+}
+
+HGLOBAL ResourceManager::getResource(int id)
+{
+  std::string propid = idToResourceName(id);
+  HRSRC resprop = FindResource(NULL, propid.c_str(), m_resourceCategory.c_str());
+  if (resprop != NULL)
+    {
+      return LoadResource(NULL, resprop);
+    }
+  else
+    return 0;
+}
+
+
+std::string ResourceManager::saveJnismoothInTempFile()
+{
+  if (m_jnismoothHandler == 0)
+    {
+      DEBUG("NO JNI SMOOTH ID !!");
+      return "";
+    }
+
+  std::string tempfilename = FileUtils::createTempFileName(".jar");
+  DEBUG("Saving jnismoothjar in " + tempfilename);
+
+  DEBUG("Created temporary filename to hold the jar (" + tempfilename + ")");
+  saveTemp(tempfilename, m_jnismoothHandler, m_jnismoothSize);
+  return tempfilename;  
+}
diff --git a/jsmooth-0.9.9-7-patch/skeletons/commonjava/ResourceManager.h b/jsmooth-0.9.9-7-patch/skeletons/commonjava/ResourceManager.h
new file mode 100644
index 0000000..1c22fab
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/skeletons/commonjava/ResourceManager.h
@@ -0,0 +1,198 @@
+/*
+  JSmooth: a VM wrapper toolkit for Windows
+  Copyright (C) 2003-2007 Rodrigo Reyes <reyes at charabia.net>
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Library General Public
+  License as published by the Free Software Foundation; either
+  version 2 of the License, or (at your option) any later version.
+  
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Library General Public License for more details.
+  
+  You should have received a copy of the GNU Library General Public
+  License along with this library; if not, write to the Free
+  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+  
+*/
+
+#ifndef RESOURCEMANAGER_H
+#define RESOURCEMANAGER_H
+
+#include <cstdio>
+#include <windows.h>
+#include <string>
+#include <vector>
+
+#include "common.h"
+#include "Properties.h"
+#include "FileUtils.h"
+#include "JavaProperty.h"
+
+/**
+ * Manages the executable resources associated to a Java
+ * application. This class manages the resources that are used to
+ * store the data associated to a java application. Those resources
+ * are: 
+ * - The JAR file, stored as a raw resource.  
+ * - The Property file, stored as a raw resource.
+ *  
+ * The Property file contains an 8-bit text, as parsed and used by the
+ * Properties class, which defines information relative to the java
+ * application (for instance the name of the main class, the java
+ * properties, and so on).
+ *
+ * @author Rodrigo Reyes <reyes at charabia.net>
+ */
+
+class ResourceManager
+{
+ private:
+  std::string m_mainName;
+  std::string m_resourceCategory;
+  Properties m_props;
+  std::string m_currentDirectory;
+
+  int m_resourcePropsId;
+  int m_resourceJarId;
+  std::string m_lastError;    
+  HGLOBAL m_jarHandler;
+  int m_jarSize;
+  HGLOBAL m_jnismoothHandler;
+  int m_jnismoothSize;
+
+  std::vector<std::string> m_arguments;
+    
+  std::vector<std::string> m_deleteOnFinalize;
+  std::vector<JavaProperty> m_javaProperties;
+    
+ public:
+
+  static char * const KEY_MAINCLASSNAME;
+  static char * const KEY_ARGUMENTS;
+  static char * const KEY_CLASSPATH;
+  static char * const KEY_JVMSEARCH;
+  static char * const KEY_MINVERSION;
+  static char * const KEY_MAXVERSION;
+  static char * const KEY_NOJVMMESSAGE;
+  static char * const KEY_NOJVMURL;
+  static char * const KEY_BUNDLEDVM;
+  static char * const KEY_CURRENTDIR;
+  static char * const KEY_EMBEDJAR;
+
+  /** 
+   * Constructs a ResourceManager which extract the jar and property
+   * files from the resources defined by the given parameters.  The
+   * resource are loaded from the resource type and resource names
+   * passed as parameters.
+   *
+   * @param category the resource type to look in
+   * @param propsId the resource id, stored under the category type, for the property file
+   * @param jarId the resource id, stored under the category type, for the jar file
+   */ 
+  ResourceManager(std::string category, int propsId, int jarId, int jniId = -1);
+
+  /**
+   * Default destructor.  The detructor tries to delete all the
+   * temporary files that have been created by the object. This is
+   * mainly the files created by the saveJarInTempFile() method.
+   *
+   * @sa ResourceManager::saveJarInTempFile
+   */
+  ~ResourceManager();
+    
+  /** Saves the jar in a temporary folder.  Extract the jar file
+   * from the resource defined in the consructor, and saves it in
+   * the temporary directory defined by the operating system.
+   *
+   * NOTE: if the KEY_EMBEDJAR key does not return "true", this method
+   * does not save the jar, and returns an empty string ("").
+   *
+   * @return the filename of the temp file where the Jar can be found.
+   */
+  std::string saveJarInTempFile();
+
+  std::string saveJnismoothInTempFile();
+
+  /** Returns the name of the main class.  The main class is the
+   *  class used to launch the java application. The static "public
+   *  void main(String[])" method of this class is called to start
+   *  the program.
+   *
+   * @return the name of the main class
+   */ 
+  std::string getMainName() const;
+
+  /**
+   * Returns the last error string. This is the string that describes
+   * the last error that was raised by an operation on the object.
+   * @return a string
+   */
+  std::string getLastErrorString()
+    {
+      return m_lastError;
+    }
+    
+  /**
+   * Retrieves a property value from the Properties resource defined
+   * in the constructor.
+   *
+   * @param key the name of the property
+   * @return a string that contains the value of the property, or an empty string if the property does not exist.
+   */
+  std::string getProperty(const std::string& key) const;
+  std::string getProperty(const std::string& key, const std::string& def) const;
+  bool getBooleanProperty(const std::string& key) const;
+
+  /**
+   * Adds a new property.
+   *
+   * @param key the name of the property
+   * @param value the value associated to the property
+   */
+  void setProperty(const std::string& key, const std::string& value);
+
+  std::vector<std::string> getNormalizedClassPathVector() const;
+  std::string getNormalizedClassPath() const;
+
+  /**
+   * Return the list of JavaProperty elements defined in the property
+   * resource. 
+   *
+   * @return a vector of JavaProperty elements, or an empty vector if none are defined.
+   */ 
+  const vector<JavaProperty>& getJavaProperties();
+    
+  std::string getCurrentDirectory() const;
+
+  bool useEmbeddedJar() const;
+
+  void printDebug() const;
+
+  void setUserArguments(std::vector<std::string> arguments);
+  void addUserArgument(std::string argument);
+
+  std::vector<std::string> getArguments();
+
+  int getResourceSize(int id);
+  HGLOBAL getResource(int id);
+
+ private:
+  void saveTemp(std::string tempname, HGLOBAL data, int size);
+
+  std::string idToResourceName(int id) const
+    {
+      char buffer[32];
+      sprintf(buffer, "%d", id);
+      std::string result("#");
+      result += buffer;
+      return result;
+    }
+    
+};
+
+
+#endif
+
diff --git a/jsmooth-0.9.9-7-patch/skeletons/commonjava/SunJVMDLL.cpp b/jsmooth-0.9.9-7-patch/skeletons/commonjava/SunJVMDLL.cpp
new file mode 100644
index 0000000..d7efa55
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/skeletons/commonjava/SunJVMDLL.cpp
@@ -0,0 +1,431 @@
+/*
+  JSmooth: a VM wrapper toolkit for Windows
+  Copyright (C) 2003-2007 Rodrigo Reyes <reyes at charabia.net>
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Library General Public
+  License as published by the Free Software Foundation; either
+  version 2 of the License, or (at your option) any later version.
+  
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Library General Public License for more details.
+  
+  You should have received a copy of the GNU Library General Public
+  License along with this library; if not, write to the Free
+  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+  
+*/
+
+
+#include "SunJVMDLL.h"
+
+#include "JClassProxy.h"
+#include "JniSmooth.h"
+
+SunJVMDLL::SunJVMDLL(const std::string& jvmdll, const Version& v)
+{
+  m_dllpath = jvmdll;
+  m_version = v;
+  m_statusCode = SunJVMDLL::JVM_NOT_STARTED;
+  m_vmlib = NULL;
+}
+
+SunJVMDLL::~SunJVMDLL()
+{
+  if (m_vmlib != NULL)
+    {
+      FreeLibrary(m_vmlib);
+    }
+}
+
+bool SunJVMDLL::run(const std::string& mainclass, bool waitDeath)
+{
+  if (m_statusCode == SunJVMDLL::JVM_NOT_STARTED)
+    instanciate();
+
+  if (m_statusCode == SunJVMDLL::JVM_LOADED)
+    {
+      JClassProxy disp(this, mainclass);
+      jstring emptystr = newUTFString(std::string(""));  
+      jobjectArray mainargs = newObjectArray(m_arguments.size(), "java.lang.String", emptystr);
+      for (int i =0; i<m_arguments.size(); i++)
+	  {
+	    env()->SetObjectArrayElement(mainargs, i, newUTFString(m_arguments[i]));
+	  }
+	printf("arguments array = %d\n", mainargs);
+	jvalue ma[1];
+	ma[0].l = mainargs;
+	disp.invokeStatic(std::string("void main(java.lang.String[] args)"), ma);
+	if (waitDeath == true)
+	  m_javavm->DestroyJavaVM();
+	return true;
+    }
+
+  return false;
+}
+
+void SunJVMDLL::join()
+{
+  if (m_statusCode == SunJVMDLL::JVM_LOADED)
+    {
+      m_javavm->DestroyJavaVM();
+    }
+}
+
+bool SunJVMDLL::instanciate()
+{
+  m_vmlib = LoadLibrary(m_dllpath.c_str());
+  if (m_vmlib == NULL)
+    {
+      m_statusCode = SunJVMDLL::JVM_DLL_CANT_LOAD;
+      return false;
+    }
+  CreateJavaVM_t CreateJavaVM = (CreateJavaVM_t)GetProcAddress(m_vmlib, "JNI_CreateJavaVM");
+  GetDefaultJavaVMInitArgs_t GetDefaultJavaVMInitArgs = (GetDefaultJavaVMInitArgs_t)GetProcAddress(m_vmlib, "JNI_GetDefaultJavaVMInitArgs");
+        
+  if ((CreateJavaVM == NULL) || (GetDefaultJavaVMInitArgs == NULL))
+    {
+      m_statusCode = SunJVMDLL::JVM_CANT_USE_VM;
+      return false;
+    }
+  
+  DEBUG("VM Created successfully");
+      
+  m_javavm = new JavaVM();
+  m_javaenv = new JNIEnv();
+
+  DEBUG("DLL Setup on " + m_version.toString());
+  bool res;
+  if ((m_version.getMajor() == 1)  && (m_version.getMinor() == 1))
+    res = setupVM11DLL(CreateJavaVM, GetDefaultJavaVMInitArgs);
+  else
+    res = setupVM12DLL(CreateJavaVM, GetDefaultJavaVMInitArgs);
+
+  registerJniSmooth();
+
+  DEBUG("Result code on DLL: " + StringUtils::toString(res));
+  if (res)
+    {
+      m_statusCode = SunJVMDLL::JVM_LOADED;
+      return true;
+    }
+
+  m_statusCode = SunJVMDLL::JVM_CANT_USE_VM;
+  return false;
+}
+
+bool SunJVMDLL::setupVM12DLL(CreateJavaVM_t CreateJavaVM, GetDefaultJavaVMInitArgs_t GetDefaultJavaVMInitArgs)
+{
+  vector<string> jpropstrv;
+   if (m_vmParameter != "")
+    {
+      std::vector<std::string> vmParameter = StringUtils::split(m_vmParameter, " ", " ", false);
+      for (std::vector<std::string>::iterator i=vmParameter.begin(); i != vmParameter.end(); i++)
+      {
+        jpropstrv.push_back(*i);
+      }
+    }
+
+  for (int i=0; i<m_properties.size(); i++)
+    if(m_properties[i].getName()[0]=='-') {
+        jpropstrv.push_back( StringUtils::requoteForCommandLine(m_properties[i].getName()));
+    } else {
+        jpropstrv.push_back( StringUtils::requoteForCommandLine("-D" + m_properties[i].getName()) + "=" + StringUtils::requoteForCommandLine(m_properties[i].getValue()));
+    }
+      
+//   DEBUG("MAXHEAP: " + StringUtils::toString(m_maxHeap));
+//   DEBUG("INITIALHEAP: " + StringUtils::toString(m_initialHeap));
+
+  if (m_maxHeap > 0)
+    {
+      jpropstrv.push_back("-Xmx" +StringUtils::toString(m_maxHeap));
+    }
+
+  if (m_initialHeap > 0)
+    {
+      jpropstrv.push_back("-Xms" + StringUtils::toString(m_initialHeap));
+    }
+
+  JavaVMInitArgs vm_args;
+  GetDefaultJavaVMInitArgs(&vm_args);
+
+  JavaVMOption options[1 + jpropstrv.size()];
+  std::string cpoption = "-Djava.class.path=" + StringUtils::join(m_pathElements, ";");
+
+  DEBUG("Classpath: " + cpoption);
+  options[0].optionString =  (char*)cpoption.c_str();
+  vm_args.version = 0x00010002;
+  vm_args.version = JNI_VERSION_1_2;
+  vm_args.options = options;
+  vm_args.nOptions = 1 + jpropstrv.size();
+                
+  for (int i=0; i<jpropstrv.size(); i++)
+    {
+      options[1 + i].optionString = (char*)jpropstrv[i].c_str();
+      DEBUG(string("Option added:") + options[1+i].optionString);
+    }
+
+  vm_args.ignoreUnrecognized = JNI_TRUE;
+
+  //
+  // Create the VM
+  if (CreateJavaVM( &m_javavm, &m_javaenv, &vm_args) != 0)
+    {
+      DEBUG("Can't create VM");
+      m_statusCode = SunJVMDLL::JVM_CANT_CREATE_VM;
+      return false;
+    }
+
+  DEBUG("VM 1.2+ Created successfully !!");
+  return true;
+}
+
+bool SunJVMDLL::setupVM11DLL(CreateJavaVM_t CreateJavaVM, GetDefaultJavaVMInitArgs_t GetDefaultJavaVMInitArgs)
+{
+  JDK1_1InitArgs vm_args;
+  vm_args.version = 0x00010001;
+  GetDefaultJavaVMInitArgs(&vm_args);
+
+  if (m_maxHeap > 0)
+    vm_args.maxHeapSize = m_maxHeap;
+  if (m_initialHeap > 0)
+    vm_args.minHeapSize = m_initialHeap;
+  
+  //
+  // create the properties array
+  //
+  char  const  * props[m_properties.size()+1];
+  vector<string> jpropstrv;
+
+  for (int i=0; i<m_properties.size(); i++)
+    jpropstrv[i] = m_properties[i].getName() + "=" + m_properties[i].getValue();
+
+  for (int i=0; i<m_properties.size(); i++)
+    props[i] = jpropstrv[i].c_str();
+  props[m_properties.size()] = NULL;
+  
+  vm_args.properties = (char**)props;
+
+  /* Append USER_CLASSPATH to the default system class path */
+
+  std::string classpath = vm_args.classpath;
+  classpath += StringUtils::join(m_pathElements, ";");
+
+  DEBUG("Classpath = " + classpath);
+  vm_args.classpath = (char*)classpath.c_str();
+
+  //
+  // Create the VM
+  if (CreateJavaVM( &m_javavm, &m_javaenv, &vm_args) != 0)
+    {
+      DEBUG("Can't create VM");
+      m_statusCode = SunJVMDLL::JVM_CANT_CREATE_VM;
+      return false;
+    }
+  DEBUG("VM 1.1 Created successfully !!");
+  return true;
+}
+
+
+jclass SunJVMDLL::findClass(const std::string& clazz)
+{
+  std::string classname = StringUtils::replace(clazz,".", "/");
+  DEBUG("Looking up for class <" + classname + ">");
+  jclass cls = env()->FindClass(classname.c_str());
+  if (cls == 0)
+    DEBUG("Can't find class " + classname + " !");
+  return cls;
+}
+
+jmethodID SunJVMDLL::findMethod(jclass& cls, const std::string& methodname, const std::string& signature, bool isStatic)
+{
+  std::string sig = StringUtils::replace(signature, ".", "/");
+  
+  jmethodID mid;
+  if (isStatic)
+    mid = env()->GetStaticMethodID(cls, methodname.c_str(), sig.c_str());
+  else
+    mid = env()->GetMethodID(cls, methodname.c_str(), sig.c_str());
+
+  return mid;
+}
+
+JavaVM* SunJVMDLL::getJavaVM()
+{
+  return m_javavm;
+}
+
+void SunJVMDLL::setIntField(jclass cls, jobject obj, const std::string& fieldName, int value)
+{
+  jfieldID binding = env()->GetFieldID(cls, fieldName.c_str(), "I");
+  env()->SetIntField(obj, binding, (jint)value);
+}
+
+void SunJVMDLL::setLongField(jclass cls, jobject obj, const std::string& fieldName, jlong value)
+{
+  jfieldID binding = env()->GetFieldID(cls, fieldName.c_str(), "J");
+  env()->SetLongField(obj, binding, (jlong)value);
+}
+
+void SunJVMDLL::setObjectField(jclass cls, jobject obj, const std::string& fieldName, const std::string& fieldclass, jobject value)
+{
+  std::string fc = "L" + StringUtils::replace(fieldclass, "." , "/") + ";";
+  jfieldID binding = env()->GetFieldID(cls, fieldName.c_str(), fc.c_str());
+  env()->SetObjectField(obj, binding, value);
+}
+
+jstring   SunJVMDLL::newUTFString(const std::string& str)
+{
+  return env()->NewStringUTF(str.c_str());
+}
+
+jobject   SunJVMDLL::newObject(jclass clazz, jmethodID& methodid, jvalue arguments[])
+{
+  return env()->NewObjectA(clazz, methodid, arguments);
+}
+
+jobjectArray  SunJVMDLL::newObjectArray(int size, jclass clazz, jobject initialValue)
+{
+  return env()->NewObjectArray((jsize)size, clazz, initialValue);
+}
+
+jobjectArray  SunJVMDLL::newObjectArray(int size, const std::string& classname, jobject initialValue)
+{
+  jclass cls = findClass(classname);
+  return newObjectArray(size, cls, initialValue);
+}
+
+//
+// Static method invocation
+//
+
+void SunJVMDLL::invokeVoidStatic(jclass clazz, jmethodID& methodid, jvalue arguments[])
+{
+  env()->CallStaticVoidMethodA(clazz, methodid, arguments);
+}
+
+jboolean SunJVMDLL::invokeBooleanStatic(jclass clazz, jmethodID& methodid, jvalue arguments[])
+{
+  return env()->CallStaticBooleanMethodA(clazz, methodid, arguments);
+}
+
+jbyte SunJVMDLL::invokeByteStatic(jclass clazz, jmethodID& methodid, jvalue arguments[])
+{
+  return env()->CallStaticByteMethodA(clazz, methodid, arguments);
+}
+
+jchar SunJVMDLL::invokeCharStatic(jclass clazz, jmethodID& methodid, jvalue arguments[])
+{
+  return env()->CallStaticCharMethodA(clazz, methodid, arguments);
+}
+
+jshort SunJVMDLL::invokeShortStatic(jclass clazz, jmethodID& methodid, jvalue arguments[])
+{
+  return env()->CallStaticShortMethodA(clazz, methodid, arguments);
+}
+
+jint SunJVMDLL::invokeIntStatic(jclass clazz, jmethodID& methodid, jvalue arguments[])
+{
+  return env()->CallStaticIntMethodA(clazz, methodid, arguments);
+}
+
+jlong SunJVMDLL::invokeLongStatic(jclass clazz, jmethodID& methodid, jvalue arguments[])
+{
+  return env()->CallStaticLongMethodA(clazz, methodid, arguments);
+}
+
+jfloat SunJVMDLL::invokeFloatStatic(jclass clazz, jmethodID& methodid, jvalue arguments[])
+{
+  return env()->CallStaticFloatMethodA(clazz, methodid, arguments);
+}
+
+jdouble SunJVMDLL::invokeDoubleStatic(jclass clazz, jmethodID& methodid, jvalue arguments[])
+{
+  return env()->CallStaticDoubleMethodA(clazz, methodid, arguments);
+}
+
+jobject SunJVMDLL::invokeObjectStatic(jclass clazz, jmethodID& methodid, jvalue arguments[])
+{
+  return env()->CallStaticObjectMethodA(clazz, methodid, arguments);
+}
+
+
+//
+//  method invocation
+//
+
+void SunJVMDLL::invokeVoid(jobject& obj, jmethodID& methodid, jvalue arguments[])
+{
+  env()->CallVoidMethodA(obj, methodid, arguments);
+}
+
+jboolean SunJVMDLL::invokeBoolean(jobject& obj, jmethodID& methodid, jvalue arguments[])
+{
+  return env()->CallBooleanMethodA(obj, methodid, arguments);
+}
+
+jbyte SunJVMDLL::invokeByte(jobject& obj, jmethodID& methodid, jvalue arguments[])
+{
+  return env()->CallByteMethodA(obj, methodid, arguments);
+}
+
+jchar SunJVMDLL::invokeChar(jobject& obj, jmethodID& methodid, jvalue arguments[])
+{
+  return env()->CallCharMethodA(obj, methodid, arguments);
+}
+
+jshort SunJVMDLL::invokeShort(jobject& obj, jmethodID& methodid, jvalue arguments[])
+{
+  return env()->CallShortMethodA(obj, methodid, arguments);
+}
+
+jint SunJVMDLL::invokeInt(jobject& obj, jmethodID& methodid, jvalue arguments[])
+{
+  return env()->CallIntMethodA(obj, methodid, arguments);
+}
+
+jlong SunJVMDLL::invokeLong(jobject& obj, jmethodID& methodid, jvalue arguments[])
+{
+  return env()->CallLongMethodA(obj, methodid, arguments);
+}
+
+jfloat SunJVMDLL::invokeFloat(jobject& obj, jmethodID& methodid, jvalue arguments[])
+{
+  return env()->CallFloatMethodA(obj, methodid, arguments);
+}
+
+jdouble SunJVMDLL::invokeDouble(jobject& obj, jmethodID& methodid, jvalue arguments[])
+{
+  return env()->CallDoubleMethodA(obj, methodid, arguments);
+}
+
+jobject SunJVMDLL::invokeObject(jobject& obj, jmethodID& methodid, jvalue arguments[])
+{
+  return env()->CallObjectMethodA(obj, methodid, arguments);
+}
+
+bool SunJVMDLL::registerMethod(const std::string& classname, const std::string& methodname, const std::string& signature,
+		    void* fn)
+{
+  jclass cc = this->findClass(classname);
+  if (cc == 0)
+    return false;
+  JNINativeMethod jnm;
+  jnm.name = (char*)methodname.c_str();
+  jnm.signature = (char*)signature.c_str();
+  jnm.fnPtr = fn;
+  
+  int res = env()->RegisterNatives(cc, &jnm, 1);
+  if (res != 0)
+    return false;
+
+  return true;
+}
+
+bool SunJVMDLL::registerJniSmooth()
+{
+  registerNativeMethods(this);
+  return true;
+}
diff --git a/jsmooth-0.9.9-7-patch/skeletons/commonjava/SunJVMExe.cpp b/jsmooth-0.9.9-7-patch/skeletons/commonjava/SunJVMExe.cpp
new file mode 100644
index 0000000..aa3b518
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/skeletons/commonjava/SunJVMExe.cpp
@@ -0,0 +1,230 @@
+/*
+  JSmooth: a VM wrapper toolkit for Windows
+  Copyright (C) 2003-2007 Rodrigo Reyes <reyes at charabia.net>
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Library General Public
+  License as published by the Free Software Foundation; either
+  version 2 of the License, or (at your option) any later version.
+  
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Library General Public License for more details.
+  
+  You should have received a copy of the GNU Library General Public
+  License along with this library; if not, write to the Free
+  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+  
+*/
+
+#include "SunJVMExe.h"
+
+#include <vector>
+#include <string>
+#include "Process.h"
+#include "FileUtils.h"
+
+SunJVMExe::SunJVMExe(const std::string& jrehome)
+{
+  m_jrehome = jrehome;
+}
+
+SunJVMExe::SunJVMExe(const std::string& jrehome, const Version& v)
+{
+  m_jrehome = jrehome;
+  m_version = v;
+}
+
+bool SunJVMExe::run(const std::string& mainclass, bool useconsole)
+{
+  if (!m_version.isValid())
+    {
+      m_version = guessVersion();
+    }
+  
+  if (!m_version.isValid())
+    return false;
+  
+  std::vector<std::string> execv;
+
+  execv.push_back(StringUtils::requoteForCommandLine(lookUpExecutable(useconsole)));
+
+   if (m_vmParameter != "")
+    {
+      std::vector<std::string> vmParameter = StringUtils::split(m_vmParameter, " ", " ", false);
+      for (std::vector<std::string>::iterator i=vmParameter.begin(); i != vmParameter.end(); i++)
+      {
+        execv.push_back(*i);
+      }
+    }
+    
+   if (m_maxHeap > 0)
+    {
+      if ((m_version.getMajor()==1)&&(m_version.getMinor()==1))
+	execv.push_back("-mx" + StringUtils::toString(m_maxHeap));
+      else
+	execv.push_back("-Xmx" + StringUtils::toString(m_maxHeap));
+    }
+
+  if (m_initialHeap > 0)
+    {
+      if ((m_version.getMajor()==1)&&(m_version.getMinor()==1))
+	execv.push_back("-ms" + StringUtils::toString(m_initialHeap));
+      else
+	execv.push_back("-Xms" + StringUtils::toString(m_initialHeap));
+    }
+
+  for (int i=0; i<m_properties.size(); i++)
+    if(m_properties[i].getName()[0]=='-') {
+        execv.push_back( StringUtils::requoteForCommandLine(m_properties[i].getName()));
+    } else {
+    execv.push_back( StringUtils::requoteForCommandLine("-D" + m_properties[i].getName()) + "=" + StringUtils::requoteForCommandLine(m_properties[i].getValue()));
+    }
+
+  std::string classpath;
+  if ((m_version.getMajor()==1)&&(m_version.getMinor()==1))
+    classpath = getClassPath(true);
+  else
+    classpath = getClassPath(false);
+  
+  if (classpath.size() > 0)
+    execv.push_back("-classpath " + StringUtils::requoteForCommandLine(classpath));
+
+  execv.push_back(mainclass);
+
+  for (int i=0; i<m_arguments.size(); i++)
+    {
+      execv.push_back( StringUtils::requoteForCommandLine(m_arguments[i]) );
+    }
+
+  std::string execmd = StringUtils::join(execv, " ");
+  DEBUG("COMMAND: <" + execmd + ">");
+
+  Process proc(execmd, useconsole);
+  if (proc.run())
+    {
+      DEBUG("Started successfully");
+      proc.join();
+      m_exitCode = proc.getExitCode();
+      return true;
+    }
+  else
+    {
+      DEBUG("Failed running " + execmd);
+    }
+  return false;
+}
+
+
+std::string SunJVMExe::lookUpExecutable(bool useconsole)
+{
+  std::string java;
+
+  if (m_jrehome.size() == 0)
+    {
+      return useconsole?"java.exe":"javaw.exe";
+    }
+
+  if (useconsole)
+    {
+      if (FileUtils::fileExists(m_jrehome, "bin\\java.exe"))
+	java = FileUtils::concFile(m_jrehome, "bin\\java.exe");
+      else if (FileUtils::fileExists(m_jrehome, "bin\\jre.exe"))
+	java = FileUtils::concFile(m_jrehome, "bin\\jre.exe");
+      else
+	{
+	  std::vector<std::string> javas = FileUtils::recursiveSearch(m_jrehome, "java.exe");
+	  DEBUG("REC: " + StringUtils::toString(javas));
+
+	  if (javas.size() == 0)
+	    javas = FileUtils::recursiveSearch(m_jrehome, "jre.exe");
+
+	  if (javas.size() > 0)
+	    java = javas[0];
+	}
+    }
+  else
+    {
+      if (FileUtils::fileExists(m_jrehome, "bin\\javaw.exe"))
+	java = FileUtils::concFile(m_jrehome, "bin\\javaw.exe");
+      else if (FileUtils::fileExists(m_jrehome, "bin\\jrew.exe"))
+	java = FileUtils::concFile(m_jrehome, "bin\\jrew.exe");
+      else
+	{
+	  std::vector<std::string> javas = FileUtils::recursiveSearch(m_jrehome, "javaw.exe");
+
+	  DEBUG("REC: " + StringUtils::toString(javas));
+
+	  if (javas.size() == 0)
+	    javas = FileUtils::recursiveSearch(m_jrehome, "jrew.exe");
+
+	  if (javas.size() > 0)
+	    java = javas[0];
+	}      
+    }
+
+  return java;
+}
+
+Version SunJVMExe::guessVersion()
+{
+  std::string exepath = lookUpExecutable(true);
+  string exeline = exepath + " -version";
+
+  Version result;
+
+  // Return immediatly if the exe does not exist
+  if (!FileUtils::fileExists(exepath))
+    return result;
+
+  string tmpfilename = FileUtils::createTempFileName(".tmp");
+
+  Process proc(exeline, true);
+  proc.setRedirect(tmpfilename);
+  proc.run();
+  proc.join();
+
+  std::string voutput = FileUtils::readFile(tmpfilename);
+  vector<string> split = StringUtils::split(voutput, " \t\n\r", "\"");
+  for (vector<string>::iterator i=split.begin(); i != split.end(); i++)
+    {
+      Version v(*i);
+      if (v.isValid())
+	{
+	  result = v;
+	  break;
+	}
+    }
+  
+  FileUtils::deleteOnReboot(tmpfilename);
+  return result;
+}
+
+
+std::string SunJVMExe::getClassPath(bool full)
+{
+  std::vector<std::string> cp;
+  for (std::vector<std::string>::const_iterator i=m_pathElements.begin(); i!=m_pathElements.end(); i++)
+    cp.push_back(*i);
+
+  if (full)
+    {
+      std::string javaexe = lookUpExecutable(true);
+      std::string home = FileUtils::getParent( FileUtils::getParent( javaexe ));
+      if (FileUtils::fileExists(home))
+	{
+	  vector<string> cpzips = FileUtils::recursiveSearch(home, "*.zip");
+	  cp.insert(cp.end(), cpzips.begin(), cpzips.end());
+	  vector<string> cpjars = FileUtils::recursiveSearch(home, "*.jar");
+	  cp.insert(cp.end(), cpjars.begin(), cpjars.end());
+	}
+    }
+
+  return StringUtils::join(cp, ";");
+}
+
+int SunJVMExe::getExitCode()
+{
+  return m_exitCode;
+}
diff --git a/jsmooth-0.9.9-7-patch/skeletons/commonjava/SunJVMLauncher.cpp b/jsmooth-0.9.9-7-patch/skeletons/commonjava/SunJVMLauncher.cpp
new file mode 100644
index 0000000..6441f11
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/skeletons/commonjava/SunJVMLauncher.cpp
@@ -0,0 +1,233 @@
+/*
+  JSmooth: a VM wrapper toolkit for Windows
+  Copyright (C) 2003-2007 Rodrigo Reyes <reyes at charabia.net>
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Library General Public
+  License as published by the Free Software Foundation; either
+  version 2 of the License, or (at your option) any later version.
+  
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Library General Public License for more details.
+  
+  You should have received a copy of the GNU Library General Public
+  License along with this library; if not, write to the Free
+  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+  
+*/
+
+#include "SunJVMLauncher.h"
+#include "Process.h"
+
+#include "SunJVMDLL.h"
+#include "JArgs.h"
+#include "JClassProxy.h"
+
+extern "C" {
+  static jint JNICALL  myvprintf(FILE *fp, const char *format, va_list args)
+  {
+        DEBUG("MYPRINTF");
+  }
+  void JNICALL myexit(jint code)
+  {
+       DEBUG("EXIT CALLED FROM JVM DLL");        
+       exit(code); 
+  }
+}
+
+std::string SunJVMLauncher::toString() const
+{
+    return "<" + JavaHome + "><" + RuntimeLibPath + "><" + VmVersion.toString() + ">";
+}
+
+bool SunJVMLauncher::run(ResourceManager& resource, const string& origin, bool justInstanciate)
+{
+  DEBUG("Running now " + this->toString() + ", instanciate=" + (justInstanciate?"yes":"no"));
+
+  Version max(resource.getProperty(ResourceManager:: KEY_MAXVERSION));
+  Version min(resource.getProperty(ResourceManager:: KEY_MINVERSION));
+
+  // patch proposed by zregvart: if you're using bundeled JVM, you
+  // apriori know the version bundled and we can trust. The version
+  // check is therefore unrequired.
+  if (origin != "bundled") {
+      
+    if (VmVersion.isValid() == false)
+      {
+	DEBUG("No version identified for " + toString());
+	SunJVMExe exe(this->JavaHome);
+	VmVersion = exe.guessVersion();
+	DEBUG("Version found: " + VmVersion.toString());
+      }
+
+    if (VmVersion.isValid() == false)
+      {
+	DEBUG("No version found, can't instanciate DLL without it");
+	return false;
+      }
+  
+    if (min.isValid() && (VmVersion < min))
+      return false;
+      
+    if (max.isValid() && (max < VmVersion))
+      return false;
+  }
+
+  DEBUG("Launching " + toString());
+    
+  //
+  // search for the dll if it's not set in the registry, or if the
+  // file doesn't exist
+  //
+  if ( (this->JavaHome.size()>0)
+       && ((this->RuntimeLibPath.size() == 0) || (!FileUtils::fileExists(this->RuntimeLibPath))) )
+    {
+      std::string assump = FileUtils::concFile(this->JavaHome, "jre\\bin\\jvm.dll");
+      std::string assump2 = FileUtils::concFile(this->JavaHome, "jre\\bin\\server\\jvm.dll"); // for JRE 1.5+
+      std::string assump3 = FileUtils::concFile(this->JavaHome, "jre\\bin\\client\\jvm.dll"); // for JRE 1.5+
+      std::string assump4 = FileUtils::concFile(this->JavaHome, "bin\\javai.dll"); // For JRE 1.1
+
+      if (FileUtils::fileExists(assump))
+	this->RuntimeLibPath = assump;
+      else if (FileUtils::fileExists(assump2))
+	this->RuntimeLibPath = assump2;
+      else if (FileUtils::fileExists(assump3))
+	this->RuntimeLibPath = assump3;
+      else if (FileUtils::fileExists(assump4))
+	this->RuntimeLibPath = assump4;
+      else
+	{
+	  vector<string> dlls = FileUtils::recursiveSearch(this->JavaHome, string("jvm.dll"));
+	  if (dlls.size() > 0)
+	    this->RuntimeLibPath = dlls[0];
+	}
+    }
+
+  if (FileUtils::fileExists(this->RuntimeLibPath))
+    {
+      DEBUG("RuntimeLibPath used: " + this->RuntimeLibPath);      
+      Version v = this->VmVersion;
+      if (!v.isValid())
+	{
+	  v = min;
+	  if (!v.isValid())
+	    v = Version("1.2.0");
+	  DEBUG("No version, trying with " + v.toString());
+	}
+
+      m_dllrunner = new SunJVMDLL(this->RuntimeLibPath, v);
+      // set up the vm parameters...
+      setupVM(resource, m_dllrunner);
+
+      if (justInstanciate)
+	return m_dllrunner->instanciate();
+      else
+	return m_dllrunner->run(resource.getProperty(ResourceManager::KEY_MAINCLASSNAME),
+				true);
+    }
+
+  return false;
+}
+
+bool SunJVMLauncher::runProc(ResourceManager& resource, bool useConsole, const string& origin)
+{
+  std::string classname = resource.getProperty(string(ResourceManager::KEY_MAINCLASSNAME));  
+
+  if (VmVersion.isValid() == false)
+    {
+      DEBUG("No version identified for " + toString());
+      SunJVMExe exe(this->JavaHome);
+      VmVersion = exe.guessVersion();
+      DEBUG("Version found: " + VmVersion.toString());
+    }
+
+  if (VmVersion.isValid() == false)
+    return false;
+
+  if (origin != "bundled") {
+      
+      Version max(resource.getProperty(ResourceManager:: KEY_MAXVERSION));
+      Version min(resource.getProperty(ResourceManager:: KEY_MINVERSION));
+  
+      if (min.isValid() && (VmVersion < min))
+        return false;
+      
+      if (max.isValid() && (max < VmVersion))
+	return false;
+  }  
+
+  SunJVMExe exe(this->JavaHome, VmVersion);   
+  setupVM(resource, &exe);
+  if (exe.run(classname, useConsole))
+    {
+      m_exitCode = exe.getExitCode();
+      return true;
+    }
+  return false;
+}
+
+bool SunJVMLauncher::setupVM(ResourceManager& resource, JVMBase* vm)
+{
+  //
+  // create the properties array
+  const vector<JavaProperty>& jprops = resource.getJavaProperties();
+  for (int i=0; i<jprops.size(); i++)
+    {
+      vm->addProperty(jprops[i]);
+    }
+
+  if (resource.getProperty("maxheap") != "")
+    vm->setMaxHeap( StringUtils::parseInt(resource.getProperty("maxheap") ));
+  
+  if (resource.getProperty("initialheap") != "")
+    vm->setInitialHeap( StringUtils::parseInt(resource.getProperty("initialheap") ));
+
+  if (resource.getProperty("vmparameter") != "")
+	vm->setVmParameter( resource.getProperty("vmparameter") );
+  
+  if (resource.useEmbeddedJar())
+    {
+      std::string embj = resource.saveJarInTempFile();
+      vm->addPathElement(embj);
+    }
+  
+  std::string jnijar = resource.saveJnismoothInTempFile();
+  if (jnijar != "")
+    vm->addPathElement(jnijar);
+
+  //
+  // Define the classpath
+  std::vector<std::string> classpath = resource.getNormalizedClassPathVector();
+  for (int i=0; i<classpath.size(); i++)
+    {
+      vm->addPathElement(classpath[i]);
+    }
+
+  //
+  // Defines the arguments passed to the java application
+  //  vm->setArguments(resource.getProperty(ResourceManager::KEY_ARGUMENTS));
+  std::vector<std::string> args = resource.getArguments();
+  for (int i=0; i<args.size(); i++)
+    {
+      vm->addArgument(args[i]);
+    }
+
+}
+
+SunJVMDLL* SunJVMLauncher::getDLL()
+{
+  return m_dllrunner;
+}
+
+bool operator < (const SunJVMLauncher& v1, const SunJVMLauncher& v2)
+{
+  return v1.VmVersion < v2.VmVersion;
+}
+
+int SunJVMLauncher::getExitCode()
+{
+  return m_exitCode;
+}
+
diff --git a/jsmooth-0.9.9-7-patch/skeletons/commonjava/Version.h b/jsmooth-0.9.9-7-patch/skeletons/commonjava/Version.h
new file mode 100644
index 0000000..bd94101
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/skeletons/commonjava/Version.h
@@ -0,0 +1,131 @@
+/*
+  JSmooth: a VM wrapper toolkit for Windows
+  Copyright (C) 2003-2007 Rodrigo Reyes <reyes at charabia.net>
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Library General Public
+  License as published by the Free Software Foundation; either
+  version 2 of the License, or (at your option) any later version.
+  
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Library General Public License for more details.
+  
+  You should have received a copy of the GNU Library General Public
+  License along with this library; if not, write to the Free
+  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+  
+*/
+
+#ifndef __VERSION_H_
+#define __VERSION_H_
+
+#include <cstdio>
+#include <string>
+
+#include "common.h"
+#include "StringUtils.h"
+
+/**
+ * Manages versions as used by Sun's JVM. The version scheme used is
+ * major.minor.sub, for instance 1.1.8 or 1.4.2.
+ *
+ * @author Rodrigo Reyes <reyes at charabia.net>
+ */
+
+class Version
+{
+ private:
+  int m_major;
+  int m_minor;
+  int m_sub;
+
+ public:
+  std::string Value;
+
+  /**
+   * Creates a Version object from a string representation. This
+   * string needs to start as a normal version, but don't need to
+   * follow exactly the schema X.Y.Z. The Major number is mandatory,
+   * but the Minor and Sub numbers are optional.
+   *
+   * If the string representation does not represent a valid
+   * version, the object is said 'invalid', and returns false to the
+   * isInvalid() method.
+   *
+   * @param val a string representation of the version.
+   */
+  Version(std::string val);
+
+  /**
+   * Creates an invalid Version object. The object created returns
+   * false to the isValid() method.
+   */
+  Version();
+
+  /**
+   * Returns the major number of this Version.
+   */
+  int getMajor() const;
+  /**
+   * Returns the minor number of this Version.
+   */
+  int getMinor() const;
+  /**
+   * Returns the subminor number of this Version.
+   */
+  int getSubMinor() const;
+
+  std::string toString() const;
+
+  //    bool operator > (const Version& v) const;
+
+  friend bool operator < (const Version& v1, const Version& v2);
+  friend bool operator <= (const Version& v1, const Version& v2);
+    
+  /**
+   * A version object may be invalid if it does not refer to a valid
+   * version. In such a case, the behaviour of object the is altered
+   * as follows:
+   *
+   * - The getMajor(), getMinor(), and getSubMinor() methods return 0
+   * - Any comparison (< or <=) between an invalid Version object
+   *   and another object (invalid or not) return true.
+   */
+  bool isValid() const;
+    
+ private:
+  void parseValue(const std::string& val);
+
+  int extractIntAt(const std::string& val, int pos) const;
+    
+ public:
+  struct AscendingSort
+  {
+    bool operator()(const Version& v1, const Version& v2)
+    {
+      if (v1.getMajor() > v2.getMajor())
+	return true;
+      if (v1.getMajor() < v2.getMajor())
+	return false;
+                
+      if (v1.getMinor() > v2.getMinor())
+	return true;
+      if (v1.getMinor() < v2.getMinor())
+	return false;
+        
+      if (v1.getSubMinor() > v2.getSubMinor())
+	return true;
+      if (v1.getSubMinor() < v2.getSubMinor())
+	return false;
+                  
+      return false;
+    }
+  };
+
+};
+
+
+
+#endif
diff --git a/jsmooth-0.9.9-7-patch/skeletons/consolewrapper/Makefile.win b/jsmooth-0.9.9-7-patch/skeletons/consolewrapper/Makefile.win
new file mode 100644
index 0000000..8913456
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/skeletons/consolewrapper/Makefile.win
@@ -0,0 +1,32 @@
+# Project: ConsoleWrapper
+# Makefile created by Dev-C++ 4.9.8.0
+
+RM   = rm -f
+CPP  = ${MINGW}/bin/${TARGET}-g++.exe
+CC   = ${MINGW}/bin/${TARGET}-gcc.exe
+WINDRES = windres.exe
+RES  = ConsoleWrapper_private.res
+OBJ  = main.o $(RES)
+LINKOBJ  = main.o $(RES)
+LIBS =  -static-libgcc -L"${MINGW}/lib" -L"../commonjava" -L"../util-core" ../commonjava/CommonJava.a ../util-core/util-core.a 
+INCS =  -I"/include"  -I"../commonjava"  -I"$(JDK)/include"  -I"$(JDK)/include/win32" 
+CXXINCS = -I"$(MINGW)/include/c++"  -I"$(MINGW)/include/c++/mingw32"  -I"$(MINGW)/include/c++/backward"  -I"$(MINGW)/include"  -I"$(JDK)/include"  -I"$(JDK)/include/win32" -I"../commonjava" -I"../util-core" -I"../util-net" $(FLTK-CXXFLAGS)
+BIN  = ConsoleWrapper.exe
+CXXFLAGS = $(CUSTOMFLAGS) $(CXXINCS) -DJDK="$(JDK)"
+CFLAGS = $(INCS) 
+
+.PHONY: all all-before all-after clean clean-custom
+
+all: all-before ConsoleWrapper.exe all-after
+
+clean: clean-custom
+	$(RM) $(OBJ) $(BIN) ConsoleWrapper_private.res
+
+$(BIN): $(LINKOBJ)  ../commonjava/CommonJava.a
+	$(CPP) $(LINKOBJ) -o "ConsoleWrapper.exe" $(LIBS)
+
+main.o: main.cpp
+	$(CPP) -c main.cpp -o main.o $(CXXFLAGS)
+
+ConsoleWrapper_private.res: ConsoleWrapper_private.rc mainres.rc 
+	$(WINDRES) -i ConsoleWrapper_private.rc -J rc -o ConsoleWrapper_private.res -O coff 
diff --git a/jsmooth-0.9.9-7-patch/skeletons/consolewrapper/description64.skel b/jsmooth-0.9.9-7-patch/skeletons/consolewrapper/description64.skel
new file mode 100644
index 0000000..2d2fced
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/skeletons/consolewrapper/description64.skel
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<jsmoothskeleton>
+<debug>false</debug>
+<description>SKEL_CONSOLEWRAPPER_DESCRIPTION</description>
+<executableName>consolewrapper.exe</executableName>
+<resourceCategory>JAVA</resourceCategory>
+<resourceJarId>102</resourceJarId>
+<resourcePropsId>103</resourcePropsId>
+<shortName>Console Wrapper x64</shortName>
+<skeletonProperties>
+<description>SKEL_CUSTOMWRAPPER_PROPERTY_MESSAGE_DESCRIPTION</description>
+<idName>Message</idName>
+<label>SKEL_CUSTOMWRAPPER_PROPERTY_MESSAGE_LABEL</label>
+<type>textarea</type>
+<value>This program needs Java to run.
+Please download it at http://www.java.com</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_CUSTOMWRAPPER_PROPERTY_KEYPRESS_DESCRIPTION</description>
+<idName>PressKey</idName>
+<label>SKEL_CUSTOMWRAPPER_PROPERTY_KEYPRESS_LABEL</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_PROPERTY_DEBUG_DESCRIPTION</description>
+<idName>Debug</idName>
+<label>SKEL_GENERIC_PROPERTY_DEBUG_LABEL</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+</jsmoothskeleton>
diff --git a/jsmooth-0.9.9-7-patch/skeletons/consolewrapper/mainres.rc b/jsmooth-0.9.9-7-patch/skeletons/consolewrapper/mainres.rc
new file mode 100644
index 0000000..8bd397f
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/skeletons/consolewrapper/mainres.rc
@@ -0,0 +1,8 @@
+
+#include "resource.h"
+
+JARID JAVA "samplejar/sample.jar"
+PROPID JAVA "samplejar/sample.props"
+
+A2 ICON MOVEABLE PURE LOADONCALL DISCARDABLE "JWrap.ico"
+1 MANIFEST "../samplejar/sample.manifest"
diff --git a/jsmooth-0.9.9-7-patch/skeletons/samplejar/sample.manifest b/jsmooth-0.9.9-7-patch/skeletons/samplejar/sample.manifest
new file mode 100644
index 0000000..3a9962e
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/skeletons/samplejar/sample.manifest
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+  <assemblyIdentity type="win32" 
+                    name="myOrganization.myDivision.mySampleApp" 
+                    version="0.0.0.0" 
+                    processorArchitecture="ia64/x86" 
+  />
+ <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+  <security>
+   <requestedPrivileges>
+    <requestedExecutionLevel level="requireAdministrator"/>
+   </requestedPrivileges>
+  </security>
+ </trustInfo>
+</assembly>
\ No newline at end of file
diff --git a/jsmooth-0.9.9-7-patch/skeletons/simplewrap/Makefile.win b/jsmooth-0.9.9-7-patch/skeletons/simplewrap/Makefile.win
new file mode 100644
index 0000000..5841f20
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/skeletons/simplewrap/Makefile.win
@@ -0,0 +1,32 @@
+# Project: JWrap
+# Makefile created by Dev-C++ 4.9.8.0
+
+CPP  = ${MINGW}/bin/${TARGET}-g++.exe
+CC   = ${MINGW}/bin/${TARGET}-gcc.exe
+WINDRES = windres.exe
+RES  = JWrap_private.res
+OBJ  = main.o $(RES)
+LINKOBJ  = main.o $(RES)
+LIBS =  -static-libgcc -L"${MINGW}/lib" -L"../commonjava"  -L"../util-core" -mwindows ../commonjava/CommonJava.a ../util-core/util-core.a
+INCS =  -I"/include"  -I"$(JDK)/include/win32"  -I"$(JDK)/include"  -I"../commonjava" -I"../util-core"
+CXXINCS = -I"$(MINGW)/include/c++"  -I"$(MINGW)/include/c++/mingw32"  -I"$(MINGW)/include/c++/backward"  -I"$(MINGW)/include"  -I"$(JDK)/include"  -I"$(JDK)/include/win32" -I"../commonjava" -I"../util-core" -I"../util-net" $(FLTK-CXXFLAGS)
+BIN  = JWrap.exe
+CXXFLAGS = $(CUSTOMFLAGS) $(CXXINCS) -DJDK="$(JDK)"
+CFLAGS = $(INCS) 
+
+.PHONY: all all-before all-after clean clean-custom
+
+all: all-before JWrap.exe all-after 
+
+
+clean: clean-custom
+	$(RM) $(OBJ) $(BIN) JWrap_private.res
+
+$(BIN): $(LINKOBJ) ../commonjava/CommonJava.a
+	$(CPP) $(LINKOBJ) -o "JWrap.exe" $(LIBS)
+
+main.o: main.cpp
+	$(CPP) -c main.cpp -o main.o $(CXXFLAGS)
+
+JWrap_private.res: JWrap_private.rc mainres.rc 
+	$(WINDRES) -i JWrap_private.rc -J rc -o JWrap_private.res -O coff 
diff --git a/jsmooth-0.9.9-7-patch/skeletons/simplewrap/description64.skel b/jsmooth-0.9.9-7-patch/skeletons/simplewrap/description64.skel
new file mode 100644
index 0000000..da01764
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/skeletons/simplewrap/description64.skel
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<jsmoothskeleton>
+<debug>false</debug>
+<description>SKEL_SIMPLEWRAPPER_DESCRIPTION</description>
+<executableName>jwrap.exe</executableName>
+<resourceCategory>JAVA</resourceCategory>
+<resourceJarId>102</resourceJarId>
+<resourcePropsId>103</resourcePropsId>
+<shortName>Windowed Wrapper x64</shortName>
+<skeletonProperties>
+<description>SKEL_SIMPLEWRAPPER_PROPERTY_MESSAGE_DESCRIPTION</description>
+<idName>Message</idName>
+<label>SKEL_SIMPLEWRAPPER_PROPERTY_MESSAGE_LABEL</label>
+<type>textarea</type>
+<value>Java has not been found on your computer. Do you want to download it?</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_SIMPLEWRAPPER_PROPERTY_URL_DESCRIPTION</description>
+<idName>URL</idName>
+<label>SKEL_SIMPLEWRAPPER_PROPERTY_URL_LABEL</label>
+<type>string</type>
+<value>http://www.java.com</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_PROPERTY_SINGLEPROCESS_DESCRIPTION</description>
+<idName>SingleProcess</idName>
+<label>SKEL_GENERIC_PROPERTY_SINGLEPROCESS_LABEL</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_SINGLEINSTANCE_DESCRIPTION</description>
+<idName>SingleInstance</idName>
+<label>SKEL_GENERIC_SINGLEINSTANCE</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_JNISMOOTH_DESCRIPTION</description>
+<idName>JniSmooth</idName>
+<label>SKEL_GENERIC_JNISMOOTH</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_PROPERTY_DEBUG_DESCRIPTION</description>
+<idName>Debug</idName>
+<label>SKEL_GENERIC_PROPERTY_DEBUG_LABEL</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+</jsmoothskeleton>
diff --git a/jsmooth-0.9.9-7-patch/skeletons/simplewrap/mainres.rc b/jsmooth-0.9.9-7-patch/skeletons/simplewrap/mainres.rc
new file mode 100644
index 0000000..e082dad
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/skeletons/simplewrap/mainres.rc
@@ -0,0 +1,8 @@
+#include "resource.h"
+
+JARID JAVA "../samplejar/sample.jar"
+PROPID JAVA "../samplejar/sample.props"
+JNISMOOTHID JAVA "../jnismooth/jnismooth.jar"
+
+A2 ICON MOVEABLE PURE LOADONCALL DISCARDABLE "JWrap.ico"
+1 MANIFEST "../samplejar/sample.manifest"
diff --git a/jsmooth-0.9.9-7-patch/skeletons/util-core/FileUtils.cpp b/jsmooth-0.9.9-7-patch/skeletons/util-core/FileUtils.cpp
new file mode 100644
index 0000000..f57ebff
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/skeletons/util-core/FileUtils.cpp
@@ -0,0 +1,211 @@
+/*
+  JSmooth: a VM wrapper toolkit for Windows
+  Copyright (C) 2003-2007 Rodrigo Reyes <reyes at charabia.net>
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Library General Public
+  License as published by the Free Software Foundation; either
+  version 2 of the License, or (at your option) any later version.
+  
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Library General Public License for more details.
+  
+  You should have received a copy of the GNU Library General Public
+  License along with this library; if not, write to the Free
+  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+  
+*/
+
+#include "FileUtils.h"
+
+string FileUtils::createTempFileName(const string& suffix)
+{
+    char temppath[512];
+    int tplen = GetTempPath(512, temppath);
+
+    int counter = 0;
+    string temp;
+    do {
+    
+       temp = string(temppath) + "temp" + StringUtils::toString(counter) + suffix;
+       counter ++;
+       
+      } while (GetFileAttributes(temp.c_str()) !=  0xFFFFFFFF);
+
+    return temp;
+}
+
+
+bool FileUtils::fileExists(const string& filename)
+{
+  if (filename[0] == '"')
+    {
+      string unquoted = StringUtils::replace(filename, "\"", "");
+      return GetFileAttributes(unquoted.c_str()) != 0xFFFFFFFF;
+    }
+  return GetFileAttributes(filename.c_str()) != 0xFFFFFFFF;
+}
+
+bool FileUtils::fileExists(const std::string& path, const std::string& filename)
+{
+  std::string f = FileUtils::concFile(path, filename);
+  return fileExists(f);
+}
+
+vector<string> FileUtils::recursiveSearch(const string& pathdir, const string& pattern)
+{
+    vector<string> result;
+    string path = StringUtils::replace(pathdir, "\"", "");
+
+    WIN32_FIND_DATA data;
+    string file = path + ((path[path.length()-1]=='\\')?"":"\\") + pattern;
+    
+    HANDLE handle = FindFirstFile(file.c_str(), &data);
+    if (handle != INVALID_HANDLE_VALUE)
+    {
+        result.push_back(path + ((path[path.length()-1]=='\\')?"":"\\") + data.cFileName);
+        for ( ; FindNextFile(handle, &data) == TRUE ; )
+        {
+              result.push_back(path + ((path[path.length()-1]=='\\')?"":"\\") + data.cFileName);
+        }    
+    
+        FindClose(handle);
+    }
+
+    handle = FindFirstFile((path + ((path[path.length()-1]=='\\')?"":"\\") + "*").c_str(), &data);
+    if (handle == INVALID_HANDLE_VALUE)
+        return result;
+
+    do {
+        string foundpath(data.cFileName);
+        if ((foundpath != ".") && (foundpath != "..") && ((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0))
+        {
+            string npath = path + ((path[path.length()-1]=='\\')?"":"\\") + data.cFileName;
+            vector<string> tres = FileUtils::recursiveSearch(npath, pattern);
+            result.insert(result.end(), tres.begin(), tres.end());
+        }
+    } while (FindNextFile(handle, &data));
+    FindClose(handle);
+    
+    return result;
+}
+
+std::string FileUtils::extractFileName(const std::string& filename)
+{
+    int start = filename.rfind("\\", filename.length()-1);
+    if (start != filename.npos)
+    {
+        return filename.substr(start+1);
+    }
+    return filename;
+}
+
+std::string FileUtils::getExecutablePath()
+{
+    char buffer[512];
+    GetModuleFileName(NULL, buffer, 512);
+    string full = buffer;
+    int pos = full.rfind('\\', full.npos);
+    
+    return full.substr(0, pos+1);
+}
+
+std::string FileUtils::getExecutableFileName()
+{
+    char buffer[512];
+    GetModuleFileName(NULL, buffer, 512);
+    string full = buffer;
+    int pos = full.rfind('\\', full.npos);
+    
+    return full.substr(pos+1);
+}
+
+std::string FileUtils::getParent(const std::string &path)
+{
+  if (path[path.length()-1] == '\\')
+    return getParent( path.substr(0, path.size() - 1) );
+
+  int pos = path.rfind('\\', path.npos);
+  if (pos != path.npos)
+    return path.substr(0, pos+1);
+  else
+    return path;
+}
+
+std::string FileUtils::getComputerName()
+{
+    char buffer[MAX_COMPUTERNAME_LENGTH + 1];
+    DWORD size = MAX_COMPUTERNAME_LENGTH+1;
+    GetComputerName(buffer, &size);
+    return buffer;
+}
+
+std::string FileUtils::concFile(std::string path, std::string file)
+{
+  if (FileUtils::isAbsolute(file))
+    return file;
+
+  if (path.length() > 0)
+    {
+      if (path[path.length()-1] != '\\')
+	path += '\\';
+    }
+  path += file;
+  
+  return path;
+}
+
+std::string FileUtils::getFileExtension(const std::string& filename)
+{
+  int pos = filename.rfind('.');
+  if (pos != std::string::npos)
+    {
+      return filename.substr(pos+1);
+    }
+  return "";
+}
+
+bool FileUtils::isAbsolute(const std::string& filename)
+{
+  if (((filename.length()>2) && (filename[1] == ':') && (filename[2] =='\\')) || ((filename.length() >2) && (filename[0] == '\\') && (filename[1]=='\\')))
+    return true;
+
+  return false;
+}
+
+void FileUtils::deleteOnReboot(std::string file)
+{
+  MoveFileEx(file.c_str(), 0, MOVEFILE_DELAY_UNTIL_REBOOT);
+}
+
+std::string FileUtils::readFile(const std::string& filename)
+{
+  HANDLE f = CreateFile(filename.c_str(), GENERIC_READ,
+			FILE_SHARE_READ, NULL,
+			OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+
+  std::string result;
+
+  if (f != INVALID_HANDLE_VALUE)
+    {
+      char buffer[129];
+      DWORD hasread;
+      buffer[127] = 0;
+
+      do {
+	ReadFile(f, buffer, 127, &hasread, NULL);
+	buffer[hasread] = 0;
+	result += buffer;
+      } while (hasread > 0);
+
+      CloseHandle(f);
+    }
+  else
+    {
+      //      printf("Can't open file %s\n",filename.c_str());
+    }
+
+  return result;
+}
diff --git a/jsmooth-0.9.9-7-patch/skeletons/util-core/FileUtils.h b/jsmooth-0.9.9-7-patch/skeletons/util-core/FileUtils.h
new file mode 100644
index 0000000..665c6af
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/skeletons/util-core/FileUtils.h
@@ -0,0 +1,127 @@
+/*
+  JSmooth: a VM wrapper toolkit for Windows
+  Copyright (C) 2003-2007 Rodrigo Reyes <reyes at charabia.net>
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Library General Public
+  License as published by the Free Software Foundation; either
+  version 2 of the License, or (at your option) any later version.
+  
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Library General Public License for more details.
+  
+  You should have received a copy of the GNU Library General Public
+  License along with this library; if not, write to the Free
+  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+  
+*/
+
+#ifndef __FILEUTILS_H_
+#define __FILEUTILS_H_
+
+#include <windows.h>
+#include <string>
+#include <vector>
+
+#include "StringUtils.h"
+
+using namespace std;
+
+/** File access utility class.
+ * This class contains static class members that provide facilities
+ * for manamement of files.
+ *
+ * @author Rodrigo Reyes <reyes at charabia.net>
+ */
+
+class FileUtils
+{
+ public:
+
+  /**
+   * Creates a temp filename.  This method builds a temporary file
+   * name in the default windows temporary directory.
+   *
+   * @param suffix the suffix to use for the filename.
+   */
+  static string createTempFileName(const string& suffix);
+
+  /**
+   * Test wether a file exists.
+   * @param filename the file name to check.
+   * @return true if the file exists, false otherwise.
+   */
+  static bool fileExists(const string& filename);    
+
+  static bool fileExists(const std::string&path, const string& filename);
+
+  /**
+   * Lookup recursively for files matching a given pattern in a given
+   * path. The method scans all the directory provided and its
+   * subdirectories. Each file matching the pattern is added to the
+   * resulting vector of strings.
+   *
+   * @param path the path of the directory to scan.
+   * @param pattern the file pattern, for instance "*.zip".
+   * @return a vector of string that contains the result.
+   */
+  static vector<string> recursiveSearch(const string& path, const string& pattern);
+
+  /**
+   * Extracts the file part of a file path. Given a path of the form
+   * c:\\a\\b\\c\\d or c\\d, return the last component of the file path
+   * (that is, d in the previous example).
+   *
+   * @param filename an absolute or relative filename.
+   * @return a string with the file part of the file.
+   */ 
+  static std::string extractFileName(const std::string& filename);
+
+  /**
+   * Returns the path where the executable application is executed
+   * from. This is not the current directory, but the directory path
+   * where the application was found. For instance if the application
+   * was runned at c:\\programs\\bin\\test.exe, it returns
+   * c:\\programs\\bin\\.
+   *
+   * @return a std::string text that holds the path.
+   */
+  static std::string getExecutablePath();
+
+
+  static std::string getParent(const std::string &path);
+
+  /**
+   * Returns the name of the executable binary that was used to start
+   * the application.For instance if the application
+   * was runned at c:\\programs\\bin\\test.exe, it returns
+   * test.exe
+   *
+   * @return the executable name.
+   */
+  static std::string getExecutableFileName();
+
+
+  /**
+   * Returns the name of the computer.
+   * @return a std::string that holds the computer name.
+   */
+  static std::string getComputerName();
+
+  static std::string concFile(std::string path, std::string file);
+
+  static std::string getFileExtension(const std::string& filename);
+
+  static bool isAbsolute(const std::string& filename);
+
+  static std::string readFile(const std::string& filename);
+
+  static void deleteOnReboot(std::string file);
+
+};
+
+#endif
+
+
diff --git a/jsmooth-0.9.9-7-patch/skeletons/util-core/Makefile.win b/jsmooth-0.9.9-7-patch/skeletons/util-core/Makefile.win
new file mode 100644
index 0000000..29e409e
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/skeletons/util-core/Makefile.win
@@ -0,0 +1,32 @@
+# Project: util-core
+
+PROJECTNAME=util-core
+RM   = rm -f
+CPP  = ${MINGW}/bin/${TARGET}-g++.exe
+CC   = ${MINGW}/bin/${TARGET}-gcc.exe
+WINDRES = windres.exe
+MINGW =
+RES  = 
+OBJ  = SingleInstanceManager.o Process.o Log.o Thread.o FileUtils.o StringUtils.o DebugConsole.o $(RES)
+LIBS =  -static-libgcc -L"$(MINGW)/lib"
+INCS =  -I"$(MINGW)/include"  -I"$(JDK)/include"  -I"$(JDK)/include/win32" 
+CXXINCS = -Os  -I"$(MINGW)/include/c++"  -I"$(MINGW)/include/c++/mingw32"  -I"$(MINGW)/include/c++/backward"  -I"$(MINGW)/include"  -I"$(JDK)/include"  -I"$(JDK)/include/win32" 
+BIN  = $(PROJECTNAME).a
+CXXFLAGS = $(CUSTOMFLAGS) $(CXXINCS) -DJDK="$(JDK)"
+CFLAGS = $(INCS) 
+
+.PHONY: all all-before all-after clean clean-custom $(PROJECTNAME)
+
+all: all-before $(PROJECTNAME).a testmain.cpp all-after
+
+
+clean: clean-custom
+	$(RM) $(OBJ) $(BIN) testmain.o test.exe
+
+$(BIN): $(OBJ)
+	ar r $(BIN) $(OBJ)
+	ranlib $(BIN)
+	$(CPP) $(OBJ) testmain.o -o test.exe $(LIBS) 
+
+$(PROJECTNAME).a: $(OBJ) testmain.o
+
diff --git a/jsmooth-0.9.9-7-patch/skeletons/util-core/StringUtils.h b/jsmooth-0.9.9-7-patch/skeletons/util-core/StringUtils.h
new file mode 100644
index 0000000..f64078e
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/skeletons/util-core/StringUtils.h
@@ -0,0 +1,163 @@
+/*
+  JSmooth: a VM wrapper toolkit for Windows
+  Copyright (C) 2003-2007 Rodrigo Reyes <reyes at charabia.net>
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Library General Public
+  License as published by the Free Software Foundation; either
+  version 2 of the License, or (at your option) any later version.
+  
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Library General Public License for more details.
+  
+  You should have received a copy of the GNU Library General Public
+  License along with this library; if not, write to the Free
+  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+  
+*/
+
+#ifndef __STRINGUTILS_H_
+#define __STRINGUTILS_H_
+
+#include <cstdio>
+#include <string>
+#include <vector>
+#include <windows.h>
+
+using namespace std;
+
+/**
+ * Provides basic string operations.
+ * 
+ * @author Rodrigo Reyes <reyes at charabia.net>
+ */
+
+class StringUtils
+{
+ public:
+  /**
+   * Splits a string in several substrings.  The split method takes a
+   * string as input, and outputs a vector of string. The string split
+   * is done according to 2 parameters:
+   *
+   * - the separators parameter, defines which chars are
+   * separators. Typically, this is a string that contains spaces,
+   * line-feed, and tabulation chars (" \n\t").
+   *
+   * - the quotechars parameter, is useful when your input contains
+   * quoted string that should not be broken down into substrings. For
+   * instance, a typical quote char is a quote or a double quote
+   * (resp. ' and "). 
+   *
+   * For example, is the input string is the line belows:
+   *  - this is "my string"    and this is 'another one'.
+   *
+   * Here is the result of the call with the following parameters:
+   *  - separators = \\n\\r\\t
+   *  - quotechars = \\"\\'
+   *
+   * The output string is:
+   *  - this
+   *  - is
+   *  - my string
+   *  - and
+   *  - this
+   *  - is
+   *  - another one
+   *
+   * @param str the string to be splitted
+   * @param separators a string that contains all the characters to be used as separators.
+   * @param quotechars a string that contains all the characters that are used as quote chars.
+   */
+  static vector<string> split(const string& str, const string& separators, const string& quotechars, bool handleEscape = true, bool returnSeparators = false);
+
+  /**
+   * Converts a string to an int. If the string is not a valid
+   * integer, it returns 0.
+   *
+   * @param val the string to parse
+   * @return an integer value
+   */
+  static int parseInt(const string& val);
+
+  static int parseHexa(const string& val);
+
+  /**
+   * Convers an integer to a string.
+   *
+   * @param val an integer
+   * @return a string
+   */
+  static string toString(int val);
+  static string toHexString(int val);
+
+  /**
+   * Provides a string representation of a vector of string.  A vector
+   * that contains 2 string aaa and bbb is represented as follows:
+   * [aaa, bbb].
+   *
+   * @param seq a vector of string
+   * @return a string representation
+   */
+  static string toString(const vector<string>& seq);
+
+  /**
+   * Copies the content of a string in a char array.
+   *
+   * @param from the string to copy from
+   * @param to the destination array of chars
+   * @param length the length of the array of char
+   */ 
+  static void copyTo(const string& from, char* to, int length);
+
+  /**
+   * Returns a copy of the string with the environment variables
+   * replaced. The environment variable must be surrounded by %
+   * signs. For each variable defined between 2 % signs, the method
+   * tries to replace it by the value of the corresponding environment
+   * variable. If no variable exists, it just replaces it with an
+   * empty string.
+   *
+   * @param str the string to transform
+   * @return the same string, but with all the environment variable replaced
+   */
+  static string replaceEnvironmentVariable(const string& str);
+
+  static string replace(const string& str, const string& pattern, const string& replacement);
+    
+  static string join(const vector<string>& seq, const string& separator);
+  static string trim(string& str);
+
+  /**
+   * If a string does not start with a quote ("), it returns a copy of
+   * the string with enclosing quotes.
+   *
+   * @param str a string
+   * @return a fixed copy of the string
+   */
+  static std::string fixQuotes(const string& str);
+
+  static std::string escape(const string& str);
+  static std::string unescape(const string& str);
+  /**
+   * Ensures a string is correctly quoted, the quotes are enclosing
+   * the whole string, not a part of the string. For instance
+   * <<"c:\\my path"\\bin>> is transformed into <<"c:\\my path\\bin">>
+   *
+   * @param str a string
+   * @return a fixed copy of the string
+   */
+  static std::string requote(const string& str);
+  static std::string requoteForCommandLine(const string& str);
+  static std::string toLowerCase(const string& str);
+
+  static std::string fixArgumentString(const std::string& arg);
+
+  static std::string sizeToJavaString(int size);
+  static std::string sizeToJavaString(std::string size);
+  
+};
+
+#endif
diff --git a/jsmooth-0.9.9-7-patch/skeletons/util-net/Makefile.win b/jsmooth-0.9.9-7-patch/skeletons/util-net/Makefile.win
new file mode 100644
index 0000000..32b2aaa
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/skeletons/util-net/Makefile.win
@@ -0,0 +1,34 @@
+# Project: CommonJava
+
+PROJECTNAME=util-net
+RM   = rm -f
+CPP  = ${MINGW}/bin/${TARGET}-g++.exe
+CC   = ${MINGW}/bin/${TARGET}-gcc.exe
+WINDRES = windres.exe
+MINGW = "c:/MinGW"
+FLTK-LDFLAGS = $(shell fltk-config --ldflags)
+FLTK-CXXFLAGS = $(shell fltk-config --cxxflags)
+RES  = 
+OBJ  = WinHttpClient.o HttpClient.o URL.o downloadgui.o httpdownload.o $(RES)
+LIBS =  -static-libgcc -L"$(MINGW)/lib" -lws2_32 -lwininet $(FLTK-LDFLAGS)
+CXXINCS = -Os -I"../util-core" -I"$(MINGW)/include/c++"  -I"$(MINGW)/include/c++/mingw32"  -I"$(MINGW)/include/c++/backward"  -I"$(MINGW)/include"  -I"$(JDK)/include"  -I"$(JDK)/include/win32"  $(FLTK-CXXFLAGS)
+BIN  = $(PROJECTNAME).a
+CXXFLAGS = $(CUSTOMFLAGS) $(CXXINCS) -DJDK="$(JDK)"
+
+.PHONY: all all-before all-after clean clean-custom $(PROJECTNAME)
+
+all: all-before $(PROJECTNAME).a testmain.cpp all-after
+
+clean: clean-custom
+	$(RM) $(OBJ) $(BIN) test.exe testmain.o
+
+$(BIN): $(OBJ)
+	ar r $(BIN) $(OBJ)
+	ranlib $(BIN)
+	$(CPP) -g $(OBJ)  ../util-core/util-core.a testmain.o -o test.exe $(LIBS)
+
+$(PROJECTNAME).a: $(OBJ) testmain.o
+
+
+testmain.o: testmain.cpp
+	$(CPP) -c testmain.cpp -o testmain.o $(CXXFLAGS)
diff --git a/jsmooth-0.9.9-7-patch/skeletons/winservice/Makefile.win b/jsmooth-0.9.9-7-patch/skeletons/winservice/Makefile.win
new file mode 100644
index 0000000..064aefc
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/skeletons/winservice/Makefile.win
@@ -0,0 +1,33 @@
+# Project: WinService Wrapper
+# Makefile created by Dev-C++ 4.9.8.0
+
+RM   = rm -f
+#cmd /C DEL
+CPP  = ${MINGW}/bin/${TARGET}-g++.exe
+CC   = ${MINGW}/bin/${TARGET}-gcc.exe
+WINDRES = windres.exe
+RES  = winservice_private.res
+OBJ  = main.o $(RES)
+LINKOBJ  = WinService.o main.o $(RES)
+LIBS =  -static-libgcc -L"${MINGW}/lib" -L"../commonjava" -L"../util-core" ../commonjava/CommonJava.a ../util-core/util-core.a 
+INCS =  -I"/include" -I"../util-core"  -I"../commonjava"  -I"$(JDK)/include"  -I"$(JDK)/include/win32" 
+CXXINCS = -I"$(MINGW)/include/c++"  -I"$(MINGW)/include/c++/mingw32"  -I"$(MINGW)/include/c++/backward"  -I"$(MINGW)/include"  -I"$(JDK)/include"  -I"$(JDK)/include/win32" -I"../commonjava" -I"../util-core" -I"../util-net" $(FLTK-CXXFLAGS)
+BIN  = winservice.exe
+CXXFLAGS = $(CUSTOMFLAGS) $(CXXINCS) -DJDK="$(JDK)"
+CFLAGS = $(INCS) 
+
+.PHONY: all all-before all-after clean clean-custom
+
+all: all-before winservice.exe all-after
+
+clean: clean-custom
+	$(RM) $(OBJ) $(BIN) winservice.o winservice_private.res
+
+$(BIN): $(LINKOBJ)  ../commonjava/CommonJava.a
+	$(CPP) $(LINKOBJ) -o "winservice.exe" $(LIBS)
+
+main.o: main.cpp
+	$(CPP) -c main.cpp -o main.o $(CXXFLAGS)
+
+winservice_private.res: winservice_private.rc mainres.rc 
+	$(WINDRES) -i winservice_private.rc -J rc -o winservice_private.res -O coff 
diff --git a/jsmooth-0.9.9-7-patch/skeletons/winservice/description64.skel b/jsmooth-0.9.9-7-patch/skeletons/winservice/description64.skel
new file mode 100644
index 0000000..bede8c4
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/skeletons/winservice/description64.skel
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<jsmoothskeleton>
+<debug>false</debug>
+<description>SKEL_WINSERVICE_DESCRIPTION</description>
+<executableName>winservice.exe</executableName>
+<resourceCategory>JAVA</resourceCategory>
+<resourceJarId>102</resourceJarId>
+<resourcePropsId>103</resourcePropsId>
+<shortName>WinService Wrapper x64</shortName>
+<skeletonProperties>
+<description>SKEL_WINSERVICE_NAME_DESCRIPTION</description>
+<idName>ServiceName</idName>
+<label>SKEL_WINSERVICE_NAME_LABEL</label>
+<type>string</type>
+<value>myservice</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_WINSERVICE_DISPLAYNAME_DESCRIPTION</description>
+<idName>ServiceDisplayName</idName>
+<label>SKEL_WINSERVICE_DISPLAYNAME_LABEL</label>
+<type>string</type>
+<value>My Service</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_WINSERVICE_PROPERTY_MESSAGE_DESCRIPTION</description>
+<idName>Message</idName>
+<label>SKEL_WINSERVICE_PROPERTY_MESSAGE_LABEL</label>
+<type>textarea</type>
+<value>This program needs Java to run. Please download it at http://www.java.comf</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_WINSERVICE_AUTOSTART_DESCRIPTION</description>
+<idName>Autostart</idName>
+<label>SKEL_WINSERVICE_AUTOSTART_LABEL</label>
+<type>boolean</type>
+<value>1</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_WINSERVICE_LOGFILE_DESCRIPTION</description>
+<idName>Logfile</idName>
+<label>SKEL_WINSERVICE_LOGFILE_LABEL</label>
+<type>string</type>
+<value>service.log</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_WINSERVICE_INTERACTIVE_DESCRIPTION</description>
+<idName>Interactive</idName>
+<label>SKEL_WINSERVICE_INTERACTIVE_LABEL</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_JNISMOOTH_DESCRIPTION</description>
+<idName>JniSmooth</idName>
+<label>SKEL_GENERIC_JNISMOOTH</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_PROPERTY_DEBUG_DESCRIPTION</description>
+<idName>Debug</idName>
+<label>SKEL_GENERIC_PROPERTY_DEBUG_LABEL</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+</jsmoothskeleton>
diff --git a/jsmooth-0.9.9-7-patch/skeletons/winservice/mainres.rc b/jsmooth-0.9.9-7-patch/skeletons/winservice/mainres.rc
new file mode 100644
index 0000000..f2634f1
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/skeletons/winservice/mainres.rc
@@ -0,0 +1,9 @@
+
+#include "resource.h"
+
+JARID JAVA "../samplejar/sample.jar"
+PROPID JAVA "../samplejar/sample.props"
+JNISMOOTHID JAVA "../jnismooth/jnismooth.jar"
+
+A2 ICON MOVEABLE PURE LOADONCALL DISCARDABLE "JWrap.ico"
+1 MANIFEST "../samplejar/sample.manifest"
diff --git a/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/application/ExeCompiler.java b/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/application/ExeCompiler.java
new file mode 100644
index 0000000..d197382
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/application/ExeCompiler.java
@@ -0,0 +1,534 @@
+/*
+  JSmooth: a VM wrapper toolkit for Windows
+  Copyright (C) 2003 Rodrigo Reyes <reyes at charabia.net>
+ 
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+ 
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+ 
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ 
+ */
+
+package net.charabia.jsmoothgen.application;
+
+import net.charabia.jsmoothgen.pe.PEFile;
+import net.charabia.jsmoothgen.pe.PEResourceDirectory;
+import net.charabia.jsmoothgen.skeleton.SkeletonBean;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.awt.image.IndexColorModel;
+import java.awt.image.PixelGrabber;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.FileChannel;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Vector;
+
+public class ExeCompiler {
+    private java.util.Vector m_errors = new java.util.Vector();
+    private Vector m_listeners = new Vector();
+
+    public interface StepListener {
+        public void setNewState(int percentComplete, String state);
+
+        public void failed();
+
+        public void complete();
+    }
+
+    public void addListener(ExeCompiler.StepListener listener) {
+        m_listeners.add(listener);
+
+    }
+
+    public void cleanErrors() {
+        m_errors.removeAllElements();
+    }
+
+    public java.util.Vector getErrors() {
+        return m_errors;
+    }
+
+    public class CompilerRunner implements Runnable {
+        private File m_skelroot;
+        private SkeletonBean m_skel;
+        private JSmoothModelBean m_data;
+        private File m_out;
+        private File m_basedir;
+
+        public CompilerRunner(File skelroot, SkeletonBean skel, File basedir, JSmoothModelBean data, File out) {
+            m_skelroot = skelroot;
+            m_skel = skel;
+            m_data = data;
+            m_out = out;
+            m_basedir = basedir;
+        }
+
+        public void run() {
+            try {
+                compile(m_skelroot, m_skel, m_basedir, m_data, m_out);
+            } catch (Exception exc) {
+                exc.printStackTrace();
+            }
+        }
+
+        public ExeCompiler getCompiler() {
+            return ExeCompiler.this;
+        }
+    }
+
+    public ExeCompiler.CompilerRunner getRunnable(File skelroot, SkeletonBean skel, File basedir, JSmoothModelBean data, File out) {
+        return new CompilerRunner(skelroot, skel, basedir, data, out);
+    }
+
+    public void compileAsync(File skelroot, SkeletonBean skel, File basedir, JSmoothModelBean data, File out) {
+        Thread t = new Thread(new CompilerRunner(skelroot, skel, basedir, data, out));
+        t.start();
+    }
+
+    public boolean compile(File skelroot, SkeletonBean skel, File basedir, JSmoothModelBean data, File out) throws Exception {
+        try {
+            fireStepChange(0, "Starting compilation");
+
+            File pattern = new File(skelroot, skel.getExecutableName());
+            if (pattern.exists() == false) {
+                m_errors.add("Error: Can't find any skeleton at " + skelroot);
+                fireFailedChange();
+                return false;
+            }
+
+            fireStepChange(10, "Scanning skeleton...");
+            PEFile pe = new PEFile(pattern);
+            pe.open();
+            PEResourceDirectory resdir = pe.getResourceDirectory();
+
+            boolean resb = false;
+
+            //
+            // Adds the jar only if the user selected one
+            //
+            if (data.getEmbeddedJar() == true) {
+                if (data.getJarLocation() == null) {
+                    m_errors.add("Error: Jar is not specified!");
+                    fireFailedChange();
+                    return false;
+                }
+
+                fireStepChange(40, "Loading Jar...");
+                File jarloc = concFile(basedir, new File(data.getJarLocation()));
+                if (jarloc.exists() == false) {
+                    m_errors.add("Error: Can't find jar at " + jarloc);
+                    fireFailedChange();
+                    return false;
+                }
+
+                ByteBuffer jardata = load(jarloc);
+
+                fireStepChange(60, "Adding Jar to Resources...");
+                resb = resdir.replaceResource(skel.getResourceCategory(), skel.getResourceJarId(), 1033, jardata);
+                if (resb == false) {
+                    m_errors.add("Error: Can't replace jar resource! It is probably missing from the skeleton.");
+                    fireFailedChange();
+                    return false;
+                }
+            }
+
+            fireStepChange(70, "Adding Properties and Manifest to Resources...");
+            // Properties...
+            String props = PropertiesBuilder.makeProperties(basedir, data);
+            ByteBuffer propdata = convert(props);
+            resb = resdir.replaceResource(skel.getResourceCategory(), skel.getResourcePropsId(), 1033, propdata);
+
+            // Manifest...
+            String manifest = PropertiesBuilder.makeManifest(data, skel);
+            if (manifest == null || manifest.isEmpty()) {
+                manifest = "";
+            }
+            ByteBuffer manifestData = convert(manifest);
+            resb = resdir.replaceManifest(1, 1033, manifestData);
+
+            if (data.getIconLocation() != null) {
+                fireStepChange(80, "Loading icon...");
+                String iconpath;
+                if (new java.io.File(data.getIconLocation()).isAbsolute())
+                    iconpath = data.getIconLocation();
+                else
+                    iconpath = new java.io.File(basedir, data.getIconLocation()).getAbsolutePath();
+
+                Image img = getScaledImage(iconpath, 32, 32);
+                //Hashtable set = calculateColorCount(img);
+                //		    System.out.println("COLORS TOTAL 4: " + set.size());
+
+                if (img != null) {
+                    net.charabia.jsmoothgen.pe.res.ResIcon32 resicon = new net.charabia.jsmoothgen.pe.res.ResIcon32(img);
+                    pe.replaceDefaultIcon(resicon);
+                }
+            }
+
+            fireStepChange(90, "Saving exe...");
+            pe.dumpTo(out);
+
+            //		System.out.println("PROPERTIES:\n" + props);
+
+            fireCompleteChange();
+            return true;
+        } catch (Exception exc) {
+            m_errors.add("Error: " + exc.getMessage());
+            exc.printStackTrace();
+            fireFailedChange();
+            return false;
+        }
+    }
+
+    public Image[] loadImages(String path) {
+        File f = new File(path);
+
+        if (path.toUpperCase().endsWith(".ICO")) {
+            //
+            // Try to load with our ico codec...
+            //
+            try {
+                java.awt.Image[] images = net.charabia.util.codec.IcoCodec.loadImages(f);
+                if ((images != null) && (images.length > 0)) {
+                    return images;
+                }
+            } catch (java.io.IOException exc) {
+                exc.printStackTrace();
+            }
+        }
+
+        //
+        // defaults to the standard java loading process
+        //
+        BufferedImage bufferedImage;
+        try {
+            bufferedImage = ImageIO.read(f);
+            javax.swing.ImageIcon icon = new javax.swing.ImageIcon(bufferedImage, "default icon");
+            java.awt.Image[] imgs = new java.awt.Image[1];
+            imgs[0] = icon.getImage();
+            return imgs;
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    public void checkImageLoaded(Image img) {
+        MediaTracker mtrack = new MediaTracker(new Canvas());
+        mtrack.addImage(img, 1);
+        try {
+            mtrack.waitForAll();
+        } catch (InterruptedException e) {
+        }
+    }
+
+    private Hashtable calculateColorCount(Image img) {
+        int width = img.getWidth(null);
+        int height = img.getHeight(null);
+        int[] pixels = new int[width * height];
+        PixelGrabber grabber = new PixelGrabber(img, 0, 0, width, height, pixels, 0, width);
+        try {
+            grabber.grabPixels();
+        } catch (InterruptedException e) {
+            System.err.println("interrupted waiting for pixels!");
+            //		    throw new Exception("Can't load the image provided",e);
+        }
+
+
+        Hashtable result = new Hashtable();
+        int colorindex = 0;
+        for (int i = 0; i < pixels.length; i++) {
+            int pix = pixels[i];
+            if (((pix >> 24) & 0xFF) > 0) {
+                pix &= 0x00FFFFFF;
+                Integer pixi = new Integer(pix);
+                Object o = result.get(pixi);
+                if (o == null) {
+                    result.put(pixi, new Integer(colorindex++));
+                }
+                //			if (colorindex > 256)
+                //			    return result;
+            }
+        }
+        return result;
+    }
+
+    public BufferedImage getQuantizedImage(Image img) {
+        // 32 bit ico file already loaded as BufferedImage
+        if (img instanceof BufferedImage) {
+            return (BufferedImage) img;
+        } else {
+            int width = img.getWidth(null);
+            int height = img.getHeight(null);
+            int[][] data = new int[width][height];
+
+            int[] pixelbuffer = new int[width * height];
+            PixelGrabber grabber = new PixelGrabber(img, 0, 0, width, height, pixelbuffer, 0, width);
+            try {
+                grabber.grabPixels();
+            } catch (InterruptedException e) {
+                System.err.println("interrupted waiting for pixels!");
+                throw new RuntimeException("Can't load the image provided", e);
+            }
+            for (int i = 0; i < pixelbuffer.length; i++) {
+                data[i % width][i / width] = pixelbuffer[i];
+            }
+
+            int[][] savedata = new int[width][height];
+
+            for (int y = 0; y < height; y++)
+                for (int x = 0; x < width; x++)
+                    savedata[x][y] = data[x][y];
+
+            int[] palette = net.charabia.util.codec.Quantize.quantizeImage(data, 255);
+            byte[] cmap = new byte[256 * 4];
+
+            for (int i = 0; i < palette.length; i++) {
+                //		System.out.println(" i= " + (i));
+                cmap[(i * 4)] = (byte) ((palette[i] >> 16) & 0xFF);
+                cmap[(i * 4) + 1] = (byte) ((palette[i] >> 8) & 0xFF);
+                cmap[(i * 4) + 2] = (byte) (palette[i] & 0xFF);
+                cmap[(i * 4) + 3] = (byte) 0xFF;
+            }
+
+            IndexColorModel colmodel = new IndexColorModel(8, palette.length, cmap, 0, true, 0);
+            BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+            //
+            // The normal manner of quantizing would be to run
+            // result.setRGB(0,0, width, height, pixelbuffer, 0, width);
+            // where result is a BufferedImage of
+            // BufferedImage.TYPE_BYTE_INDEXED type. Unfortunately, I
+            // couldn't make it work. So, here is a work-around that
+            // should work similarly.
+            //
+            java.util.Hashtable set = new java.util.Hashtable();
+            for (int y = 0; y < height; y++) {
+                for (int x = 0; x < width; x++) {
+                    int alpha = (savedata[x][y] >> 24) & 0xFF;
+                    if (alpha == 0) {
+                        result.setRGB(x, y, 0);
+                        // 				System.out.print(".");
+                    } else {
+                        int rgb = colmodel.getRGB(data[x][y]);
+                        rgb |= 0xFF000000;
+                        set.put(new Integer(rgb), new Integer(rgb));
+                        result.setRGB(x, y, rgb);
+                        // 				System.out.print("*");
+                    }
+                }
+                //		System.out.println("");
+            }
+
+
+            return result;
+        }
+    }
+
+    public Image checkImageSize(Image img, int width, int height) {
+        int w = img.getWidth(null);
+        int h = img.getHeight(null);
+        if ((w == width) && (h == height))
+            return img;
+        return null;
+    }
+
+    public Image getScaledImage(String path, int width, int height) {
+        Image[] orgimages = loadImages(path);
+
+        if ((orgimages == null) || (orgimages.length == 0))
+            return null;
+
+        for (int i = 0; i < orgimages.length; i++)
+            checkImageLoaded(orgimages[i]);
+
+        //	System.out.println("Loaded " + orgimages.length + " images");
+        for (int i = 0; (i < orgimages.length); i++) {
+            int w = orgimages[i].getWidth(null);
+            int h = orgimages[i].getHeight(null);
+            //		System.out.println("Size of " + i + " = " + w + "," + h);
+        }
+
+        //
+        // We prefer 32x32 pictures, then 64x64, then 16x16...
+        //
+        Image selected = null;
+        for (int i = 0; (i < orgimages.length) && (selected == null); i++)
+            selected = checkImageSize(orgimages[i], 32, 32);
+        for (int i = 0; (i < orgimages.length) && (selected == null); i++)
+            selected = checkImageSize(orgimages[i], 64, 64);
+        for (int i = 0; (i < orgimages.length) && (selected == null); i++)
+            selected = checkImageSize(orgimages[i], 16, 16);
+
+        if (selected != null) {
+            return getQuantizedImage(selected);
+        }
+
+        //
+        // If there is no 32x32, 64x64, nor 16x16, then we scale the
+        // biggest image to be 32x32... This should happen mainly when
+        // loading an image from a png of gif file, and in most case
+        // there is only one image on the array.
+        //
+        int maxsize = 0;
+        Image biggest = null;
+        for (int i = 0; (i < orgimages.length) && (selected == null); i++) {
+            int size = orgimages[i].getWidth(null) * orgimages[i].getHeight(null);
+            if (size > maxsize) {
+                maxsize = size;
+                biggest = orgimages[i];
+            }
+        }
+
+        if (biggest != null) {
+            BufferedImage result = getScaledInstance((BufferedImage) biggest, 32, 32, RenderingHints.VALUE_INTERPOLATION_BILINEAR, true);
+            checkImageLoaded(result);
+            return getQuantizedImage(result);
+        }
+        //
+        // Here, we have failed and return null
+        //
+        return null;
+    }
+
+    /**
+     * Convenience method that returns a scaled instance of the
+     * provided {@code BufferedImage}.
+     *
+     * @param img           the original image to be scaled
+     * @param targetWidth   the desired width of the scaled instance,
+     *                      in pixels
+     * @param targetHeight  the desired height of the scaled instance,
+     *                      in pixels
+     * @param hint          one of the rendering hints that corresponds to
+     *                      {@code RenderingHints.KEY_INTERPOLATION} (e.g.
+     *                      {@code RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR},
+     *                      {@code RenderingHints.VALUE_INTERPOLATION_BILINEAR},
+     *                      {@code RenderingHints.VALUE_INTERPOLATION_BICUBIC})
+     * @param higherQuality if true, this method will use a multi-step
+     *                      scaling technique that provides higher quality than the usual
+     *                      one-step technique (only useful in downscaling cases, where
+     *                      {@code targetWidth} or {@code targetHeight} is
+     *                      smaller than the original dimensions, and generally only when
+     *                      the {@code BILINEAR} hint is specified)
+     * @return a scaled version of the original {@code BufferedImage}
+     */
+    public BufferedImage getScaledInstance(BufferedImage img,
+                                           int targetWidth,
+                                           int targetHeight,
+                                           Object hint,
+                                           boolean higherQuality) {
+        int type = (img.getTransparency() == Transparency.OPAQUE) ?
+                BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
+        BufferedImage ret = (BufferedImage) img;
+        int w, h;
+        if (higherQuality) {
+            // Use multi-step technique: start with original size, then
+            // scale down in multiple passes with drawImage()
+            // until the target size is reached
+            w = img.getWidth();
+            h = img.getHeight();
+        } else {
+            // Use one-step technique: scale directly from original
+            // size to target size with a single drawImage() call
+            w = targetWidth;
+            h = targetHeight;
+        }
+
+        do {
+            if (higherQuality && w > targetWidth) {
+                w /= 2;
+                if (w < targetWidth) {
+                    w = targetWidth;
+                }
+            }
+
+            if (higherQuality && h > targetHeight) {
+                h /= 2;
+                if (h < targetHeight) {
+                    h = targetHeight;
+                }
+            }
+
+            BufferedImage tmp = new BufferedImage(w, h, type);
+            Graphics2D g2 = tmp.createGraphics();
+            g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
+            g2.drawImage(ret, 0, 0, w, h, null);
+            g2.dispose();
+
+            ret = tmp;
+        } while (w != targetWidth || h != targetHeight);
+
+        return ret;
+    }
+
+
+    private ByteBuffer load(File in) throws Exception {
+        FileInputStream fis = new FileInputStream(in);
+        ByteBuffer data = ByteBuffer.allocate((int) in.length());
+        data.order(ByteOrder.LITTLE_ENDIAN);
+        FileChannel fischan = fis.getChannel();
+        fischan.read(data);
+        data.position(0);
+        fis.close();
+
+        return data;
+    }
+
+    private ByteBuffer convert(String data) {
+        data = data.replace("\r\n", "\n").replace("\n", "\r\n");
+        ByteBuffer result = ByteBuffer.allocate(data.length());
+        result.position(0);
+
+        for (int i = 0; i < data.length(); i++) {
+            result.put((byte) data.charAt(i));
+        }
+        // result.put((byte)0);
+
+        result.position(0);
+        return result;
+    }
+
+    static public File concFile(File root, File name) {
+        if (name.isAbsolute())
+            return name;
+
+        return new File(root, name.toString());
+    }
+
+    public void fireStepChange(int percentComplete, String state) {
+        for (Iterator i = m_listeners.iterator(); i.hasNext();) {
+            ExeCompiler.StepListener l = (ExeCompiler.StepListener) i.next();
+            l.setNewState(percentComplete, state);
+        }
+    }
+
+    public void fireFailedChange() {
+        for (Iterator i = m_listeners.iterator(); i.hasNext();) {
+            ExeCompiler.StepListener l = (ExeCompiler.StepListener) i.next();
+            l.failed();
+        }
+    }
+
+    public void fireCompleteChange() {
+        for (Iterator i = m_listeners.iterator(); i.hasNext();) {
+            ExeCompiler.StepListener l = (ExeCompiler.StepListener) i.next();
+            l.complete();
+        }
+    }
+
+}
diff --git a/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/application/JSmoothModelBean.java b/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/application/JSmoothModelBean.java
new file mode 100644
index 0000000..5215a1c
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/application/JSmoothModelBean.java
@@ -0,0 +1,408 @@
+/*
+  JSmooth: a VM wrapper toolkit for Windows
+  Copyright (C) 2003 Rodrigo Reyes <reyes at charabia.net>
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ */
+
+/*
+ * JSmoothModelBean.java
+ *
+ * Created on 7 aout 2003, 18:32
+ */
+
+package net.charabia.jsmoothgen.application;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.Vector;
+
+public class JSmoothModelBean {
+  private String m_skeletonName;
+  private String m_executableName;
+  private String m_currentDirectory;
+
+  private String m_iconLocation;
+  private boolean m_embedJar = false;
+  private String m_jarLocation;
+  private String m_mainClassName;
+  private String m_arguments;
+  private String[] m_classPath;
+  private String m_minimumVersion = "";
+  private String m_maximumVersion = "";
+  private String[] m_jvmSearch = null;
+
+  private int m_maxHeap = -1;
+  private int m_initialHeap = -1;
+
+  private String m_vmParamter;
+
+  private int m_uacRequireAdmin;
+
+  private String m_noJvmMessage;
+  private String m_noJvmURL;
+
+  private String m_bundledJVM = null;
+
+  private JavaPropertyPair[] m_javaprops = new JavaPropertyPair[0];
+  private JSmoothModelBean.Property[] m_skelproperties;
+
+  static public class Property {
+    public String Key;
+    public String Value;
+
+    public void setKey(String key) {
+      this.Key = key;
+    }
+
+    public String getKey() {
+      return this.Key;
+    }
+
+    public void setValue(String val) {
+      this.Value = val;
+    }
+
+    public String getValue() {
+      return this.Value;
+    }
+
+    public String toString() {
+      return getKey() + "==" + getValue();
+    }
+  }
+
+  transient Vector m_listeners = new Vector();
+
+  public static interface Listener {
+    public void dataChanged();
+  }
+
+  transient Vector m_skeletonChangedListener = new Vector();
+
+  public static interface SkeletonChangedListener {
+    public void skeletonChanged();
+  }
+
+    /**
+     * Creates a new instance of JSmoothModelBean
+     */
+    public JSmoothModelBean() {
+  }
+
+  public void addListener(JSmoothModelBean.Listener l) {
+    m_listeners.add(l);
+  }
+
+  public void removeListener(JSmoothModelBean.Listener l) {
+    m_listeners.remove(l);
+  }
+
+  public void addSkeletonChangedListener(JSmoothModelBean.SkeletonChangedListener l) {
+    m_skeletonChangedListener.add(l);
+  }
+
+  public void removeSkeletonChangedListener(JSmoothModelBean.SkeletonChangedListener l) {
+    m_skeletonChangedListener.remove(l);
+  }
+
+
+  private void fireChanged() {
+    for (Iterator i = m_listeners.iterator(); i.hasNext();) {
+      JSmoothModelBean.Listener l = (JSmoothModelBean.Listener)i.next();
+      l.dataChanged();
+    }
+  }
+
+  private void fireSkeletonChanged() {
+    for (Iterator i = m_skeletonChangedListener.iterator(); i.hasNext();) {
+      JSmoothModelBean.SkeletonChangedListener l = (JSmoothModelBean.SkeletonChangedListener)i.next();
+      l.skeletonChanged();
+    }
+  }
+
+  public void setSkeletonName(String name) {
+    if (name != m_skeletonName) {
+      m_skeletonName = name;
+      fireSkeletonChanged();
+      fireChanged();
+    }
+  }
+
+  public String getSkeletonName() {
+    return m_skeletonName;
+  }
+
+  public void setExecutableName(String name) {
+    if (name != m_executableName) {
+      m_executableName = name;
+      fireChanged();
+    }
+  }
+
+
+  public void setCurrentDirectory(String curdir) {
+    if (curdir != m_currentDirectory) {
+      m_currentDirectory = curdir;
+      fireChanged();
+    }
+  }
+
+  public String getCurrentDirectory() {
+    return m_currentDirectory;
+  }
+
+  public String getExecutableName() {
+    return m_executableName;
+  }
+
+  public void setIconLocation(String name) {
+    if (name != m_iconLocation) {
+      m_iconLocation = name;
+      fireChanged();
+    }
+  }
+
+  public String getIconLocation() {
+    return m_iconLocation;
+  }
+
+  public boolean getEmbeddedJar() {
+    return m_embedJar;
+  }
+
+  public void setEmbeddedJar(boolean b) {
+    m_embedJar = b;
+    fireChanged();
+  }
+
+  public void setJarLocation(String name) {
+    if (name != m_jarLocation) {
+      m_jarLocation = name;
+      fireChanged();
+    }
+  }
+
+  public String getJarLocation() {
+    return m_jarLocation;
+  }
+
+
+  public void setMainClassName(String name) {
+    if (name != m_mainClassName) {
+      m_mainClassName = name;
+      fireChanged();
+    }
+  }
+
+  public String getMainClassName() {
+    return m_mainClassName;
+  }
+
+  public void setArguments(String args) {
+    m_arguments = args;
+    fireChanged();
+  }
+
+  public String getArguments() {
+    return m_arguments;
+  }
+
+  public void setClassPath(String[] cp) {
+    m_classPath = cp;
+    fireChanged();
+  }
+
+  public String[] getClassPath() {
+    return m_classPath;
+  }
+
+  public void setMaximumVersion(String version) {
+    m_maximumVersion = version;
+    fireChanged();
+  }
+
+  public String getMaximumVersion() {
+    return m_maximumVersion;
+  }
+
+  public void setMinimumVersion(String version) {
+    m_minimumVersion = version;
+    fireChanged();
+  }
+
+  public String getMinimumVersion() {
+    return m_minimumVersion;
+  }
+
+  public void setJVMSearchPath(String[] path) {
+    m_jvmSearch = path;
+    fireChanged();
+  }
+
+  public String[] getJVMSearchPath() {
+    return m_jvmSearch;
+  }
+
+  public void setSkeletonProperties(JSmoothModelBean.Property[] props) {
+    // for (int i=0; i<props.length; i++)
+    // System.out.println("SET PROPERTY: " + props[i].getIdName() + "=" + props[i].getValue());
+
+    m_skelproperties = props;
+    fireChanged();
+  }
+
+  public JSmoothModelBean.Property[] getSkeletonProperties() {
+    return m_skelproperties;
+  }
+
+  public void setNoJvmMessage(String msg) {
+    m_noJvmMessage = msg;
+    fireChanged();
+  }
+
+  public String getNoJvmMessage() {
+    return m_noJvmMessage;
+  }
+
+  public void setNoJvmURL(String url) {
+    m_noJvmURL = url;
+    fireChanged();
+  }
+
+  public String getNoJvmURL() {
+    return m_noJvmURL;
+  }
+
+  public String getBundledJVMPath() {
+    return m_bundledJVM;
+  }
+
+  public void setBundledJVMPath(String path) {
+    m_bundledJVM = path;
+    fireChanged();
+  }
+
+  public void setJavaProperties(JavaPropertyPair[] pairs) {
+    m_javaprops = pairs;
+  }
+
+  public JavaPropertyPair[] getJavaProperties() {
+    return m_javaprops;
+  }
+
+  public void setMaximumMemoryHeap(int size) {
+    m_maxHeap = size;
+  }
+
+  public int getMaximumMemoryHeap() {
+    return m_maxHeap;
+  }
+
+  public void setInitialMemoryHeap(int size) {
+    m_initialHeap = size;
+  }
+
+  public int getInitialMemoryHeap() {
+    return m_initialHeap;
+  }
+
+  public void setVmParameter(String parameter) {
+    m_vmParamter = parameter;
+  }
+
+  public String getVmParameter() {
+    return m_vmParamter;
+  }
+
+  public void setUacRequireAdministrator(int parameter) {
+    m_uacRequireAdmin = parameter;
+  }
+
+  public int getUacRequireAdministrator() {
+    return m_uacRequireAdmin;
+  }
+
+  public boolean isUacRequireAdmin() {
+    return m_uacRequireAdmin > 0;
+  }
+
+  public String[] normalizePaths(java.io.File filebase) {
+    return normalizePaths(filebase, true);
+  }
+
+  public String[] normalizePaths(java.io.File filebase, boolean toRelativePath) {
+    // System.out.println("Normalize Path " + filebase + " / " + toRelativePath);
+    Vector result = new Vector();
+
+    m_iconLocation = checkRelativePath(filebase, m_iconLocation, result, "Icon location", toRelativePath);
+    m_jarLocation = checkRelativePath(filebase, m_jarLocation, result, "Jar location", toRelativePath);
+    m_bundledJVM = checkRelativePath(filebase, m_bundledJVM, result, "Bundle JVM location", toRelativePath);
+    m_executableName = checkRelativePath(filebase, m_executableName, result, "Executable location", toRelativePath);
+
+    if (m_executableName != null) {
+      File exebase = new File(m_executableName);
+            if (exebase.isAbsolute() == false)
+                exebase = new File(filebase, exebase.toString()).getParentFile();
+
+      // System.out.println("EXE FILEBASE: " + exebase.toString());
+      if ((m_currentDirectory != null) && (m_currentDirectory.indexOf("${") >= 0))
+        m_currentDirectory = checkRelativePath(exebase, m_currentDirectory, result, "Current directory", toRelativePath);
+    }
+
+    if (m_classPath != null) {
+      for (int i = 0; i < m_classPath.length; i++) {
+        m_classPath[i] = checkRelativePath(filebase, m_classPath[i], result, "Classpath entry (" + i + ")", toRelativePath);
+      }
+    }
+
+        if (result.size() == 0)
+            return null;
+
+    String[] res = new String[result.size()];
+    result.toArray(res);
+
+    return res;
+  }
+
+  private String checkRelativePath(java.io.File root, String value, java.util.Vector errors, String name, boolean toRelativePath) {
+        if (value == null)
+            return value;
+
+    if (toRelativePath) {
+      File nf = JSmoothModelPersistency.makePathRelativeIfPossible(root, new File(value));
+      if (nf.isAbsolute()) {
+        errors.add(name);
+      }
+      return nf.toString();
+    } else {
+      File nf = new File(value);
+      if (nf.isAbsolute() == false) {
+        nf = new File(root, value);
+        nf = nf.getAbsoluteFile();
+
+        try {
+          nf = nf.getCanonicalFile();
+          nf = nf.getAbsoluteFile();
+        } catch (IOException iox) {
+          // do nothing
+        }
+      }
+      return nf.toString();
+    }
+  }
+}
diff --git a/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/application/PropertiesBuilder.java b/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/application/PropertiesBuilder.java
new file mode 100644
index 0000000..82b7421
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/application/PropertiesBuilder.java
@@ -0,0 +1,250 @@
+/*
+  JSmooth: a VM wrapper toolkit for Windows
+  Copyright (C) 2003 Rodrigo Reyes <reyes at charabia.net>
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ */
+
+package net.charabia.jsmoothgen.application;
+
+import java.io.File;
+
+import net.charabia.jsmoothgen.skeleton.SkeletonBean;
+
+/**
+ * @author Rodrigo
+ */
+public class PropertiesBuilder {
+    /**
+     * Creates a text containing all the relevant properties of a
+     * JSmoothModelBean object. The properties are output in the form
+     * "key=value".
+     * <p/>
+     * <p/>
+     * Note that all the paths are converted to be made relative to
+     * the basedir parameter provided. All the paths converted are
+     * expected to be relative to the targetted executable binary
+     * (before the conversion is applied, that is).
+     */
+    static public String makeProperties(File basedir, JSmoothModelBean obj) {
+        StringBuffer out = new StringBuffer();
+
+    addPair("arguments", obj.getArguments(), out);
+    addPair("mainclassname", obj.getMainClassName(), out);
+    addPair("jvmsearch", makePathConc(obj.getJVMSearchPath()), out);
+    addPair("minversion", obj.getMinimumVersion(), out);
+    addPair("maxversion", obj.getMaximumVersion(), out);
+
+    addPair("currentdir", obj.getCurrentDirectory(), out);
+
+    if (obj.getEmbeddedJar() && (obj.getJarLocation().trim().length() > 0)) {
+      addPair("embedjar", "true", out);
+    } else {
+      addPair("embedjar", "false", out);
+    }
+
+    if (obj.getMaximumMemoryHeap() > 1) {
+      addPair("maxheap", Integer.toString(obj.getMaximumMemoryHeap()), out);
+    }
+
+    if (obj.getInitialMemoryHeap() > 1) {
+      addPair("initialheap", Integer.toString(obj.getInitialMemoryHeap()), out);
+    }
+
+    if ((obj.getVmParameter() != null) && (!obj.getVmParameter().isEmpty())) {
+      addPair("vmparameter", obj.getVmParameter(), out);
+    }
+    // BundledVM & classpaths are changed to be accessible
+    // from the current directory
+    File curdir = new File(obj.getExecutableName()).getParentFile();
+
+        if (curdir == null)
+            curdir = basedir.getAbsoluteFile();
+
+        if (curdir.isAbsolute() == false) {
+            curdir = new File(basedir, curdir.toString());
+        }
+
+
+        //	System.out.println("... curdir1 : " + curdir.toString());
+
+    if (obj.getCurrentDirectory() != null) {
+      File newcurdir = new File(obj.getCurrentDirectory());
+      // System.out.println("... curdir1.5 : " + obj.getCurrentDirectory());
+
+      if (!"${EXECUTABLEPATH}".equalsIgnoreCase(obj.getCurrentDirectory())) {
+        if (newcurdir.isAbsolute() == false) {
+          curdir = new File(curdir, newcurdir.toString());
+        } else
+          curdir = newcurdir;
+      }
+    }
+    // System.out.println("... curdir2 : " + curdir.toString());
+
+    if (obj.getBundledJVMPath() != null) 
+		addPair("bundledvm", getRenormalizedPathIfNeeded(obj.getBundledJVMPath(), basedir, curdir), out);
+
+    if (obj.getClassPath() != null) {
+      String[] relcp = new String[obj.getClassPath().length];
+      for (int i = 0; i < relcp.length; i++) {
+        relcp[i] = getRenormalizedPathIfNeeded(obj.getClassPath()[i], basedir, curdir);
+      }
+      addPair("classpath", makePathConc(relcp), out);
+    }
+
+    //
+    // Adds all the skeleton-specific properties
+    //
+    if (obj.getSkeletonProperties() != null) {
+      for (int i = 0; i < obj.getSkeletonProperties().length; i++) {
+        JSmoothModelBean.Property prop = obj.getSkeletonProperties()[i];
+        if (prop.getKey() != null) {
+          String val = prop.getValue();
+                    if (val == null)
+                        val = "";
+                    addPair("skel_" + prop.getKey(), val, out);
+                }
+            }
+        }
+
+
+        //
+        // Adds all the java properties. Those properties are
+    // typically passed as -Dname=value arguments for the sun's
+    // JVM.
+    //
+
+    JavaPropertyPair[] javapairs = obj.getJavaProperties();
+    if (javapairs != null) {
+      addPair("javapropertiescount", new Integer(javapairs.length).toString(), out);
+      for (int i = 0; i < javapairs.length; i++) {
+        addPair("javaproperty_name_" + i, javapairs[i].getName(), out);
+        addPair("javaproperty_value_" + i, javapairs[i].getValue(), out);
+      }
+    }
+
+    return out.toString();
+  }
+
+  /**
+   * Create a manifest entry for windows right elevation
+   * 
+   * @param data
+   * @param skel
+   * @return the manifest xml
+   */
+  public static String makeManifest(JSmoothModelBean data, SkeletonBean skel) {
+    StringBuilder retVal = new StringBuilder();
+    String platform = "x86";
+    String shortName = skel.getShortName();
+    if (shortName.contains("64")) {
+      platform = "ia64";
+    }
+    retVal.append("<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>").append("\n");
+    retVal.append("<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\">").append("\n");
+    retVal.append("  <assemblyIdentity type=\"win32\"").append("\n");
+    retVal.append("    name=\"").append(shortName).append("\"").append("\n");
+    retVal.append("    version=\"1.0.0.0\"").append("\n");
+    retVal.append("    processorArchitecture=\"").append(platform).append("\"").append("\n");
+    retVal.append("  />").append("\n");
+    retVal.append("  <dependency>").append("\n");
+    retVal.append("    <dependentAssembly>").append("\n");
+    retVal
+        .append(
+            "      <assemblyIdentity type=\"win32\" name=\"Microsoft.Windows.Common-Controls\" version=\"6.0.0.0\" processorArchitecture=\"*\" publicKeyToken=\"6595b64144ccf1df\" language=\"*\"/>")
+        .append("\n");
+    retVal.append("    </dependentAssembly>").append("\n");
+    retVal.append("  </dependency>").append("\n");
+    if (data.isUacRequireAdmin()) {
+      retVal.append("  <trustInfo xmlns=\"urn:schemas-microsoft-com:asm.v2\">").append("\n");
+      retVal.append("    <security>").append("\n");
+      retVal.append("      <requestedPrivileges>").append("\n");
+      retVal.append("        <requestedExecutionLevel level=\"requireAdministrator\"/>").append("\n");
+      retVal.append("      </requestedPrivileges>").append("\n");
+      retVal.append("    </security>").append("\n");
+      retVal.append("  </trustInfo>").append("\n");
+    }
+    retVal.append("</assembly>");
+    return retVal.toString();
+  }
+
+  /**
+   * Converts a path relative to previousbasedir into a path 
+   * relative to newbasedir.
+   */
+  static public String getRenormalizedPathIfNeeded(String value, File previousbasedir, File newbasedir) {
+    // File f = new File(value);
+    // if (f.isAbsolute())
+    // return value;
+
+        if (newbasedir == null)
+            return value;
+
+        if (value == null)
+            return "";
+
+        File abs = new File(previousbasedir, value).getAbsoluteFile();
+        File n = JSmoothModelPersistency.makePathRelativeIfPossible(newbasedir, abs);
+
+        return n.toString();
+    }
+
+    static public String escapeString(String str) {
+        if (str == null)
+            return "";
+
+        StringBuffer out = new StringBuffer();
+        for (int i = 0; i < str.length(); i++) {
+      char c = str.charAt(i);
+      switch (c) {
+        case '\n':
+          out.append("\\n");
+          break;
+        case '\t':
+          out.append("\\t");
+          break;
+        case '\r':
+          out.append("\\r");
+          break;
+        case '\\':
+          out.append("\\\\");
+          break;
+        default:
+          out.append(c);
+      }
+    }
+    return out.toString();
+  }
+
+  static private void addPair(String name, String value, StringBuffer out) {
+    out.append(escapeString(name));
+    out.append("=");
+    out.append(escapeString(value));
+    out.append("\n");
+  }
+
+    static public String makePathConc(String[] elements) {
+        StringBuffer buf = new StringBuffer();
+        if (elements != null)
+            for (int i = 0; i < elements.length; i++) {
+                buf.append(elements[i]);
+                if ((i + 1) < elements.length)
+                    buf.append(";");
+            }
+        return buf.toString();
+  }
+
+}
diff --git a/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/application/gui/editors/ExecutableIcon.java b/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/application/gui/editors/ExecutableIcon.java
new file mode 100644
index 0000000..e7940dd
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/application/gui/editors/ExecutableIcon.java
@@ -0,0 +1,159 @@
+/*
+  JSmooth: a VM wrapper toolkit for Windows
+  Copyright (C) 2003 Rodrigo Reyes <reyes at charabia.net>
+ 
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+ 
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+ 
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ 
+ */
+
+package net.charabia.jsmoothgen.application.gui.editors;
+
+import net.charabia.jsmoothgen.skeleton.*;
+import net.charabia.jsmoothgen.application.*;
+import net.charabia.jsmoothgen.application.gui.*;
+import net.charabia.jsmoothgen.application.gui.util.*;
+import javax.swing.*;
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.util.*;
+import java.io.*;
+import com.l2fprod.common.swing.*;
+import com.l2fprod.common.propertysheet.*;
+
+public class ExecutableIcon extends Editor
+{
+    private FileSelectionTextField m_selector = new FileSelectionTextField();
+    private JLabel m_iconDisplay = new JLabel("(no image)");
+
+    public ExecutableIcon()
+    {
+	setLayout(new BorderLayout());
+	add(BorderLayout.CENTER, m_selector);
+	add(BorderLayout.SOUTH, m_iconDisplay);
+
+	m_iconDisplay.setHorizontalAlignment(JLabel.CENTER);
+
+	m_selector.addListener(new FileSelectionTextField.FileSelected() {
+		public void fileSelected(String filename)
+		{
+// 		    System.out.println("new icon: " + filename);
+		    setIconLocation(new File(filename));
+		}
+	    });
+    }
+
+    public void dataChanged()
+    {
+	if (getBaseDir() != null)
+	    m_selector.setBaseDir(getBaseDir());
+
+	if (m_model.getIconLocation() != null)
+	    {
+		m_selector.setFile(getAbsolutePath(new java.io.File(m_model.getIconLocation())));
+		setIconLocation(getAbsolutePath(new java.io.File(m_model.getIconLocation())));
+		
+	    }
+	else
+	    {
+		m_selector.setFile(null);
+		setIconLocation(new File(""));
+	    }
+    }
+
+    public void updateModel()
+    {
+	File f = m_selector.getFile();
+	if (f != null)
+	    m_model.setIconLocation(m_selector.getFile().toString());
+	else
+	    m_model.setIconLocation(null);
+    }
+
+    public String getLabel()
+    {
+	return "ICONLOCATION_LABEL";
+    }
+
+    public String getDescription()
+    {
+	return "ICONLOCATION_HELP";
+    }
+
+    private void setIconLocation(File iconfile)
+    {
+	if (iconfile.isAbsolute() == false)
+	    {
+		iconfile = new File(m_basedir, iconfile.toString());
+	    }
+	ImageIcon icon = null;
+	
+// 	System.out.println("setIconLocation: " + iconfile);
+
+	if (iconfile.toString().toUpperCase().endsWith(".ICO"))
+	    {
+		//
+		// Try to load with our ico codec...
+		//
+		try {
+		    java.awt.image.BufferedImage image = net.charabia.util.codec.IcoCodec.getPreferredImage(iconfile);
+		    if (image != null)
+			{
+			    icon = new ImageIcon(image);
+			}
+		} catch (java.io.IOException exc)
+		    {
+			exc.printStackTrace();
+		    }
+	    }
+	else   // Otherwise try with the standard toolkit functions...
+	    {
+            BufferedImage bufferedImage;
+            try {
+                bufferedImage = ImageIO.read(iconfile);
+                icon = new javax.swing.ImageIcon(bufferedImage, "default icon");
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+
+	    }
+
+
+	if (icon != null)
+	    {
+		int width = icon.getIconWidth();
+		int height = icon.getIconHeight();
+
+		m_iconDisplay.setIcon(icon);
+		m_iconDisplay.setText("");
+		m_model.setIconLocation(iconfile.getAbsolutePath());
+		this.validate();
+		this.invalidate();
+	    }
+	else
+	    {
+		m_iconDisplay.setIcon(null);
+		m_iconDisplay.setText("(no image)");
+		m_model.setIconLocation(null);
+	    }
+
+	doLayout();
+	invalidate();
+	validate();
+	repaint();
+    }
+
+    
+}
diff --git a/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/application/gui/util/HTMLPane.java b/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/application/gui/util/HTMLPane.java
new file mode 100644
index 0000000..0129c4b
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/application/gui/util/HTMLPane.java
@@ -0,0 +1,126 @@
+/*
+  JSmooth: a VM wrapper toolkit for Windows
+  Copyright (C) 2003 Rodrigo Reyes <reyes at charabia.net>
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ */
+
+package net.charabia.jsmoothgen.application.gui.util;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.border.*;
+import java.io.*;
+import java.util.*;
+import javax.swing.text.html.*;
+import javax.swing.event.*;
+import java.net.*;
+
+/**
+ * 
+ */
+
+public class HTMLPane extends JPanel
+{
+    private JScrollPane m_scroller;
+  private JEditorPane m_html;
+  private URL m_baseurl;
+
+  edu.stanford.ejalbert.BrowserLauncher m_launcher;
+
+  class Hyperactive implements HyperlinkListener {
+
+    public void hyperlinkUpdate(HyperlinkEvent e) {
+      if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
+
+        JEditorPane pane = (JEditorPane)e.getSource();
+        if (e instanceof HTMLFrameHyperlinkEvent) {
+          HTMLFrameHyperlinkEvent evt = (HTMLFrameHyperlinkEvent)e;
+          HTMLDocument doc = (HTMLDocument)pane.getDocument();
+          doc.processHTMLFrameHyperlinkEvent(evt);
+        } else {
+		    try {
+			URL nurl = e.getURL();
+			if (nurl == null)
+			    nurl = new URL(m_baseurl, e.getDescription());
+			if (jsmooth.Native.isAvailable())
+			    {
+              jsmooth.Native.shellExecute(jsmooth.Native.SHELLEXECUTE_OPEN, nurl.toString(), null, null, jsmooth.Native.SW_NORMAL);
+			    }
+			else
+			    m_launcher.openURLinBrowser(nurl.toExternalForm());
+ 		    } catch (Throwable t) {
+			t.printStackTrace();
+		    }
+		}
+	    }
+	}
+    }
+
+
+    public HTMLPane()
+    {
+	try {
+      m_baseurl = new File(".").toURI().toURL();
+	} catch (Exception ex) { ex.printStackTrace(); }
+	m_html = new JEditorPane("text/html","<html></html>") {
+		public boolean getScrollableTracksViewportWidth()
+		{
+		    return true;
+		}
+    };
+    HTMLEditorKit hek = new HTMLEditorKit();
+    m_html.setEditorKit(hek);
+
+    m_scroller = new JScrollPane(m_html);
+    setLayout(new BorderLayout());
+    m_html.setEditable(false);
+    add(m_scroller, BorderLayout.CENTER);
+    // add(m_html, BorderLayout.CENTER);
+    m_html.addHyperlinkListener(new Hyperactive());
+
+	try {
+	    m_launcher = new edu.stanford.ejalbert.BrowserLauncher();
+	}catch (Exception ex)
+	    {
+		ex.printStackTrace();
+	    }
+
+    }
+
+    public java.awt.Dimension getPreferredSize()
+    {
+	return new java.awt.Dimension(200,200);
+    }
+
+    public void setPage(URL url)
+    {
+	try {
+	    URL u = new URL(m_baseurl, url.toExternalForm());
+	    m_html.setPage(u);
+	} catch (Exception ex) { ex.printStackTrace(); }
+    }
+
+    public void setText(String s)
+    {
+	m_html.setContentType("text/html");
+	m_html.setText(s);
+	m_html.setCaretPosition(0);
+  }
+
+    
+}
diff --git a/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/pe/PEFile.java b/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/pe/PEFile.java
new file mode 100644
index 0000000..e4bb992
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/pe/PEFile.java
@@ -0,0 +1,478 @@
+/*
+  JSmooth: a VM wrapper toolkit for Windows
+  Copyright (C) 2003 Rodrigo Reyes <reyes at charabia.net>
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ */
+
+/*
+ * PEFile.java
+ *
+ * Created on 28 juillet 2003, 21:28
+ */
+
+package net.charabia.jsmoothgen.pe;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.Vector;
+
+import net.charabia.jsmoothgen.pe.res.ResIcon;
+import net.charabia.jsmoothgen.pe.res.ResIconDir;
+
+/**
+ * @author Rodrigo
+ */
+public class PEFile {
+  private File m_file;
+  private FileInputStream m_in = null;
+  private FileChannel m_channel = null;
+
+  private PEOldMSHeader m_oldmsheader;
+  private PEHeader m_header;
+  private Vector m_sections = new Vector();
+
+  private PEResourceDirectory m_resourceDir;
+
+    /**
+     * Creates a new instance of PEFile
+     */
+    public PEFile(File f) {
+    m_file = f;
+  }
+
+  public void close() throws IOException {
+    m_in.close();
+  }
+
+  public void open() throws FileNotFoundException, IOException {
+    m_in = new FileInputStream(m_file);
+    m_channel = m_in.getChannel();
+
+    m_oldmsheader = new PEOldMSHeader(this);
+
+    m_oldmsheader.read();
+    // m_oldmsheader.dump(System.out);
+    long headoffset = m_oldmsheader.e_lfanew;
+
+    m_header = new PEHeader(this, headoffset);
+    m_header.read();
+    // m_header.dump(System.out);
+
+    int seccount = m_header.NumberOfSections;
+    // System.out.println("LOADING " + seccount + " sections...");
+
+    long offset = headoffset + (m_header.NumberOfRvaAndSizes * 8) + 24 + getPeHeaderOffset();
+
+    for (int i = 0; i < seccount; i++) {
+      // System.out.println("Offset: " + offset + " (" + this.m_channel.position());
+
+      PESection sect = new PESection(this, offset);
+      sect.read();
+      // sect.dump(System.out);
+      m_sections.add(sect);
+      offset += 40;
+    }
+    // System.out.println("After sections: " + this.m_channel.position() + " (" + offset + ")");
+
+    ByteBuffer resbuf = null;
+    long resourceoffset = m_header.ResourceDirectory_VA;
+    for (int i = 0; i < seccount; i++) {
+      PESection sect = (PESection)m_sections.get(i);
+      if (sect.VirtualAddress == resourceoffset) {
+        // System.out.println("  Resource section found: " + resourceoffset);
+        PEResourceDirectory prd = new PEResourceDirectory(this, sect);
+        resbuf = prd.buildResource(sect.VirtualAddress);
+        break;
+      }
+    }
+  }
+
+  private int getPeHeaderOffset() {
+    int pe32Offset = 96;
+    if (m_header.isPe32Plus()) {
+      // It is a pe32+ header (x64)
+      pe32Offset = 112;
+    }
+    return pe32Offset;
+  }
+
+  public FileChannel getChannel() {
+    return m_channel;
+  }
+
+  public static void main(String args[]) throws IOException, CloneNotSupportedException, Exception {
+    // (no)PEFile pe = new PEFile(new File("F:/Program Files/LAN Search PRO/lansearch.exe"));
+
+    PEFile pe = new PEFile(new File("c:/scratch/rc3.exe"));
+    // PEFile pe = new PEFile(new File("c:/projects/jwrap/Copie.exe"));
+    // PEFile pe = new PEFile(new File("c:/projects/jwrap/test.exe"));
+    // PEFile pe = new PEFile(new File("F:/Program Files/bQuery/bQuery.exe"));
+    // PEFile pe = new PEFile(new File("F:/Program Files/Server Query/query.exe"));
+    // PEFile pe = new PEFile(new File("F:/Program Files/AvRack/rtlrack.exe"));
+    pe.open();
+    System.out.println("OldMSHeader");
+    pe.m_oldmsheader.dump(System.out);
+    System.out.println("COFFHeader");
+    pe.m_header.dump(System.out);
+
+    // System.out.println("===============\nADDING A RES");
+    // File fout = new File("F:/Documents and Settings/Rodrigo/Mes documents/projects/jsmooth/skeletons/simplewrap/gen-application.jar");
+    // FileInputStream fis = new FileInputStream(fout);
+    //	
+    // ByteBuffer data = ByteBuffer.allocate((int)fout.length());
+    // data.order(ByteOrder.LITTLE_ENDIAN);
+    // FileChannel fischan = fis.getChannel();
+    // fischan.read(data);
+    // data.position(0);
+    // fis.close();
+
+    PEResourceDirectory resdir = pe.getResourceDirectory();
+    System.out.println("ResourceDirectory");
+    resdir.dump(System.out);
+
+    // DataEntry inputResData = resdir.getData("JAVA", "#" + String.valueOf(103), "#" + String.valueOf(1033));
+    // ByteBuffer inputResDataBuffer = ByteBuffer.allocate(inputResData.diskSize() + 1024);
+    // inputResDataBuffer.order(ByteOrder.LITTLE_ENDIAN);
+    // inputResData.buildBuffer(inputResDataBuffer, 0, 0);
+    // int inputResDataBufferSize = inputResDataBuffer.position();
+    // inputResDataBuffer.flip();
+    // int offset = inputResDataBuffer.getInt();
+    // inputResDataBuffer.position(offset);
+    // StringBuilder inputResDataString = new StringBuilder(inputResDataBufferSize);
+    // while (inputResDataBuffer.position() <= inputResDataBufferSize - 2) {
+    // byte dummyByte = inputResDataBuffer.get();
+    // inputResDataString.append((char)dummyByte);
+    // }
+    // // Modify the data...
+    // String newInputResDataString = inputResDataString.toString();
+    // newInputResDataString = newInputResDataString.replace("samplejar", "ThisIsMyJarAndOnlyMine");
+    //
+    // inputResDataBuffer = ByteBuffer.allocate(newInputResDataString.length() + 2);
+    // for (int index = 0; index < newInputResDataString.length(); index++) { // C- do not change because buffer can be modified during loop
+    // inputResDataBuffer.put((byte)newInputResDataString.charAt(index));
+    // }
+    // inputResDataBuffer.put((byte)0);
+    // inputResDataBuffer.put((byte)0);
+    // inputResDataBuffer.position(0);
+    //
+    // boolean resb = resdir.replaceResource("JAVA", 102, 1033, inputResDataBuffer);
+
+    // PEResourceDirectory.DataEntry entry = resdir.getData("#14", "A", "#1033");
+    // entry.Data.position(0);
+    // System.out.println("DataEntry found : " + entry + " (size=" + entry.Data.remaining() + ")");
+    // entry.Data.position(0);
+    //	
+    // ResIconDir rid = new ResIconDir(entry.Data);
+    // System.out.println("ResIconDir :");
+    // System.out.println(rid.toString());
+    // int iconid = rid.getEntries()[0].dwImageOffset;
+    // System.out.println("Icon Index: " + iconid);
+    //	
+    // PEResourceDirectory.DataEntry iconentry = resdir.getData("#3", "#"+iconid, "#1033");
+    // iconentry.Data.position(0);
+    // ResIcon icon = new ResIcon(iconentry.Data);
+    // System.out.println("Icon :");
+    // System.out.println(icon.toString());
+
+    // java.awt.Image img = java.awt.Toolkit.getDefaultToolkit().getImage ("c:\\test.gif");
+    // java.awt.Image img = java.awt.Toolkit.getDefaultToolkit().getImage ("c:\\gnome-day2.png");
+    // java.awt.Image img = java.awt.Toolkit.getDefaultToolkit().getImage("c:\\gnome-color-browser2.png");
+    //
+    // java.awt.MediaTracker mt = new java.awt.MediaTracker(new javax.swing.JLabel("toto"));
+    // mt.addImage(img, 1);
+    // try {
+    // mt.waitForAll();
+    // } catch (Exception exc) {
+    // exc.printStackTrace();
+    // }
+    //
+    // ResIcon newicon = new ResIcon(img);
+    //
+    // pe.replaceDefaultIcon(newicon);
+
+    // System.out.println("-----------------\nNEW ICON:");
+    // System.out.println(newicon.toString());
+    //	
+    // rid.getEntries()[0].bWidth = (short)newicon.Width;
+    // rid.getEntries()[0].bHeight = (short)(newicon.Height/2);
+    // rid.getEntries()[0].bColorCount = (short)(1 <<newicon.BitsPerPixel);
+    // rid.getEntries()[0].wBitCount = newicon.BitsPerPixel;
+    // rid.getEntries()[0].dwBytesInRes = newicon.getData().remaining();
+    //	
+    // iconentry.Data = newicon.getData();
+    // iconentry.Size = iconentry.Data.remaining();
+    //
+    // entry.setData(rid.getData());
+    // System.out.println("POST CHANGE ResIconDir :");
+    // System.out.println(rid.toString());
+
+    // ResIcon test = new ResIcon(icon.getData());
+    // System.out.println("PROOF-TEST:\n" + test.toString());
+
+    // / BACK
+    //	
+    // rid.getEntries()[0].bWidth = (short)icon.Width;
+    // rid.getEntries()[0].bHeight = (short)(icon.Height/2);
+    // rid.getEntries()[0].bColorCount = (short)(1 <<icon.BitsPerPixel);
+    // rid.getEntries()[0].wBitCount = icon.BitsPerPixel;
+    // iconentry.Data = icon.getData();
+    // iconentry.Size = iconentry.Data.remaining();
+
+    // resdir.addNewResource("POUET", "A666", "#1033", data);
+
+    // resdir.dump(System.out);
+
+    // System.out.println("New size = " + resdir.size());
+    File out = new File("c:/scratch/COPIE.exe");
+    pe.dumpTo(out);
+
+  }
+
+  public PEResourceDirectory getResourceDirectory() throws IOException {
+        if (m_resourceDir != null)
+            return m_resourceDir;
+
+    long resourceoffset = m_header.ResourceDirectory_VA;
+    for (int i = 0; i < m_sections.size(); i++) {
+      PESection sect = (PESection)m_sections.get(i);
+      if (sect.VirtualAddress == resourceoffset) {
+        m_resourceDir = new PEResourceDirectory(this, sect);
+        return m_resourceDir;
+      }
+    }
+
+    return null;
+  }
+
+  public void dumpTo(File destination) throws IOException, CloneNotSupportedException {
+    int outputcount = 0;
+    FileOutputStream fos = new FileOutputStream(destination);
+    FileChannel out = fos.getChannel();
+
+    //
+    // Make a copy of the Header, for safe modifications
+    //
+    PEOldMSHeader oldmsheader = (PEOldMSHeader)this.m_oldmsheader.clone();
+    PEHeader peheader = (PEHeader)m_header.clone();
+    Vector sections = new Vector();
+    for (int i = 0; i < m_sections.size(); i++) {
+      PESection sect = (PESection)m_sections.get(i);
+      PESection cs = (PESection)sect.clone();
+      sections.add(cs);
+    }
+
+    //
+    // First, write the old MS Header, the one starting
+    // with "MZ"...
+    //
+    long newexeoffset = oldmsheader.e_lfanew;
+    ByteBuffer msheadbuffer = oldmsheader.get();
+    outputcount = out.write(msheadbuffer);
+    this.m_channel.position(64);
+    out.transferFrom(this.m_channel, 64, newexeoffset - 64);
+
+
+    //
+    // Then Write the new Header...
+    //
+    ByteBuffer headbuffer = peheader.get();
+    out.position(newexeoffset);
+    outputcount = out.write(headbuffer);
+
+    //
+    // After the header, there are all the section
+    // headers...
+    //
+    long offset = oldmsheader.e_lfanew + (m_header.NumberOfRvaAndSizes * 8) + 24 + getPeHeaderOffset();
+    out.position(offset);
+    for (int i = 0; i < sections.size(); i++) {
+      // System.out.println("  offset: " + out.position());
+      PESection sect = (PESection)sections.get(i);
+
+      ByteBuffer buf = sect.get();
+      outputcount = out.write(buf);
+    }
+
+    //
+    // Now, we write the real data: each of the section
+    // and their data...
+    //
+
+    // Not sure why it's always at 1024... ?
+    offset = 1024;
+
+    //
+    // Dump each section data
+    //
+
+    long virtualAddress = offset;
+    if ((virtualAddress % peheader.SectionAlignment) > 0)
+      virtualAddress += peheader.SectionAlignment - (virtualAddress % peheader.SectionAlignment);
+
+    long resourceoffset = m_header.ResourceDirectory_VA;
+    for (int i = 0; i < sections.size(); i++) {
+      PESection sect = (PESection)sections.get(i);
+      if (resourceoffset == sect.VirtualAddress) {
+                //			System.out.println("Dumping RES section " + i + " at " + offset + " from " + sect.PointerToRawData + " (VA=" + virtualAddress + ")");
+        out.position(offset);
+        long sectoffset = offset;
+        PEResourceDirectory prd = this.getResourceDirectory();
+        ByteBuffer resbuf = prd.buildResource(sect.VirtualAddress);
+        resbuf.position(0);
+
+        out.write(resbuf);
+        offset += resbuf.capacity();
+        long rem = offset % this.m_header.FileAlignment;
+                if (rem != 0)
+                    offset += this.m_header.FileAlignment - rem;
+
+        if (out.size() + 1 < offset) {
+          ByteBuffer padder = ByteBuffer.allocate(1);
+          out.write(padder, offset - 1);
+        }
+
+        long virtualSize = resbuf.capacity();
+        if ((virtualSize % peheader.SectionAlignment) > 0)
+          virtualSize += peheader.SectionAlignment - (virtualSize % peheader.SectionAlignment);
+
+        sect.PointerToRawData = sectoffset;
+        sect.SizeOfRawData = resbuf.capacity();
+        if ((sect.SizeOfRawData % this.m_header.FileAlignment) > 0)
+          sect.SizeOfRawData += (this.m_header.FileAlignment - (sect.SizeOfRawData % this.m_header.FileAlignment));
+        sect.VirtualAddress = virtualAddress;
+        sect.VirtualSize = virtualSize;
+        // System.out.println("  VS=" + virtualSize + " at VA=" + virtualAddress);
+        virtualAddress += virtualSize;
+
+      } else if (sect.PointerToRawData > 0) {
+                //			System.out.println("Dumping section " + i + "/" + sect.getName() + " at " + offset + " from " + sect.PointerToRawData + " (VA=" + virtualAddress + ")");
+        out.position(offset);
+        this.m_channel.position(sect.PointerToRawData);
+        long sectoffset = offset;
+
+        out.position(offset + sect.SizeOfRawData);
+        ByteBuffer padder = ByteBuffer.allocate(1);
+        out.write(padder, offset + sect.SizeOfRawData - 1);
+
+        long outted = out.transferFrom(this.m_channel, offset, sect.SizeOfRawData);
+        offset += sect.SizeOfRawData;
+        // System.out.println("offset before alignment, " + offset);
+
+        long rem = offset % this.m_header.FileAlignment;
+        if (rem != 0) {
+          offset += this.m_header.FileAlignment - rem;
+        }
+        // System.out.println("offset after alignment, " + offset);
+        
+        // long virtualSize = sect.SizeOfRawData;
+        // if ((virtualSize % peheader.SectionAlignment)>0)
+        // virtualSize += peheader.SectionAlignment - (virtualSize%peheader.SectionAlignment);
+
+        sect.PointerToRawData = sectoffset;
+        // sect.SizeOfRawData =
+        sect.VirtualAddress = virtualAddress;
+        // sect.VirtualSize = virtualSize;
+
+        virtualAddress += sect.VirtualSize;
+        if ((virtualAddress % peheader.SectionAlignment) > 0)
+          virtualAddress += peheader.SectionAlignment - (virtualAddress % peheader.SectionAlignment);
+
+      } else {
+        // generally a BSS, with a virtual size but no
+        // data in the file...
+        // System.out.println("Dumping section " + i + " at " + offset + " from " + sect.PointerToRawData + " (VA=" + virtualAddress + ")");
+        long virtualSize = sect.VirtualSize;
+        if ((virtualSize % peheader.SectionAlignment) > 0)
+          virtualSize += peheader.SectionAlignment - (virtualSize % peheader.SectionAlignment);
+
+        sect.VirtualAddress = virtualAddress;
+        // sect.VirtualSize = virtualSize;
+        virtualAddress += virtualSize;
+
+      }
+    }
+
+    // 
+    // Now that all the sections have been written, we have the
+    // correct VirtualAddress and Sizes, so we can update the new
+    // header and all the section headers...
+
+    peheader.updateVAAndSize(m_sections, sections);
+    headbuffer = peheader.get();
+    out.position(newexeoffset);
+    outputcount = out.write(headbuffer);
+
+    // peheader.dump(System.out);
+    // System.out.println("Dumping the section again...");
+    offset = oldmsheader.e_lfanew + (m_header.NumberOfRvaAndSizes * 8) + 24 + getPeHeaderOffset();
+    out.position(offset);
+    for (int i = 0; i < sections.size(); i++) {
+      // System.out.println("  offset: " + out.position());
+      PESection sect = (PESection)sections.get(i);
+      // sect.dump(System.out);
+      ByteBuffer buf = sect.get();
+      outputcount = out.write(buf);
+    }
+
+    fos.flush();
+    fos.close();
+  }
+
+  /*
+     */
+
+  public void replaceDefaultIcon(ResIcon icon) throws Exception {
+    PEResourceDirectory resdir = getResourceDirectory();
+
+    PEResourceDirectory.DataEntry entry = resdir.getData("#14", null, null);
+    if (entry == null) {
+      throw new Exception("Can't find any icon group in the file!");
+    }
+
+    entry.Data.position(0);
+    // System.out.println("DataEntry found : " + entry + " (size=" + entry.Data.remaining() + ")");
+    entry.Data.position(0);
+
+    ResIconDir rid = new ResIconDir(entry.Data);
+    // System.out.println("ResIconDir :");
+    // System.out.println(rid.toString());
+    int iconid = rid.getEntries()[0].dwImageOffset;
+    // System.out.println("Icon Index: " + iconid);
+
+    PEResourceDirectory.DataEntry iconentry = resdir.getData("#3", "#" + iconid, null);
+    iconentry.Data.position(0);
+    // System.out.println("Icon :");
+    // System.out.println(icon.toString());
+
+    rid.getEntries()[0].bWidth = (short)icon.Width;
+    rid.getEntries()[0].bHeight = (short)(icon.Height / 2);
+    rid.getEntries()[0].bColorCount = (short)(1 << icon.BitsPerPixel);
+    rid.getEntries()[0].wBitCount = icon.BitsPerPixel;
+    rid.getEntries()[0].dwBytesInRes = icon.getData().remaining();
+
+    iconentry.Data = icon.getData();
+    iconentry.Size = iconentry.Data.remaining();
+
+    entry.setData(rid.getData());
+  }
+
+}
diff --git a/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/pe/PEHeader.java b/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/pe/PEHeader.java
new file mode 100644
index 0000000..da96141
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/pe/PEHeader.java
@@ -0,0 +1,482 @@
+/*
+  JSmooth: a VM wrapper toolkit for Windows
+  Copyright (C) 2003 Rodrigo Reyes <reyes at charabia.net>
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ */
+
+/*
+ * PEHeader.java
+ *
+ * Created on 28 juillet 2003, 21:38
+ */
+
+package net.charabia.jsmoothgen.pe;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.FileChannel;
+import java.util.Vector;
+
+/**
+ * @author Rodrigo Reyes
+ */
+public class PEHeader implements Cloneable {
+  private int PeMagic; // 0
+  public int Machine; // 4
+  public int NumberOfSections; // 6
+  public long TimeDateStamp; // 8
+  public long PointerToSymbolTable; // C
+  public long NumberOfSymbols; // 10
+  public int SizeOfOptionalHeader; // 14
+  public int Characteristics; // 16
+
+  // Optional Header
+  public int Magic; // 18
+  public short MajorLinkerVersion; // 1a
+  public short MinorLinkerVersion; // 1b
+  public long SizeOfCode; // 1c
+  public long SizeOfInitializedData; // 20
+  public long SizeOfUninitializedData; // 24
+  public long AddressOfEntryPoint; // 28
+  public long BaseOfCode; // 2c
+  public long BaseOfData; // NT additional fields. 30
+  public long ImageBase; // 34
+  public long SectionAlignment; // 38
+  public long FileAlignment; // 3c
+  public int MajorOperatingSystemVersion; // 40
+  public int MinorOperatingSystemVersion; // 42
+  public int MajorImageVersion; // 44
+  public int MinorImageVersion; // 46
+  public int MajorSubsystemVersion; // 48
+  public int MinorSubsystemVersion; // 4a
+  public long Reserved1; // 4c
+  public long SizeOfImage; // 50
+  public long SizeOfHeaders; // 54
+  public long CheckSum; // 58
+  public int Subsystem; // 5c
+  public int DllCharacteristics; // 5e
+  public long SizeOfStackReserve; // 60
+  public long SizeOfStackCommit; // 64
+  public long SizeOfHeapReserve; // 68
+  public long SizeOfHeapCommit; // 6c
+  public long LoaderFlags; // 70
+  public long NumberOfRvaAndSizes; // 74
+
+  public long ExportDirectory_VA; // 78
+  public long ExportDirectory_Size; // 7c
+  public long ImportDirectory_VA; // 80
+  public long ImportDirectory_Size; // 84
+  public long ResourceDirectory_VA; // 88
+  public long ResourceDirectory_Size; // 8c
+  public long ExceptionDirectory_VA; // 90
+  public long ExceptionDirectory_Size; // 94
+  public long SecurityDirectory_VA; // 98
+  public long SecurityDirectory_Size; // 9c
+  public long BaseRelocationTable_VA; // a0
+  public long BaseRelocationTable_Size; // a4
+  public long DebugDirectory_VA; // a8
+  public long DebugDirectory_Size; // ac
+  public long ArchitectureSpecificData_VA; // b0
+  public long ArchitectureSpecificData_Size; // b4
+  public long RVAofGP_VA; // b8
+  public long RVAofGP_Size; // bc
+  public long TLSDirectory_VA; // c0
+  public long TLSDirectory_Size; // c4
+  public long LoadConfigurationDirectory_VA; // c8
+  public long LoadConfigurationDirectory_Size; // cc
+  public long BoundImportDirectoryinheaders_VA; // d0
+  public long BoundImportDirectoryinheaders_Size; // d4
+  public long ImportAddressTable_VA; // d8
+  public long ImportAddressTable_Size; // dc
+  public long DelayLoadImportDescriptors_VA; // e0
+  public long DelayLoadImportDescriptors_Size; // e4
+  public long COMRuntimedescriptor_VA; // e8
+  public long COMRuntimedescriptor_Size; // ec
+
+  private long m_baseoffset;
+  private PEFile m_pe;
+
+    /**
+     * Creates a new instance of PEHeader
+     */
+  public PEHeader(PEFile pef, long baseoffset) {
+    m_pe = pef;
+    m_baseoffset = baseoffset;
+  }
+
+  public Object clone() throws CloneNotSupportedException {
+    return super.clone();
+  }
+
+  public void read() throws IOException {
+    FileChannel ch = m_pe.getChannel();
+    ByteBuffer head = ByteBuffer.allocate(350);
+    head.order(ByteOrder.LITTLE_ENDIAN);
+    ch.position(m_baseoffset);
+    ch.read(head);
+    head.position(0);
+
+    PeMagic = head.getInt();
+    // System.out.println("MAGIC::: " + pemagic);
+    Machine = head.getShort(); // 4
+    NumberOfSections = head.getShort(); // 6
+    TimeDateStamp = head.getInt(); // 8
+    PointerToSymbolTable = head.getInt(); // C
+    NumberOfSymbols = head.getInt(); // 10
+    SizeOfOptionalHeader = head.getShort(); // 14
+    Characteristics = head.getShort(); // 16
+    // Optional Header
+
+    Magic = head.getShort(); // 18
+    MajorLinkerVersion = head.get(); // 1a
+    MinorLinkerVersion = head.get(); // 1b
+    SizeOfCode = head.getInt(); // 1c
+    SizeOfInitializedData = head.getInt(); // 20
+    SizeOfUninitializedData = head.getInt(); // 24
+    AddressOfEntryPoint = head.getInt(); // 28
+    BaseOfCode = head.getInt(); // 2c
+    if (isPe32Plus()) {
+      ImageBase = head.getLong(); // 34
+    } else {
+      BaseOfData = head.getInt(); // // NT additional fields. // 30
+      ImageBase = head.getInt(); // 34
+    }
+    SectionAlignment = head.getInt(); // 38
+    FileAlignment = head.getInt(); // 3c
+    MajorOperatingSystemVersion = head.getShort(); // 40
+    MinorOperatingSystemVersion = head.getShort(); // 42
+    MajorImageVersion = head.getShort(); // 44
+    MinorImageVersion = head.getShort(); // 46
+    MajorSubsystemVersion = head.getShort(); // 48
+    MinorSubsystemVersion = head.getShort(); // 4a
+    Reserved1 = head.getInt(); // 4c
+    SizeOfImage = head.getInt(); // 50
+    SizeOfHeaders = head.getInt(); // 54
+    CheckSum = head.getInt(); // 58
+    Subsystem = head.getShort(); // 5c
+    DllCharacteristics = head.getShort(); // 5e
+    if (isPe32Plus()) {
+      SizeOfStackReserve = head.getLong(); // 60
+      SizeOfStackCommit = head.getLong(); // 64
+      SizeOfHeapReserve = head.getLong(); // 68
+      SizeOfHeapCommit = head.getLong(); // 6c
+    } else {
+      SizeOfStackReserve = head.getInt(); // 60
+      SizeOfStackCommit = head.getInt(); // 64
+      SizeOfHeapReserve = head.getInt(); // 68
+      SizeOfHeapCommit = head.getInt(); // 6c
+    }
+    LoaderFlags = head.getInt(); // 70
+    NumberOfRvaAndSizes = head.getInt(); // 74
+
+    ExportDirectory_VA = head.getInt(); // 78
+    ExportDirectory_Size = head.getInt(); // 7c
+    ImportDirectory_VA = head.getInt(); // 80
+    ImportDirectory_Size = head.getInt(); // 84
+    ResourceDirectory_VA = head.getInt(); // 88
+    ResourceDirectory_Size = head.getInt(); // 8c
+    ExceptionDirectory_VA = head.getInt(); // 90
+    ExceptionDirectory_Size = head.getInt(); // 94
+    SecurityDirectory_VA = head.getInt(); // 98
+    SecurityDirectory_Size = head.getInt(); // 9c
+    BaseRelocationTable_VA = head.getInt(); // a0
+    BaseRelocationTable_Size = head.getInt(); // a4
+    DebugDirectory_VA = head.getInt(); // a8
+    DebugDirectory_Size = head.getInt(); // ac
+    ArchitectureSpecificData_VA = head.getInt(); // b0
+    ArchitectureSpecificData_Size = head.getInt(); // b4
+    RVAofGP_VA = head.getInt(); // b8
+    RVAofGP_Size = head.getInt(); // bc
+    TLSDirectory_VA = head.getInt(); // c0
+    TLSDirectory_Size = head.getInt(); // c4
+    LoadConfigurationDirectory_VA = head.getInt(); // c8
+    LoadConfigurationDirectory_Size = head.getInt(); // cc
+    BoundImportDirectoryinheaders_VA = head.getInt(); // d0
+    BoundImportDirectoryinheaders_Size = head.getInt(); // d4
+    ImportAddressTable_VA = head.getInt(); // d8
+    ImportAddressTable_Size = head.getInt(); // dc
+    DelayLoadImportDescriptors_VA = head.getInt(); // e0
+    DelayLoadImportDescriptors_Size = head.getInt(); // e4
+    COMRuntimedescriptor_VA = head.getInt(); // e8
+    COMRuntimedescriptor_Size = head.getInt(); // ec
+  }
+
+  public void dump(PrintStream out) {
+    out.println("HEADER:");
+    out.println("int  Machine=" + Machine + " //  4");
+    out.println("int  NumberOfSections=" + NumberOfSections + "     //  6");
+    out.println("long   TimeDateStamp=" + TimeDateStamp + " //  8");
+    out.println("long   PointerToSymbolTable=" + PointerToSymbolTable + "     //  C");
+    out.println("long   NumberOfSymbols=" + NumberOfSymbols + " // 10");
+    out.println("int  SizeOfOptionalHeader=" + SizeOfOptionalHeader + "     // 14");
+    out.println("int  Characteristics=" + Characteristics + " // 16");
+    // Optional Header
+
+    out.println("int    Magic=" + Magic + "     // 18");
+    out.println("short   MajorLinkerVersion=" + MajorLinkerVersion + "     // 1a");
+    out.println("short   MinorLinkerVersion=" + MinorLinkerVersion + " // 1b");
+    out.println("long   SizeOfCode=" + SizeOfCode + "     // 1c");
+    out.println("long   SizeOfInitializedData=" + SizeOfInitializedData + " // 20");
+    out.println("long   SizeOfUninitializedData=" + SizeOfUninitializedData + "     // 24");
+    out.println("long   AddressOfEntryPoint=" + AddressOfEntryPoint + " // 28");
+    out.println("long   BaseOfCode=" + BaseOfCode + "     // 2c");
+    out.println("long   BaseOfData=" + BaseOfData + "    //    // NT additional fields. // 30");
+    //    
+    out.println("long   ImageBase=" + ImageBase + "     // 34");
+    out.println("long   SectionAlignment=" + SectionAlignment + " // 38");
+    out.println("long   FileAlignment=" + FileAlignment + "     // 3c");
+    out.println("int    MajorOperatingSystemVersion=" + MajorOperatingSystemVersion + " // 40");
+    out.println("int    MinorOperatingSystemVersion=" + MinorOperatingSystemVersion + "     // 42");
+    out.println("int    MajorImageVersion=" + MajorImageVersion + " // 44");
+    out.println("int    MinorImageVersion=" + MinorImageVersion + "     // 46");
+    out.println("int    MajorSubsystemVersion=" + MajorSubsystemVersion + " // 48");
+    out.println("int    MinorSubsystemVersion=" + MinorSubsystemVersion + "     // 4a");
+    out.println("long   Reserved1=" + Reserved1 + "     // 4c");
+    out.println("long   SizeOfImage=" + SizeOfImage + " // 50");
+    out.println("long   SizeOfHeaders=" + SizeOfHeaders + "     // 54");
+    out.println("long   CheckSum=" + CheckSum + "     // 58");
+    out.println("int    Subsystem=" + Subsystem + " // 5c");
+    out.println("int    DllCharacteristics=" + DllCharacteristics + "     // 5e");
+    out.println("long   SizeOfStackReserve=" + SizeOfStackReserve + " // 60");
+    out.println("long   SizeOfStackCommit=" + SizeOfStackCommit + "     // 64");
+    out.println("long   SizeOfHeapReserve=" + SizeOfHeapReserve + " // 68");
+    out.println("long   SizeOfHeapCommit=" + SizeOfHeapCommit + "     // 6c");
+    out.println("long   LoaderFlags=" + LoaderFlags + " // 70");
+    out.println("long   NumberOfRvaAndSizes=" + NumberOfRvaAndSizes + " // 74");
+
+    out.println("long ExportDirectory_VA=" + ExportDirectory_VA + " // 78");
+    out.println("long ExportDirectory_Size=" + ExportDirectory_Size + " // 7c");
+    out.println("long ImportDirectory_VA=" + ImportDirectory_VA + " // 80");
+    out.println("long ImportDirectory_Size=" + ImportDirectory_Size + " // 84");
+    out.println("long ResourceDirectory_VA=" + ResourceDirectory_VA + " // 88");
+    out.println("long ResourceDirectory_Size=" + ResourceDirectory_Size + " // 8c");
+    out.println("long ExceptionDirectory_VA=" + ExceptionDirectory_VA + " // 90");
+    out.println("long ExceptionDirectory_Size=" + ExceptionDirectory_Size + " // 94");
+    out.println("long SecurityDirectory_VA=" + SecurityDirectory_VA + " // 98");
+    out.println("long SecurityDirectory_Size=" + SecurityDirectory_Size + " // 9c");
+    out.println("long BaseRelocationTable_VA=" + BaseRelocationTable_VA + " // a0");
+    out.println("long BaseRelocationTable_Size=" + BaseRelocationTable_Size + " // a4");
+    out.println("long DebugDirectory_VA=" + DebugDirectory_VA + " // a8");
+    out.println("long DebugDirectory_Size=" + DebugDirectory_Size + " // ac");
+    out.println("long ArchitectureSpecificData_VA=" + ArchitectureSpecificData_VA + " // b0");
+    out.println("long ArchitectureSpecificData_Size=" + ArchitectureSpecificData_Size + " // b4");
+    out.println("long RVAofGP_VA=" + RVAofGP_VA + " // b8");
+    out.println("long RVAofGP_Size=" + RVAofGP_Size + " // bc");
+    out.println("long TLSDirectory_VA=" + TLSDirectory_VA + " // c0");
+    out.println("long TLSDirectory_Size=" + TLSDirectory_Size + " // c4");
+    out.println("long LoadConfigurationDirectory_VA=" + LoadConfigurationDirectory_VA + " // c8");
+    out.println("long LoadConfigurationDirectory_Size=" + LoadConfigurationDirectory_Size + " // cc");
+    out.println("long BoundImportDirectoryinheaders_VA=" + BoundImportDirectoryinheaders_VA + " // d0");
+    out.println("long BoundImportDirectoryinheaders_Size=" + BoundImportDirectoryinheaders_Size + " // d4");
+    out.println("long ImportAddressTable_VA=" + ImportAddressTable_VA + " // d8");
+    out.println("long ImportAddressTable_Size=" + ImportAddressTable_Size + " // dc");
+    out.println("long DelayLoadImportDescriptors_VA=" + DelayLoadImportDescriptors_VA + " // e0");
+    out.println("long DelayLoadImportDescriptors_Size=" + DelayLoadImportDescriptors_Size + " // e4");
+    out.println("long COMRuntimedescriptor_VA=" + COMRuntimedescriptor_VA + " // e8");
+    out.println("long COMRuntimedescriptor_Size=" + COMRuntimedescriptor_Size + " // ec");
+  }
+
+  public ByteBuffer get() {
+    ByteBuffer head = ByteBuffer.allocate(16 + this.SizeOfOptionalHeader);
+    head.order(ByteOrder.LITTLE_ENDIAN);
+    head.position(0);
+
+    head.putInt(PeMagic);
+
+    head.putShort((short)Machine); // 4
+    head.putShort((short)NumberOfSections); // 6
+    head.putInt((int)TimeDateStamp); // 8
+    head.putInt((int)PointerToSymbolTable); // C
+    head.putInt((int)NumberOfSymbols); // 10
+    head.putShort((short)SizeOfOptionalHeader); // 14
+    head.putShort((short)Characteristics); // 16
+    // Optional Header
+
+    head.putShort((short)Magic); // 18
+    head.put((byte)MajorLinkerVersion); // 1a
+    head.put((byte)MinorLinkerVersion); // 1b
+    head.putInt((int)SizeOfCode); // 1c
+    head.putInt((int)SizeOfInitializedData); // 20
+    head.putInt((int)SizeOfUninitializedData); // 24
+    head.putInt((int)AddressOfEntryPoint); // 28
+    head.putInt((int)BaseOfCode); // 2c
+    if (isPe32Plus()) {
+      head.putLong(ImageBase); // 34
+    } else {
+      head.putInt((int)BaseOfData); // // NT additional fields. // 30
+      head.putInt((int)ImageBase); // 34
+    }
+    head.putInt((int)SectionAlignment); // 38
+    head.putInt((int)FileAlignment); // 3c
+    head.putShort((short)MajorOperatingSystemVersion); // 40
+    head.putShort((short)MinorOperatingSystemVersion); // 42
+    head.putShort((short)MajorImageVersion); // 44
+    head.putShort((short)MinorImageVersion); // 46
+    head.putShort((short)MajorSubsystemVersion); // 48
+    head.putShort((short)MinorSubsystemVersion); // 4a
+    head.putInt((int)Reserved1); // 4c
+    head.putInt((int)SizeOfImage); // 50
+    head.putInt((int)SizeOfHeaders); // 54
+    head.putInt((int)CheckSum); // 58
+    head.putShort((short)Subsystem); // 5c
+    head.putShort((short)DllCharacteristics); // 5e
+    if (isPe32Plus()) {
+      head.putLong(SizeOfStackReserve); // 60
+      head.putLong(SizeOfStackCommit); // 64
+      head.putLong(SizeOfHeapReserve); // 68
+      head.putLong(SizeOfHeapCommit); // 6c
+      } else {
+      head.putInt((int)SizeOfStackReserve); // 60
+      head.putInt((int)SizeOfStackCommit); // 64
+      head.putInt((int)SizeOfHeapReserve); // 68
+      head.putInt((int)SizeOfHeapCommit); // 6c    
+      }
+    head.putInt((int)LoaderFlags); // 70
+    head.putInt((int)NumberOfRvaAndSizes); // 74
+
+    head.putInt((int)ExportDirectory_VA); // 78
+    head.putInt((int)ExportDirectory_Size); // 7c
+    head.putInt((int)ImportDirectory_VA); // 80
+    head.putInt((int)ImportDirectory_Size); // 84
+    head.putInt((int)ResourceDirectory_VA); // 88
+    head.putInt((int)ResourceDirectory_Size); // 8c
+    head.putInt((int)ExceptionDirectory_VA); // 90
+    head.putInt((int)ExceptionDirectory_Size); // 94
+    head.putInt((int)SecurityDirectory_VA); // 98
+    head.putInt((int)SecurityDirectory_Size); // 9c
+    head.putInt((int)BaseRelocationTable_VA); // a0
+    head.putInt((int)BaseRelocationTable_Size); // a4
+    head.putInt((int)DebugDirectory_VA); // a8
+    head.putInt((int)DebugDirectory_Size); // ac
+    head.putInt((int)ArchitectureSpecificData_VA); // b0
+    head.putInt((int)ArchitectureSpecificData_Size); // b4
+    head.putInt((int)RVAofGP_VA); // b8
+    head.putInt((int)RVAofGP_Size); // bc
+    head.putInt((int)TLSDirectory_VA); // c0
+    head.putInt((int)TLSDirectory_Size); // c4
+    head.putInt((int)LoadConfigurationDirectory_VA); // c8
+    head.putInt((int)LoadConfigurationDirectory_Size); // cc
+    head.putInt((int)BoundImportDirectoryinheaders_VA); // d0
+    head.putInt((int)BoundImportDirectoryinheaders_Size); // d4
+    head.putInt((int)ImportAddressTable_VA); // d8
+    head.putInt((int)ImportAddressTable_Size); // dc
+    head.putInt((int)DelayLoadImportDescriptors_VA); // e0
+    head.putInt((int)DelayLoadImportDescriptors_Size); // e4
+    head.putInt((int)COMRuntimedescriptor_VA); // e8
+    head.putInt((int)COMRuntimedescriptor_Size); // ec
+
+    head.position(0);
+    return head;
+  }
+
+  public void updateVAAndSize(Vector oldsections, Vector newsections) {
+    long codebase = findNewVA(this.BaseOfCode, oldsections, newsections);
+    long codesize = findNewSize(this.BaseOfCode, oldsections, newsections);
+    // System.out.println("New BaseOfCode=" + codebase + " (size=" + codesize + ")");
+    this.BaseOfCode = codebase;
+    this.SizeOfCode = codesize;
+
+    this.AddressOfEntryPoint = findNewVA(this.AddressOfEntryPoint, oldsections, newsections);
+
+    long database = findNewVA(this.BaseOfData, oldsections, newsections);
+    long datasize = findNewSize(this.BaseOfData, oldsections, newsections);
+    // System.out.println("New BaseOfData=" + database + " (size=" + datasize + ")");
+    this.BaseOfData = database;
+
+    long imagesize = 0;
+    for (int i = 0; i < newsections.size(); i++) {
+      PESection sect = (PESection)newsections.get(i);
+      long curmax = sect.VirtualAddress + sect.VirtualSize;
+            if (curmax > imagesize)
+                imagesize = curmax;
+    }
+    this.SizeOfImage = imagesize;
+
+    // this.SizeOfInitializedData = datasize;
+
+    ExportDirectory_Size = findNewSize(ExportDirectory_VA, oldsections, newsections);
+    ExportDirectory_VA = findNewVA(ExportDirectory_VA, oldsections, newsections);
+    ImportDirectory_Size = findNewSize(ImportDirectory_VA, oldsections, newsections);
+    ImportDirectory_VA = findNewVA(ImportDirectory_VA, oldsections, newsections);
+    ResourceDirectory_Size = findNewSize(ResourceDirectory_VA, oldsections, newsections);
+    ResourceDirectory_VA = findNewVA(ResourceDirectory_VA, oldsections, newsections);
+    ExceptionDirectory_Size = findNewSize(ExceptionDirectory_VA, oldsections, newsections);
+    ExceptionDirectory_VA = findNewVA(ExceptionDirectory_VA, oldsections, newsections);
+    SecurityDirectory_Size = findNewSize(SecurityDirectory_VA, oldsections, newsections);
+    SecurityDirectory_VA = findNewVA(SecurityDirectory_VA, oldsections, newsections);
+    BaseRelocationTable_Size = findNewSize(BaseRelocationTable_VA, oldsections, newsections);
+    BaseRelocationTable_VA = findNewVA(BaseRelocationTable_VA, oldsections, newsections);
+    DebugDirectory_Size = findNewSize(DebugDirectory_VA, oldsections, newsections);
+    DebugDirectory_VA = findNewVA(DebugDirectory_VA, oldsections, newsections);
+    ArchitectureSpecificData_Size = findNewSize(ArchitectureSpecificData_VA, oldsections, newsections);
+    ArchitectureSpecificData_VA = findNewVA(ArchitectureSpecificData_VA, oldsections, newsections);
+    RVAofGP_Size = findNewSize(RVAofGP_VA, oldsections, newsections);
+    RVAofGP_VA = findNewVA(RVAofGP_VA, oldsections, newsections);
+    TLSDirectory_Size = findNewSize(TLSDirectory_VA, oldsections, newsections);
+    TLSDirectory_VA = findNewVA(TLSDirectory_VA, oldsections, newsections);
+    LoadConfigurationDirectory_Size = findNewSize(LoadConfigurationDirectory_VA, oldsections, newsections);
+    LoadConfigurationDirectory_VA = findNewVA(LoadConfigurationDirectory_VA, oldsections, newsections);
+    BoundImportDirectoryinheaders_Size = findNewSize(BoundImportDirectoryinheaders_VA, oldsections, newsections);
+    BoundImportDirectoryinheaders_VA = findNewVA(BoundImportDirectoryinheaders_VA, oldsections, newsections);
+    ImportAddressTable_Size = findNewSize(ImportAddressTable_VA, oldsections, newsections);
+    ImportAddressTable_VA = findNewVA(ImportAddressTable_VA, oldsections, newsections);
+    DelayLoadImportDescriptors_Size = findNewSize(DelayLoadImportDescriptors_VA, oldsections, newsections);
+    DelayLoadImportDescriptors_VA = findNewVA(DelayLoadImportDescriptors_VA, oldsections, newsections);
+    COMRuntimedescriptor_Size = findNewSize(COMRuntimedescriptor_VA, oldsections, newsections);
+    COMRuntimedescriptor_VA = findNewVA(COMRuntimedescriptor_VA, oldsections, newsections);
+  }
+
+  public boolean isPe32Plus() {
+    return Magic == 523;
+  }
+
+  private long findNewVA(long current, Vector oldsections, Vector newsections) {
+    for (int i = 0; i < oldsections.size(); i++) {
+      PESection sect = (PESection)oldsections.get(i);
+      if (sect.VirtualAddress == current) {
+        PESection newsect = (PESection)newsections.get(i);
+
+        // System.out.println("Translation VA found for " + current + " = " + i + " (" +newsect.VirtualAddress + ")=" + newsect.getName());
+        return newsect.VirtualAddress;
+      } else if ((current > sect.VirtualAddress) && (current < (sect.VirtualAddress + sect.VirtualSize))) {
+        long diff = current - sect.VirtualAddress;
+        PESection newsect = (PESection)newsections.get(i);
+                //			System.out.println("Translation VA found INSIDE " + current + " = " + i + " (" +newsect.VirtualAddress + ")=" + newsect.getName());
+        return newsect.VirtualAddress + diff;
+      }
+    }
+
+
+    return 0;
+  }
+
+  private long findNewSize(long current, Vector oldsections, Vector newsections) {
+    for (int i = 0; i < oldsections.size(); i++) {
+      PESection sect = (PESection)oldsections.get(i);
+      if (sect.VirtualAddress == current) {
+        PESection newsect = (PESection)newsections.get(i);
+                //			System.out.println("Translation Size found for " + current + " = " + i + " (" +newsect.VirtualAddress + ")=" + newsect.getName());
+        // System.out.println("         Old size " + sect.VirtualSize + " vs new size " + newsect.VirtualSize);
+        return newsect.VirtualSize;
+      }
+    }
+    return 0;
+  }
+
+}
diff --git a/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/pe/PEResourceDirectory.java b/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/pe/PEResourceDirectory.java
new file mode 100644
index 0000000..09676ad
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/pe/PEResourceDirectory.java
@@ -0,0 +1,625 @@
+/*
+  JSmooth: a VM wrapper toolkit for Windows
+  Copyright (C) 2003 Rodrigo Reyes <reyes at charabia.net>
+ 
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+ 
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+ 
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ 
+ */
+
+/*
+ * PEResourceDirectory.java
+ *
+ * Created on 2 aout 2003, 01:28
+ */
+
+package net.charabia.jsmoothgen.pe;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.FileChannel;
+import java.util.Iterator;
+import java.util.Vector;
+
+/**
+ * @author Rodrigo
+ */
+public class PEResourceDirectory {
+
+  /*
+     typedef struct IMAGE_RESOURCE_DIRECTORY {
+     uint32_t   Characteristics;
+     uint32_t   TimeDateStamp;
+     uint16_t    MajorVersion;
+     uint16_t    MinorVersion;
+     uint16_t    NumberOfNamedEntries;
+     uint16_t    NumberOfIdEntries;
+     }
+    */
+
+  public class DataEntry {
+    long OffsetToData; // To update at each change
+    long Size;
+    long CodePage; // never changed
+    long Reserved; // never changed
+    ByteBuffer Data;
+
+    public DataEntry(ByteBuffer data) {
+      this.Data = data;
+      this.Size = data.capacity();
+    }
+
+    public DataEntry(FileChannel chan, long offset) throws IOException {
+      long orgpos = chan.position();
+      chan.position(PEResourceDirectory.this.m_offsetBase + offset);
+      ByteBuffer buf = ByteBuffer.allocate(16);
+      buf.order(ByteOrder.LITTLE_ENDIAN);
+      chan.read(buf);
+      buf.position(0);
+
+      OffsetToData = buf.getInt();
+      Size = buf.getInt();
+      CodePage = buf.getInt();
+      Reserved = buf.getInt();
+
+      long datapos = PEResourceDirectory.this.m_master.PointerToRawData + (OffsetToData - PEResourceDirectory.this.m_master.VirtualAddress);
+      Data = ByteBuffer.allocate((int)Size);
+      Data.order(ByteOrder.LITTLE_ENDIAN);
+      chan.position(datapos);
+      chan.read(Data);
+      Data.position(0);
+
+      chan.position(orgpos);
+    }
+
+    public int diskSize() {
+      int size = 16 + (int)this.Size;
+            if ((size % 4) > 0)
+                size += 4 - (size % 4);
+      return size;
+    }
+
+    public void dump(PrintStream out, int level) {
+      indent(level, out);
+      out.println("OffsetToData=" + OffsetToData);
+      indent(level, out);
+      out.println("Size=" + Size);
+      indent(level, out);
+      out.println("CodePage=" + CodePage);
+      indent(level, out);
+      out.println("Reserved=" + Reserved);
+      indent(level, out);
+      out.print("Data={ ");
+      for (int i = 0; i < this.Data.capacity(); i++) {
+        out.print("" + Integer.toHexString((byte)Data.get()) + ",");
+      }
+      out.println(" }");
+
+    }
+
+    private void indent(int level, PrintStream out) {
+      for (int i = 0; i < level; i++)
+        out.print("    ");
+    }
+
+    public int buildBuffer(ByteBuffer buffer, long virtualBaseOffset, int dataOffset) {
+      // System.out.println("Building Data Entry buffer @ " + buffer.position() + " (" + dataOffset + ")");
+
+      dataOffset = buffer.position() + 16;
+
+      buffer.putInt((int)(dataOffset + virtualBaseOffset));
+      buffer.putInt((int)Size);
+      buffer.putInt((int)CodePage);
+      buffer.putInt((int)Reserved);
+
+      Data.position(0);
+      buffer.put(Data);
+
+      dataOffset += Size;
+            if ((dataOffset % 4) > 0)
+                dataOffset += (4 - (dataOffset % 4));
+
+      return dataOffset;
+    }
+
+    public void setData(ByteBuffer data) {
+      Data = data;
+      Size = data.capacity();
+    }
+  }
+
+  public class ResourceEntry {
+    int Id;
+    String Name;
+
+    ImageResourceDirectory Directory;
+    DataEntry Data;
+
+    public ResourceEntry(int id, DataEntry data) {
+      this.Id = id;
+      this.Data = data;
+    }
+
+    public ResourceEntry(String name, DataEntry data) {
+      this.Name = name;
+      this.Data = data;
+    }
+
+    public ResourceEntry(int id, ImageResourceDirectory dir) {
+      this.Id = id;
+      this.Directory = dir;
+    }
+
+    public ResourceEntry(String name, ImageResourceDirectory dir) {
+      this.Name = name;
+      this.Directory = dir;
+    }
+
+    public ResourceEntry(FileChannel chan) throws IOException {
+      // System.out.println("Resource Entry Offset: " + chan.position());
+      ByteBuffer buf = ByteBuffer.allocate(8);
+      buf.order(ByteOrder.LITTLE_ENDIAN);
+      chan.read(buf);
+      buf.position(0);
+      long orgchanpos = chan.position();
+      int val = buf.getInt();
+      long offsetToData = buf.getInt();
+      // System.out.println("Entry: Val=" + val);
+      // System.out.println("       Off=" + offsetToData);
+
+      if (val < 0) {
+        val &= 0x7FFFFFFF;
+        Name = extractStringAt(chan, val);
+        Id = -1;
+        // System.out.println("    String at " + val + " = " + Name);
+      } else {
+        Id = val;
+      }
+
+      if (offsetToData < 0) {
+        offsetToData &= 0x7FFFFFFF;
+        long orgpos = chan.position();
+        chan.position(PEResourceDirectory.this.m_offsetBase + offsetToData);
+        Directory = new PEResourceDirectory.ImageResourceDirectory(chan);
+        chan.position(orgpos);
+      } else {
+        Data = new DataEntry(chan, offsetToData);
+      }
+    }
+
+    public String extractStringAt(FileChannel chan, int offset) throws IOException {
+      long orgchanpos = chan.position();
+      chan.position(PEResourceDirectory.this.m_offsetBase + offset);
+
+      ByteBuffer sizebuf = ByteBuffer.allocate(2);
+      sizebuf.order(ByteOrder.LITTLE_ENDIAN);
+      chan.read(sizebuf);
+      sizebuf.position(0);
+
+      int size = sizebuf.getShort();
+      ByteBuffer buffer = ByteBuffer.allocate(size * 2);
+      buffer.order(ByteOrder.LITTLE_ENDIAN);
+      chan.read(buffer);
+      buffer.position(0);
+
+      StringBuffer buf = new StringBuffer(size);
+      for (int i = 0; i < size; i++) {
+        int c = buffer.getShort();
+        buf.append((char)c);
+      }
+
+      chan.position(orgchanpos);
+      return buf.toString();
+    }
+
+    public int diskSize() {
+            int size = 8;
+            if (Name != null)
+                size += (Name.length() * 2) + 2;
+
+            if (Directory != null)
+                size += Directory.diskSize();
+            else if (Data != null)
+                size += Data.diskSize();
+
+            if ((size % 4) > 0)
+                size += 4 - (size % 4);
+            return size;
+        }
+
+    public void dump(PrintStream out, int level) {
+      indent(level, out);
+      if (this.Name != null)
+        out.println("Name=" + Name);
+      else
+        out.println("Id=#" + Id);
+
+      indent(level, out);
+      if (this.Directory != null) {
+        out.println("ENTRY: DIRECTORY POINTER");
+        this.Directory.dump(out, level + 1);
+      } else {
+        out.println("ENTRY: DATA ENTRY");
+        Data.dump(out, level + 1);
+      }
+    }
+
+    private void indent(int level, PrintStream out) {
+      for (int i = 0; i < level; i++)
+        out.print("    ");
+    }
+
+    public int buildBuffer(ByteBuffer buffer, long virtualBaseOffset, int dataOffset) {
+      // System.out.println("Building Resource Entry buffer  " + Name + "/" + Id + " @ " + buffer.position() + " (" + dataOffset + ")");
+      if (Name != null) {
+        buffer.putInt((int)(dataOffset | 0x80000000));
+
+        int stringoffset = dataOffset;
+        ByteBuffer strbuf = ByteBuffer.allocate(Name.length() * 2 + 2);
+        strbuf.order(ByteOrder.LITTLE_ENDIAN);
+
+        strbuf.putShort((short)Name.length());
+        for (int i = 0; i < Name.length(); i++) {
+          strbuf.putShort((short)Name.charAt(i));
+        }
+        strbuf.position(0);
+
+        long oldpos = buffer.position();
+        buffer.position(dataOffset);
+        buffer.put(strbuf);
+                dataOffset += Name.length() * 2 + 2;
+                if ((dataOffset % 4) != 0)
+                    dataOffset += 4 - (dataOffset % 4);
+                buffer.position((int) oldpos);
+      } else {
+        buffer.putInt(Id);
+      }
+
+      if (Directory != null) {
+        buffer.putInt((int)(dataOffset | 0x80000000));
+
+        int oldpos = buffer.position();
+        buffer.position(dataOffset);
+        int dirsize = Directory.buildBuffer(buffer, virtualBaseOffset);
+        dataOffset = dirsize;
+        buffer.position(oldpos);
+
+      } else if (Data != null) {
+        buffer.putInt(dataOffset);
+        int oldpos = buffer.position();
+        buffer.position(dataOffset);
+        dataOffset = Data.buildBuffer(buffer, virtualBaseOffset, dataOffset);
+        buffer.position(oldpos);
+      } else {
+        throw new RuntimeException("Directory and Data are both null!");
+      }
+
+      return dataOffset;
+    }
+  }
+
+  public class ImageResourceDirectory {
+    long Characteristics; // uint32_t
+    long TimeDateStamp; // uint32_t
+    int MajorVersion; // uint16_t
+    int MinorVersion; // uint16_t
+    int NumberOfNamedEntries; // uint16_t
+    int NumberOfIdEntries; // uint16_t
+    Vector NamedEntries = new Vector();
+    Vector IdEntries = new Vector();
+
+    public ImageResourceDirectory() {
+    }
+
+    public ImageResourceDirectory(FileChannel chan) throws IOException {
+      ByteBuffer header = ByteBuffer.allocate(16);
+      header.order(ByteOrder.LITTLE_ENDIAN);
+      chan.read(header);
+      header.position(0);
+
+      Characteristics = header.getInt();
+      TimeDateStamp = header.getInt();
+      MajorVersion = header.getShort();
+      MinorVersion = header.getShort();
+      NumberOfNamedEntries = header.getShort();
+      NumberOfIdEntries = header.getShort();
+
+      for (int i = 0; i < NumberOfNamedEntries; i++) {
+        ResourceEntry re = new ResourceEntry(chan);
+        NamedEntries.add(re);
+      }
+      for (int i = 0; i < NumberOfIdEntries; i++) {
+        ResourceEntry re = new ResourceEntry(chan);
+        IdEntries.add(re);
+      }
+    }
+
+    public void addNamedEntry(ResourceEntry entry) {
+      this.NamedEntries.add(entry);
+    }
+
+    public void addIdEntry(ResourceEntry entry) {
+      this.IdEntries.add(entry);
+    }
+
+    public void addEntry(ResourceEntry entry) {
+      if (entry.Name != null)
+        addNamedEntry(entry);
+      else
+        addIdEntry(entry);
+    }
+
+    public void dump(PrintStream out, int level) {
+      indent(level, out);
+      out.println("Directory: ");
+      indent(level, out);
+      out.println("Characteristics=" + this.Characteristics);
+      indent(level, out);
+      out.println("TimeDateStamp=" + this.TimeDateStamp);
+      indent(level, out);
+      out.println("MajorVersion=" + this.MajorVersion);
+      indent(level, out);
+      out.println("MinorVersion=" + this.MinorVersion);
+      indent(level, out);
+      out.println("NumberOfNamedEntries=" + this.NumberOfNamedEntries);
+      indent(level, out);
+      out.println("NumberOfIdEntries=" + this.NumberOfIdEntries);
+      indent(level, out);
+      out.println("Named Entries:");
+      for (int i = 0; i < NumberOfNamedEntries; i++) {
+        ResourceEntry re = (ResourceEntry)NamedEntries.get(i);
+        re.dump(out, level + 1);
+      }
+      indent(level, out);
+      out.println("Id Entries:");
+      for (int i = 0; i < NumberOfIdEntries; i++) {
+        ResourceEntry re = (ResourceEntry)IdEntries.get(i);
+        re.dump(out, level + 1);
+      }
+    }
+
+    private void indent(int level, PrintStream out) {
+      for (int i = 0; i < level; i++)
+        out.print("    ");
+    }
+
+    public int diskSize() {
+      int size = 16;
+      for (int i = 0; i < this.NamedEntries.size(); i++) {
+        ResourceEntry re = (ResourceEntry)NamedEntries.get(i);
+        size += re.diskSize();
+      }
+      for (int i = 0; i < this.IdEntries.size(); i++) {
+        ResourceEntry re = (ResourceEntry)IdEntries.get(i);
+        size += re.diskSize();
+      }
+
+            if ((size % 4) > 0)
+                size += 4 - (size % 4);
+
+      return size;
+    }
+
+    public int buildBuffer(ByteBuffer buffer, long virtualBaseOffset) {
+      // System.out.println("Building Directory Entry buffer @ " + buffer.position());
+
+      buffer.putInt((int)this.Characteristics);
+      buffer.putInt((int)this.TimeDateStamp);
+      buffer.putShort((short)this.MajorVersion);
+      buffer.putShort((short)this.MinorVersion);
+      buffer.putShort((short)this.NamedEntries.size());
+      buffer.putShort((short)this.IdEntries.size());
+
+      int dataOffset = buffer.position() + (NamedEntries.size() * 8) + (IdEntries.size() * 8);
+
+      for (int i = 0; i < this.NamedEntries.size(); i++) {
+        ResourceEntry re = (ResourceEntry)this.NamedEntries.get(i);
+        dataOffset = re.buildBuffer(buffer, virtualBaseOffset, dataOffset);
+      }
+
+      for (int i = 0; i < this.IdEntries.size(); i++) {
+        ResourceEntry re = (ResourceEntry)this.IdEntries.get(i);
+        dataOffset = re.buildBuffer(buffer, virtualBaseOffset, dataOffset);
+      }
+
+      buffer.position(dataOffset);
+      return dataOffset;
+    }
+
+    public ResourceEntry getResourceEntry(String name) {
+      // If name == null, get the first entry in lexical
+      // order. If no entry in lexical order, choose the
+      // lowest integer id entry.
+      if (name == null) {
+        if (NamedEntries.size() > 0) {
+          return (PEResourceDirectory.ResourceEntry)NamedEntries.get(0);
+        }
+        if (IdEntries.size() > 0) {
+          return (PEResourceDirectory.ResourceEntry)IdEntries.get(0);
+        }
+        return null;
+      }
+
+      if ((name.length() > 0) && (name.charAt(0) == '#')) {
+        try {
+          String nb = name.substring(1);
+          int i = Integer.parseInt(nb);
+          return getResourceEntry(i);
+        } catch (Exception exc) {
+          exc.printStackTrace();
+        }
+      }
+
+      for (Iterator i = this.NamedEntries.iterator(); i.hasNext();) {
+        ResourceEntry re = (ResourceEntry)i.next();
+        if (name.equals(re.Name)) {
+          return re;
+        }
+      }
+      return null;
+    }
+
+    public ResourceEntry getResourceEntry(int id) {
+      for (Iterator i = this.IdEntries.iterator(); i.hasNext();) {
+        ResourceEntry re = (ResourceEntry)i.next();
+        if (id == re.Id) {
+          return re;
+        }
+      }
+      return null;
+    }
+
+  }
+
+  PESection m_master;
+  PEFile m_file;
+  long m_offsetBase;
+
+  PEResourceDirectory.ImageResourceDirectory m_root;
+
+    /**
+     * Creates a new instance of PEResourceDirectory
+     */
+    public PEResourceDirectory(PEFile file, PESection sect) throws IOException {
+        m_master = sect;
+    m_file = file;
+    m_offsetBase = sect.PointerToRawData;
+    init();
+
+    // System.out.println("--------\nTOTAL SIZE: " + m_root.diskSize());
+
+    // System.out.println("\n\n");
+  }
+
+  public void init() throws IOException {
+    // / System.out.println("RESOURCE INIT");
+    // System.out.println("   Offset: " + m_master.PointerToRawData);
+    FileChannel chan = m_file.getChannel();
+    chan.position(m_master.PointerToRawData);
+    PEResourceDirectory.ImageResourceDirectory dir = new PEResourceDirectory.ImageResourceDirectory(chan);
+    // System.out.println("-----------------\nDUMP\n---------------");
+    m_root = dir;
+
+    // dir.dump(System.out, 0);
+  }
+
+  public void dump(PrintStream out) {
+    m_root.dump(out, 0);
+  }
+
+  public int size() {
+    return m_root.diskSize();
+  }
+
+  public ByteBuffer buildResource(long virtualBaseOffset) {
+    // System.out.println("BUILDING RESOURCE / VIRTUAL: " + virtualBaseOffset);
+    int resourceSize = m_root.diskSize();
+    ByteBuffer resbuf = ByteBuffer.allocate(resourceSize);
+    resbuf.order(ByteOrder.LITTLE_ENDIAN);
+    resbuf.position(0);
+    m_root.buildBuffer(resbuf, virtualBaseOffset);
+    return resbuf;
+  }
+
+  public PEResourceDirectory.ImageResourceDirectory getRoot() {
+    return m_root;
+  }
+
+  public boolean replaceManifest(int resourceId, int langId, ByteBuffer data) {
+    ResourceEntry catEntry = m_root.getResourceEntry(24);
+    if ((catEntry != null) && (catEntry.Directory != null)) {
+      ResourceEntry identEntry = catEntry.Directory.getResourceEntry(resourceId);
+      if ((identEntry != null) && (identEntry.Directory != null)) {
+        ResourceEntry langEntry = identEntry.Directory.getResourceEntry(langId);
+        if ((langEntry != null) && (langEntry.Data != null)) {
+          DataEntry dataslot = langEntry.Data;
+          dataslot.setData(data);
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  public boolean replaceResource(String catId, int resourceId, int langId, ByteBuffer data) {
+    ResourceEntry catEntry = m_root.getResourceEntry(catId);
+    if ((catEntry != null) && (catEntry.Directory != null)) {
+      ResourceEntry identEntry = catEntry.Directory.getResourceEntry(resourceId);
+      if ((identEntry != null) && (identEntry.Directory != null)) {
+        ResourceEntry langEntry = identEntry.Directory.getResourceEntry(langId);
+        if ((langEntry != null) && (langEntry.Data != null)) {
+          DataEntry dataslot = langEntry.Data;
+          dataslot.setData(data);
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  public void addNewResource(String catId, String resourceId, String languageId, ByteBuffer data) {
+    DataEntry dataEntry = new DataEntry(data);
+    ResourceEntry languageEntry = buildResourceEntry(languageId, dataEntry);
+    ImageResourceDirectory languageDir = new ImageResourceDirectory();
+
+    languageDir.TimeDateStamp = 0x3F2CCF64;
+    languageDir.addEntry(languageEntry);
+
+    ResourceEntry identEntry = buildResourceEntry(resourceId, languageDir);
+
+    ImageResourceDirectory identDir = new ImageResourceDirectory();
+    identDir.TimeDateStamp = 0x3F2CCF64;
+    identDir.addEntry(identEntry);
+
+    ResourceEntry catEntry = buildResourceEntry(catId, identDir);
+    m_root.addEntry(catEntry);
+  }
+
+  public DataEntry getData(String catId, String resourceId, String langId) {
+    ResourceEntry catEntry = m_root.getResourceEntry(catId);
+    if ((catEntry != null) && (catEntry.Directory != null)) {
+      ResourceEntry identEntry = catEntry.Directory.getResourceEntry(resourceId);
+      if ((identEntry != null) && (identEntry.Directory != null)) {
+        ResourceEntry langEntry = identEntry.Directory.getResourceEntry(langId);
+        if ((langEntry != null) && (langEntry.Data != null)) {
+          DataEntry dataslot = langEntry.Data;
+          return dataslot;
+        }
+      }
+    }
+    return null;
+  }
+
+  public ResourceEntry buildResourceEntry(String id, DataEntry data) {
+    if ((id.length() > 1) && (id.charAt(0) == '#')) {
+      int intid = Integer.parseInt(id.substring(1));
+      return new ResourceEntry(intid, data);
+    }
+
+    return new ResourceEntry(id, data);
+  }
+
+
+  public ResourceEntry buildResourceEntry(String id, ImageResourceDirectory dir) {
+    if ((id.length() > 1) && (id.charAt(0) == '#')) {
+      int intid = Integer.parseInt(id.substring(1));
+      return new ResourceEntry(intid, dir);
+    }
+
+    return new ResourceEntry(id, dir);
+  }
+
+}
diff --git a/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/pe/PESection.java b/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/pe/PESection.java
new file mode 100644
index 0000000..bbe857b
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/pe/PESection.java
@@ -0,0 +1,133 @@
+/*
+  JSmooth: a VM wrapper toolkit for Windows
+  Copyright (C) 2003 Rodrigo Reyes <reyes at charabia.net>
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/*
+ * PESection.java
+ *
+ * Created on 29 juillet 2003, 21:34
+ */
+
+package net.charabia.jsmoothgen.pe;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.FileChannel;
+
+/**
+ * @author Rodrigo
+ */
+public class PESection implements Cloneable {
+    byte[] ANSI_Name; //  	Name of the Section. Can be anything (0)(8BYTES)
+    long VirtualSize; // 	The size of the section when it is mapped to memory. Must be a multiple of 4096. (8)(DWORD)
+    long VirtualAddress; // 	An rva to where it should be mapped in memory. (12)(DWORD)
+    long SizeOfRawData; // 	The size of the section in the PE file. Must be a multiple of 512 (16)(DWORD)
+    long PointerToRawData; // 	A file based offset which points to the location of this sections data (20)(DWORD)
+    long PointerToRelocations; // 	In EXE's this field is meaningless, and is set 0 (24)(DWORD)
+    long PointerToLinenumbers; // 	This is the file-based offset of the line number table. This field is only used for debug purposes, and is usualy set to 0 (28)(DWORD)
+    int NumberOfRelocations; // 	In EXE's this field is meaningless, and is set 0 (32)(WORD)
+    int NumberOfLinenumbers; // 	The number of line numbers in the line number table for this section. This field is only used for debug purposes, and is usualy set to 0 (34)(WORD)
+    long Characteristics; // 	The kind of data stored in this section ie. Code, Data, Import data, Relocation data (36)(DWORD)
+
+    private long m_baseoffset;
+    private PEFile m_pe;
+
+    /**
+     * Creates a new instance of PESection
+     */
+    public PESection(PEFile pef, long baseoffset) {
+        m_pe = pef;
+        m_baseoffset = baseoffset;
+    }
+
+    public Object clone() throws CloneNotSupportedException {
+        return super.clone();
+    }
+
+    public String getName() {
+        StringBuffer buffer = new StringBuffer();
+        for (int i = 0; i < 8; i++)
+            buffer.append((char) ANSI_Name[i]);
+        return buffer.toString();
+    }
+
+    public void read() throws IOException {
+        FileChannel ch = m_pe.getChannel();
+        ByteBuffer head = ByteBuffer.allocate(40);
+        head.order(ByteOrder.LITTLE_ENDIAN);
+        ch.position(m_baseoffset);
+        ch.read(head);
+        head.position(0);
+
+        ANSI_Name = new byte[8];
+        for (int i = 0; i < 8; i++)
+            ANSI_Name[i] = head.get();
+
+        VirtualSize = head.getInt();
+        VirtualAddress = head.getInt();
+        SizeOfRawData = head.getInt();
+        PointerToRawData = head.getInt();
+        PointerToRelocations = head.getInt();
+        PointerToLinenumbers = head.getInt();
+        NumberOfRelocations = head.getShort();
+        NumberOfLinenumbers = head.getShort();
+        Characteristics = head.getInt();
+    }
+
+    public void dump(PrintStream out) {
+        out.println("SECTION:");
+        out.println("  Name= "+getName());
+        out.println("  VirtualSize= " + VirtualSize + "  // 	The size of the section when it is mapped to memory. Must be a multiple of 4096. (8)(DWORD)");
+        out.println("  VirtualAddress= " + VirtualAddress + "   // 	An rva to where it should be mapped in memory. (12)(DWORD)");
+        out.println("  SizeOfRawData= " + SizeOfRawData + "   // 	The size of the section in the PE file. Must be a multiple of 512 (16)(DWORD)");
+        out.println("  PointerToRawData= " + PointerToRawData + "   // 	A file based offset which points to the location of this sections data (20)(DWORD)");
+        out.println("  PointerToRelocations= " + PointerToRelocations + "   // 	In EXE's this field is meaningless, and is set 0 (24)(DWORD)");
+        out.println("  PointerToLinenumbers= " + PointerToLinenumbers + "   // 	This is the file-based offset of the line number table. This field is only used for debug purposes, and is usualy set to 0 (28)(DWORD)");
+        out.println("  NumberOfRelocations= " + NumberOfRelocations + "   // 	In EXE's this field is meaningless, and is set 0 (32)(WORD)");
+        out.println("  NumberOfLinenumbers= " + NumberOfLinenumbers + "   // 	The number of line numbers in the line number table for this section. This field is only used for debug purposes, and is usualy set to 0 (34)(WORD)");
+        out.println("  Characteristics= " + Characteristics + "   // 	The kind of data stored in this section ie. Code, Data, Import data, Relocation data (36)(DWORD)");
+
+    }
+
+
+  public ByteBuffer get() {
+    ByteBuffer head = ByteBuffer.allocate(40);
+    head.order(ByteOrder.LITTLE_ENDIAN);
+    head.position(0);
+
+    for (int i = 0; i < 8; i++)
+      head.put((byte)ANSI_Name[i]);
+
+    head.putInt((int)VirtualSize);
+    head.putInt((int)VirtualAddress);
+    head.putInt((int)SizeOfRawData);
+    head.putInt((int)PointerToRawData);
+    head.putInt((int)PointerToRelocations);
+    head.putInt((int)PointerToLinenumbers);
+    head.putShort((short)NumberOfRelocations);
+    head.putShort((short)NumberOfLinenumbers);
+    head.putInt((int)Characteristics);
+
+    head.position(0);
+    return head;
+  }
+
+}
diff --git a/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/pe/res/ResIcon.java b/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/pe/res/ResIcon.java
new file mode 100644
index 0000000..b5d3dec
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/pe/res/ResIcon.java
@@ -0,0 +1,462 @@
+/*
+  JSmooth: a VM wrapper toolkit for Windows
+  Copyright (C) 2003 Rodrigo Reyes <reyes at charabia.net>
+ 
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+ 
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+ 
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ 
+ */
+
+/*
+ * ResIcon.java
+ *
+ * Created on 17 août 2003, 22:51
+ */
+
+package net.charabia.jsmoothgen.pe.res;
+
+import java.util.*;
+import java.io.*;
+import java.nio.*;
+import java.nio.channels.*;
+import java.awt.*;
+import java.awt.image.*;
+
+/**
+ * @see
+ */
+public class ResIcon
+{
+    public long Size;            /* Size of this header in bytes DWORD*/
+    public long  Width;           /* Image width in pixels LONG*/
+    public long  Height;          /* Image height in pixels LONG*/
+    public int  Planes;          /* Number of color planes WORD*/
+    public int  BitsPerPixel;    /* Number of bits per pixel WORD*/
+    /* Fields added for Windows 3.x follow this line */
+    public long Compression;     /* Compression methods used DWORD*/
+    public long SizeOfBitmap;    /* Size of bitmap in bytes DWORD*/
+    public long  HorzResolution;  /* Horizontal resolution in pixels per meter LONG*/
+    public long  VertResolution;  /* Vertical resolution in pixels per meter LONG*/
+    public long ColorsUsed;      /* Number of colors in the image DWORD*/
+    public long ColorsImportant; /* Minimum number of important colors DWORD*/
+	
+    public PaletteElement[] Palette;
+    public short[] BitmapXOR;
+    public short[] BitmapAND;
+	
+    public class PaletteElement
+    {
+	public int Blue;
+	public int Green;
+	public int Red;
+	public int Reserved;
+	public String toString()
+	{
+	    return "{"+Blue+","+Green+","+Red+","+Reserved+"}";
+	}
+    }
+	
+    public ResIcon(){};
+    /**
+     * Creates a new instance of ResIcon 
+     * @see
+     * @param in
+     */
+    public ResIcon(ByteBuffer in)
+    {
+	Size = in.getInt();
+	Width = in.getInt();
+	Height = in.getInt();
+	Planes = in.getShort();
+	BitsPerPixel = in.getShort();
+	Compression = in.getInt();
+	SizeOfBitmap = in.getInt();
+	HorzResolution = in.getInt();
+	VertResolution = in.getInt();
+	ColorsUsed = in.getInt();
+	ColorsImportant = in.getInt();
+		
+	int cols = (int)ColorsUsed;
+	if (cols == 0)
+	    cols = 1 << BitsPerPixel;
+		
+	Palette = new PaletteElement[(int)cols];
+	for (int i=0; i<Palette.length; i++)
+	    {
+		PaletteElement el = new PaletteElement();
+		el.Blue = in.get();
+		el.Green = in.get();
+		el.Red = in.get();
+		el.Reserved = in.get();
+		Palette[i] = el;
+	    }
+
+	// int xorbytes = (((int)Height/2) * (int)Width * (int)BitsPerPixel) / 8;
+	int xorbytes = (((int)Height/2) * (int)Width);
+	//		System.out.println("POSITION " + in.position() + " : xorbitmap = " + xorbytes + " bytes");
+		
+	BitmapXOR = new short[xorbytes];
+	for (int i=0; i<BitmapXOR.length; i++)
+	    {
+		switch(BitsPerPixel)
+		    {
+		    case 4:
+			{
+			    int pix = in.get();
+			    BitmapXOR[i] = (short)((pix >> 4) & 0x0F);
+			    i++;
+			    BitmapXOR[i] = (short)(pix & 0x0F);
+			}
+			break;
+		    case 8:
+			{
+			    BitmapXOR[i] = in.get();
+			}
+			break;
+		    }
+	    }
+		
+
+	int height = (int)(Height/2);
+	int rowsize = (int)Width / 8;
+	if ((rowsize%4)>0)
+	    {
+		rowsize += 4 - (rowsize%4);
+	    }
+		
+	//		System.out.println("POSITION " + in.position() + " : andbitmap = " + andbytes + " bytes");
+
+	int andbytes = height * rowsize;   // (((int)Height/2) * (int)Width) / 8;
+				
+	BitmapAND = new short[andbytes];
+	for (int i=0; i<BitmapAND.length; i++)
+	    {
+		BitmapAND[i] = in.get();
+	    }
+		
+    }
+	
+    /** Creates a new instance based on the data of the Image argument.
+     * @param img
+     */	
+    public ResIcon(Image img) throws Exception
+    {
+	int width = img.getWidth(null);
+	int height = img.getHeight(null);
+		
+	if ((width % 8) != 0)
+	    width += (7-(width%8));
+		
+	if ((height % 8) != 0)
+	    height += (7-(height%8));
+		
+	//		System.out.println("FOUND WIDTH " + width + " (was " + img.getWidth(null) + ")");
+	//		System.out.println("FOUND HEIGHT " + height + " (was " + img.getHeight(null) + ")");
+
+	//	System.out.println("RESICON...");
+	if (img instanceof BufferedImage)
+	    {
+		BufferedImage result = (BufferedImage)img;
+
+		for (int y=0; y<result.getHeight(); y++)
+		    {
+			for (int x=0; x<result.getWidth(); x++)
+			    {
+				int rgb = result.getRGB(x, y);
+				if (((rgb>>24)&0xFF)>0)
+				    {
+					//					System.out.print(".");
+				    }
+				//				else
+				//				    System.out.print("*");
+			    }
+			//			System.out.println("");
+		    }
+
+	    }
+		
+	int[] pixelbuffer = new int[width*height];
+	PixelGrabber grabber = new PixelGrabber(img, 0, 0, width, height, pixelbuffer, 0, width);
+	try
+	    {
+		grabber.grabPixels();
+	    } catch (InterruptedException e)
+		{
+		    System.err.println("interrupted waiting for pixels!");
+		    throw new Exception("Can't load the image provided",e);
+		}
+		
+	Hashtable colors = calculateColorCount(pixelbuffer);
+
+	// FORCE ALWAYS to 8
+	this.BitsPerPixel = 8;
+		
+	Palette = new ResIcon.PaletteElement[1 << BitsPerPixel];
+	//	System.out.println("Creating palette of " + Palette.length + " colors (" + colors.size() + ")");
+	for (Enumeration e=colors.keys(); e.hasMoreElements(); )
+	    {
+		Integer pixi = (Integer)e.nextElement();
+		int pix = pixi.intValue();
+		int index = ((Integer)colors.get(pixi)).intValue();
+		//		System.out.println("set pixel " + index);
+	
+		Palette[index] = new ResIcon.PaletteElement();
+		Palette[index].Blue = pix & 0xFF;
+		Palette[index].Green = (pix >>  8) & 0xff;
+		Palette[index].Red = (pix >> 16) & 0xff;
+	    }
+	for (int i=0; i<Palette.length; i++)
+	    {
+		if (Palette[i] == null)
+		    Palette[i] = new ResIcon.PaletteElement();
+	    }
+		
+		
+	this.Size = 40;
+	this.Width = width;
+	this.Height = height* 2;
+	this.Planes = 1;
+	this.Compression = 0;
+		
+	this.SizeOfBitmap = 0;
+	this.HorzResolution = 0;
+	this.VertResolution = 0;
+
+	this.ColorsUsed = 0;
+	this.ColorsImportant = 0;
+
+	//
+	// We calculate the rowsize in bytes. It seems that it must be
+	// aligned on a double word, although none of the
+	// documentation I have on the icon format states so.
+	//
+	int rowsize = width / 8;
+	if ((rowsize%4)>0)
+	    {
+		rowsize += 4 - (rowsize%4);
+	    }
+
+	BitmapXOR = new short[(((int)Height/2) * (int)Width * (int)BitsPerPixel) / 8];
+	BitmapAND = new short[((int)Height/2) * rowsize];
+		
+	int bxl = BitmapXOR.length-1;
+	int bal = BitmapAND.length-1;
+	
+	for (int i=0; i<pixelbuffer.length; i++)
+	    {
+		int col = i%width;
+		int line = i/width;
+			
+		bxl = (width * height) - (((i/width)+1)*width) + (i%width);
+		//		bal = ((width * height)/8) - ((line+1)*(width/8)) + (col/8);
+		bal = (rowsize * height) - ((line+1)*(rowsize)) + (col/8);
+
+		// if ((pixelbuffer[i] & 0xFF000000) != 0x00000000)
+
+		//
+		// If the color is transparent, any color will suit
+		// (as it is not supposed to be displayed)
+		//
+		if (  (((pixelbuffer[i]>>24)& 0xFF) == 0))
+		    {
+			BitmapAND[ bal ] |= 1 << (7-(i%8));
+			BitmapXOR[bxl] = 0xFF; // (short)getBrightest(); FF
+
+			// 				int pixel = pixelbuffer[i] & 0x00FFFFFF;
+			// 				pixel = 0x000000;
+			// 				Integer icol = (Integer)colors.get(new Integer(pixel));
+			// 				if (icol != null)
+			// 				{
+			// 					int palindex = icol.intValue();
+			// 					BitmapXOR[bxl] = (short)palindex;
+			// 				}
+			// 				else
+			// 				{
+			// 				    BitmapXOR[bxl] = 0; // (short)getBrightest();
+			// 				    System.out.println("Can't find TRANSP BLACK COL " + icol );
+			// 				}
+		    }
+		else
+		    {
+			int pixel = pixelbuffer[i] & 0x00FFFFFF;
+			// pixel = 0x000000;
+			Integer icol = (Integer)colors.get(new Integer(pixel));
+			if (icol != null)
+			    {
+				int palindex = icol.intValue();
+				BitmapXOR[bxl] = (short)palindex;
+			    }
+		    }
+	    }
+    }
+
+    private int getBrightest()
+    {
+	int result = 0;
+	int averesult = 0;
+	for (int i=0; i<Palette.length; i++)
+	    {
+		int ave1 = (Palette[0].Red + Palette[0].Green + Palette[0].Blue)/3;
+		if (ave1 > averesult)
+		    {
+			averesult = ave1;
+			result = i;
+		    }
+	    }
+	return result;
+    }
+	
+    private Hashtable calculateColorCount(int[] pixels)
+    {
+	Hashtable result = new Hashtable();
+	int colorindex = 0;
+	for (int i=0; i<pixels.length; i++)
+	    {
+		int pix = pixels[i];
+		if (((pix>>24)&0xFF) > 0)
+		    {
+			pix &= 0x00FFFFFF;
+			Integer pixi = new Integer(pix);
+			Object o = result.get(pixi);
+			if (o == null)
+			    {
+				result.put(pixi, new Integer(colorindex++));
+			    }
+			//			if (colorindex > 256)
+			//			    return result;
+		    }
+	    }
+	return result;
+    }
+	
+    /** Creates and returns a ByteBuffer containing an image under
+     * the .ico format expected by Windows.
+     * @return a ByteBuffer with the .ico data
+     */	
+    public ByteBuffer getData()
+    {
+	int cols = (int)ColorsUsed;
+	if (cols == 0)
+	    cols = 1 << BitsPerPixel;
+
+	int rowsize = (int)Width / 8;
+	if ((rowsize%4)>0)
+	    {
+		rowsize += 4 - (rowsize%4);
+	    }
+
+	ByteBuffer buf = ByteBuffer.allocate((int) (40 + (cols*4) + (Width*(Height/2)*BitsPerPixel)/8 + (rowsize*(Height/2))));
+	buf.order(ByteOrder.LITTLE_ENDIAN);
+	buf.position(0);
+
+	buf.putInt((int)Size);
+	buf.putInt((int)Width);
+	buf.putInt((int)Height);
+	buf.putShort((short)Planes);
+	buf.putShort((short)BitsPerPixel);
+	buf.putInt((int)Compression);
+	buf.putInt((int)SizeOfBitmap);
+	buf.putInt((int)HorzResolution);
+	buf.putInt((int)VertResolution);
+	buf.putInt((int)ColorsUsed);
+	buf.putInt((int)ColorsImportant);
+
+	//		System.out.println("GET DATA :: Palette.size= "+Palette.length + " // position=" + buf.position());
+	for (int i=0; i<Palette.length; i++)
+	    {
+		PaletteElement el = Palette[i];
+		buf.put((byte)el.Blue);
+		buf.put((byte)el.Green);
+		buf.put((byte)el.Red);
+		buf.put((byte)el.Reserved);
+	    }
+
+	switch (BitsPerPixel)
+	    {
+	    case 4:
+		{
+		    for (int i=0; i<BitmapXOR.length; i+=2)
+			{
+			    int v1 = BitmapXOR[i];
+			    int v2 = BitmapXOR[i+1];
+			    buf.put((byte)( (v1<<4) | v2 ));
+			}		
+		}
+		break;
+			
+	    case 8:
+		{
+		    //				System.out.println("GET DATA :: XORBitmap.size= "+BitmapXOR.length + " // position=" + buf.position());
+		    for (int i=0; i<BitmapXOR.length; i++)
+			{
+			    buf.put((byte)BitmapXOR[i]);
+			}				
+		}
+		break;
+			
+	    default:
+		throw new RuntimeException("BitRes " + BitsPerPixel + " not supported!");
+	    }
+		
+	//		System.out.println("GET DATA :: AndBitmap.size= "+BitmapAND.length + " // position=" + buf.position());
+	for (int i=0; i<BitmapAND.length; i++)
+	    {
+		buf.put((byte)BitmapAND[i]);
+	    }
+		
+	//		System.out.println("GET DATA END AT " + buf.position());
+	buf.position(0);
+	return buf;
+    }
+	
+    public String toString()
+    {
+	StringBuffer out = new StringBuffer();
+		
+	out.append("Size: " + Size);
+	out.append("\nWidth: " + Width);
+	out.append("\nHeight: " + Height);
+	out.append("\nPlanes: " + Planes);
+	out.append("\nBitsPerPixel: " + BitsPerPixel);
+	out.append("\nCompression: " + Compression);
+	out.append("\nSizeOfBitmap: " + SizeOfBitmap);
+	out.append("\nHorzResolution: " + HorzResolution);
+	out.append("\nVertResolution: " + VertResolution);
+	out.append("\nColorsUsed: " + ColorsUsed);
+	out.append("\nColorsImportant: " + ColorsImportant);
+		
+	//		for (int i = 0; i<Palette.length; i++)
+	//		{
+	//			out.append("\n");
+	//			out.append(Palette[i].toString());
+	//		}
+	out.append("\nBitmapXOR["+ BitmapXOR.length+ "]={");
+	for (int i=0; i<BitmapXOR.length; i++)
+	    {
+		out.append((byte)BitmapXOR[i]);
+	    }
+	out.append("}\nBitmapAnd["+ BitmapAND.length +"]={");
+	for (int i=0; i<BitmapAND.length; i++)
+	    {
+		out.append((byte)BitmapAND[i]);
+	    }
+		
+	return out.toString();
+    }
+	
+    public static void main(String[]args) throws Exception
+    {
+	net.charabia.jsmoothgen.pe.PEFile.main(args);
+    }
+}
diff --git a/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/pe/res/ResIcon32.java b/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/pe/res/ResIcon32.java
new file mode 100644
index 0000000..0ecca5a
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/pe/res/ResIcon32.java
@@ -0,0 +1,126 @@
+package net.charabia.jsmoothgen.pe.res;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * 32 bits res icon with alpha channel
+ */
+public class ResIcon32 extends ResIcon {
+    BufferedImage image;
+
+    /**
+     * Creates a new instance based on the data of the Image argument.
+     *
+     * @param img image
+     * @throws Exception on error
+     */
+    public ResIcon32(Image img) throws Exception {
+        int width = img.getWidth(null);
+        int height = img.getHeight(null);
+
+        image = (BufferedImage) img;
+
+        this.BitsPerPixel = 32;
+
+        // header size
+        this.Size = 40;
+        this.Width = width;
+        this.Height = height * 2;
+        this.Planes = 1;
+        this.Compression = 0;
+
+        this.SizeOfBitmap = 0;
+        this.HorzResolution = 0;
+        this.VertResolution = 0;
+
+        this.ColorsUsed = 0;
+        this.ColorsImportant = 0;
+    }
+
+    /**
+     * Creates and returns a ByteBuffer containing an image under
+     * the .ico format expected by Windows.
+     *
+     * @return a ByteBuffer with the .ico data
+     */
+    public ByteBuffer getData() {
+        // transparency mask rows must be double bytes aligned
+        int rowsize = (int) Width / 8;
+        int padding = 0;
+        if ((rowsize % 4) > 0) {
+            padding = (int) ((4 - Width / 8 % 4) * 8);
+            rowsize += 4 - (rowsize % 4);
+        }
+        // transparency line padding size
+        // create transparency mask buffer
+        int transparencyBytesSize = (int) (rowsize * (Height / 2));
+        int[] transparencyMask = new int[transparencyBytesSize];
+
+        // allocate header + pixel count * bytes / pixel + transparency mask size
+        ByteBuffer buf = ByteBuffer.allocate((int) (40 + (Width * ((Height) / 2) * BitsPerPixel/8) + transparencyBytesSize));
+
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        buf.position(0);
+
+        // write header
+        buf.putInt((int) Size);
+        buf.putInt((int) Width);
+        buf.putInt((int) Height);
+        buf.putShort((short) Planes);
+        buf.putShort((short) BitsPerPixel);
+        buf.putInt((int) Compression);
+        buf.putInt((int) SizeOfBitmap);
+        buf.putInt((int) HorzResolution);
+        buf.putInt((int) VertResolution);
+        buf.putInt((int) ColorsUsed);
+        buf.putInt((int) ColorsImportant);
+
+        int pixelCount = 0;
+        for (int y = (int) ((Height / 2) - 1); y >= 0; y--) {
+            for (int x = 0; x < Width; x++) {
+                int pix = image.getRGB(x, y);
+                //System.out.println(x+" "+y+":"+Integer.toHexString((pix >> 24) & 0xff));
+                boolean isTransparent = ((pix >> 24) & 0xff) == 0;
+                if (isTransparent) {
+                    int index = pixelCount / 8;
+                    byte bitindex = (byte) (pixelCount % 8);
+                    transparencyMask[index] |= 0x80 >> bitindex;
+                }
+                buf.putInt(pix);
+                pixelCount++;
+            }
+            // skip to next transparency line
+            pixelCount += padding;
+        }
+
+        // transparency mask
+        for (int x = 0; x < transparencyBytesSize; x++) {
+            buf.put((byte) (transparencyMask[x] & 0xff));
+        }
+
+        buf.position(0);
+        return buf;
+    }
+
+    public String toString() {
+        StringBuffer out = new StringBuffer();
+
+        out.append("Size: ").append(Size);
+        out.append("\nWidth: ").append(Width);
+        out.append("\nHeight: ").append(Height);
+        out.append("\nPlanes: ").append(Planes);
+        out.append("\nBitsPerPixel: ").append(BitsPerPixel);
+        out.append("\nCompression: ").append(Compression);
+        out.append("\nSizeOfBitmap: ").append(SizeOfBitmap);
+        out.append("\nHorzResolution: ").append(HorzResolution);
+        out.append("\nVertResolution: ").append(VertResolution);
+        out.append("\nColorsUsed: ").append(ColorsUsed);
+        out.append("\nColorsImportant: ").append(ColorsImportant);
+
+        return out.toString();
+    }
+
+}
diff --git a/jsmooth-0.9.9-7-patch/src/net/charabia/util/codec/IcoCodec.java b/jsmooth-0.9.9-7-patch/src/net/charabia/util/codec/IcoCodec.java
new file mode 100644
index 0000000..7e88b27
--- /dev/null
+++ b/jsmooth-0.9.9-7-patch/src/net/charabia/util/codec/IcoCodec.java
@@ -0,0 +1,522 @@
+/*
+  JSmooth: a VM wrapper toolkit for Windows
+  Copyright (C) 2003 Rodrigo Reyes <reyes at charabia.net>
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ */
+
+package net.charabia.util.codec;
+
+import java.awt.Image;
+import java.awt.image.BufferedImage;
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+import javax.imageio.ImageIO;
+
+import net.charabia.util.io.BinaryInputStream;
+
+/**
+ *
+ * @author  Rodrigo Reyes
+ * @author  Riccardo Gerosa
+ */
+public class IcoCodec
+{
+
+	static private byte[] PNG_SIGNATURE = new byte[] { -119, 80, 78, 71, 13, 10, 26, 10 };
+
+    static public class IconDir
+    {
+        int idType;
+        int idCount;
+
+	public IconDir(BinaryInputStream in) throws IOException
+	{
+	    in.readUShortLE();
+	    idType = in.readUShortLE();
+	    idCount = in.readUShortLE();
+	}
+
+	public String toString()
+	{
+	    return "{ idType=" + idType + ", " + idCount + " }";
+	}
+    }
+
+    static public class IconEntry
+    {
+        short  bWidth;
+        short  bHeight;
+        short  bColorCount;
+        short  bReserved;
+        int  wPlanes;
+        int  wBitCount;
+        long dwBytesInRes;
+        long dwImageOffset;
+
+	public IconEntry(BinaryInputStream in) throws IOException
+	{
+	    bWidth = in.readUByte();
+	    if (bWidth == 0) { bWidth = 256; }
+	    bHeight = in.readUByte();
+	    if (bHeight == 0) { bHeight = 256; }
+	    bColorCount = in.readUByte();
+	    bReserved = in.readUByte();
+	    wPlanes = in.readUShortLE();
+	    wBitCount = in.readUShortLE();
+	    dwBytesInRes = in.readUIntLE();
+	    dwImageOffset = in.readUIntLE();
+	}
+
+	public String toString()
+	{
+	    StringBuffer buffer = new StringBuffer();
+	    buffer.append("{ bWidth="+bWidth+"\n");
+	    buffer.append("  bHeight="+bHeight+"\n");
+	    buffer.append("  bColorCount="+bColorCount+(bColorCount == 0 ? "(not paletted)":"")+"\n");
+	    buffer.append("  wPlanes="+wPlanes+"\n");
+	    buffer.append("  wBitCount="+wBitCount+"\n");
+	    buffer.append("  dwBytesInRes="+dwBytesInRes+"\n");
+	    buffer.append("  dwImageOffset="+dwImageOffset+"\n");
+	    buffer.append("}");
+
+	    return buffer.toString();
+	}
+
+    }
+
+    static public class IconHeader
+    {
+	public long Size;            /* Size of this header in bytes DWORD 0*/
+	public long Width;           /* Image width in pixels LONG 4*/
+	public long Height;          /* Image height in pixels LONG 8*/
+	public int  Planes;          /* Number of color planes WORD 12 */
+	public int  BitsPerPixel;    /* Number of bits per pixel WORD 14 */
+	/* Fields added for Windows 3.x follow this line */
+	public long Compression;     /* Compression methods used DWORD 16 */
+	public long SizeOfBitmap;    /* Size of bitmap in bytes DWORD 20 */
+	public long HorzResolution;  /* Horizontal resolution in pixels per meter LONG 24 */
+	public long VertResolution;  /* Vertical resolution in pixels per meter LONG 28*/
+	public long ColorsUsed;      /* Number of colors in the image DWORD 32 */
+	public long ColorsImportant; /* Minimum number of important colors DWORD 36 */
+
+	public IconHeader(BinaryInputStream in) throws IOException
+	{
+	    Size = in.readUIntLE();
+	    Width = in.readUIntLE();
+	    Height = in.readUIntLE();
+	    Planes = in.readUShortLE();
+	    BitsPerPixel = in.readUShortLE();
+	    Compression = in.readUIntLE();
+	    SizeOfBitmap = in.readUIntLE();
+	    HorzResolution = in.readUIntLE();
+	    VertResolution = in.readUIntLE();
+	    ColorsUsed = in.readUIntLE();
+	    ColorsImportant = in.readUIntLE();
+	}
+
+	public String toString()
+	{
+	    StringBuffer buffer = new StringBuffer();
+	    buffer.append("Size="); buffer.append(Size);
+	    buffer.append("\nWidth="); buffer.append(Width);
+	    buffer.append("\nHeight="); buffer.append(Height);
+	    buffer.append("\nPlanes="); buffer.append(Planes);
+	    buffer.append("\nBitsPerPixel="); buffer.append(BitsPerPixel);
+	    buffer.append("\nCompression="); buffer.append(Compression);
+	    buffer.append("\nSizeOfBitmap="); buffer.append(SizeOfBitmap);
+	    buffer.append("\nHorzResolution="); buffer.append(HorzResolution);
+	    buffer.append("\nVertResolution="); buffer.append(VertResolution);
+	    buffer.append("\nColorsUsed="); buffer.append(ColorsUsed);
+	    buffer.append("\nColorsImportant="); buffer.append(ColorsImportant);
+
+	    return buffer.toString();
+	}
+
+    }
+
+    public static BufferedImage checkImageSize(BufferedImage img, int width, int height) {
+        int w = img.getWidth(null);
+        int h = img.getHeight(null);
+        if ((w == width) && (h == height))
+            return img;
+        return null;
+    }
+
+    static boolean isSquareSize(BufferedImage bufferedImage, int size) {
+        return bufferedImage.getWidth() == bufferedImage.getHeight() && bufferedImage.getWidth() == size;
+    }
+
+    static int getColorDepth(BufferedImage bufferedImage) {
+        return bufferedImage.getColorModel().getPixelSize();
+    }
+
+    static public BufferedImage getPreferredImage(File f) throws IOException {
+        BufferedImage selected = null;
+        BufferedImage[] orgimages = loadImages(f);
+        if (orgimages != null) {
+            // select first image
+            selected = orgimages[0];
+            for (int i = 1; (i < orgimages.length); i++) {
+                 // We prefer 32x32 pictures, then 64x64, then 16x16...
+                 if (isSquareSize(orgimages[i], 32)) {
+                     if (!isSquareSize(selected, 32) || getColorDepth(orgimages[i]) > getColorDepth(selected)) {
+                         selected = orgimages[i];
+                     }
+                 } else if (isSquareSize(orgimages[i], 64)) {
+                     if (!isSquareSize(selected, 32) ||
+                             (isSquareSize(selected, 64) && getColorDepth(orgimages[i]) > getColorDepth(selected))) {
+                         selected = orgimages[i];
+                     }
+
+                 } else if (isSquareSize(orgimages[i], 16)) {
+                     if ((!isSquareSize(selected, 32) && !isSquareSize(selected, 64)) ||
+                             (isSquareSize(selected, 16) && getColorDepth(orgimages[i]) > getColorDepth(selected))) {
+                         selected = orgimages[i];
+                     }
+                 }
+            }
+
+            if (selected == null) {
+                //
+                // If there is no 32x32, 64x64, nor 16x16, then we scale the
+                // biggest image to be 32x32... This should happen mainly when
+                // loading an image from a png of gif file, and in most case
+                // there is only one image on the array.
+                //
+                int maxsize = 0;
+                for (int i = 0; (i < orgimages.length) && (selected == null); i++) {
+                    int size = orgimages[i].getWidth(null) * orgimages[i].getHeight(null);
+                    if (size > maxsize) {
+                        maxsize = size;
+                        selected = orgimages[i];
+                    }
+                }
+            }
+        }
+        return selected;
+    }
+
+    static public BufferedImage[] loadImages(File f) throws IOException
+    {
+	InputStream istream = new FileInputStream(f);
+        BufferedInputStream buffin = new BufferedInputStream(istream);
+	BinaryInputStream in = new BinaryInputStream(buffin);
+
+	try {
+	    in.mark(1024 * 1024);
+
+	    IconDir dir = new IconDir(in);
+	    //	    System.out.println("DIR = " + dir);
+
+	    IconEntry[] entries = new IconEntry[dir.idCount];
+	    BufferedImage[] images = new BufferedImage[dir.idCount];
+
+	    for (int i=0; i<dir.idCount; i++)
+		{
+		    entries[i] = new IconEntry(in);
+		    //		    System.out.println("ENTRY " + i + " = " + entries[i]);
+		}
+
+	    IconEntry entry = entries[0];
+	    //	    System.out.println("ENTRYx = " + entry);
+
+	    for (int i=0; i<dir.idCount; i++)
+		{
+	    	in.reset();
+
+	    	boolean pngIcon = false;
+	    	if (entries[i].dwBytesInRes > PNG_SIGNATURE.length) {
+	    		// 		Check if this is a PNG icon (introduced in Windows Vista)
+	    		in.mark(1024 * 1024);
+			    in.skip(entries[i].dwImageOffset);
+			    byte[] signatureBuffer = new byte[PNG_SIGNATURE.length];
+			    in.read(signatureBuffer, 0, PNG_SIGNATURE.length);
+			    pngIcon = Arrays.equals(PNG_SIGNATURE, signatureBuffer);
+			    in.reset();
+	    	}
+	    	//		System.out.println("PNG Icon = " + pngIcon);
+
+		    in.skip(entries[i].dwImageOffset);
+
+		    BufferedImage image;
+
+		    if (pngIcon) {
+		    	// This is a PNG icon (introduced in Windows Vista)
+		    	byte[] imageData = new byte[(int) entries[i].dwBytesInRes];
+		    	in.read(imageData, 0, (int) entries[i].dwBytesInRes);
+		    	ByteArrayInputStream imageDataBuffer = new ByteArrayInputStream(imageData);
+		    	image = ImageIO.read(imageDataBuffer);
+		    } else {
+		    	//		This is a standard icon (not PNG)
+			    IconHeader header = new IconHeader(in);
+			    //		    System.out.println("Header: " + header);
+
+			    long toskip = header.Size - 40;
+			    if (toskip>0)
+				in.skip((int)toskip);
+
+			    //		System.out.println("skipped data");
+
+
+			    switch(header.BitsPerPixel)
+				{
+				case 4:
+				case 8:
+                    image = new BufferedImage((int)header.Width, (int)header.Height/2,
+                                        BufferedImage.TYPE_BYTE_INDEXED);
+				    loadPalettedImage(in, entries[i], header, image);
+				    break;
+				case 24:
+				case 32:
+                    image = new BufferedImage((int)header.Width, (int)header.Height/2,
+                                        BufferedImage.TYPE_INT_ARGB);
+					//		This is a true-color icon (introduced in Windows XP)
+					loadTrueColorImage(in, entries[i], header, image);
+					break;
+				default:
+					throw new Exception("Unsupported ICO color depth: " + header.BitsPerPixel);
+				}
+		    }
+
+		    images[i] = image;
+		}
+
+	    return images;
+
+	} catch (Exception exc)
+	    {
+		exc.printStackTrace();
+	    }
+
+	return null;
+    }
+
+    static private void loadPalettedImage(BinaryInputStream in, IconEntry entry, IconHeader header, BufferedImage image) throws Exception
+    {
+	//	System.out.println("Loading image...");
+
+	//	System.out.println("Loading palette...");
+
+	//
+	// First, load the palette
+	//
+	int cols = (int)header.ColorsUsed;
+	if (cols == 0)
+	    {
+		if (entry.bColorCount != 0)
+		    cols = entry.bColorCount;
+		else
+		    cols = 1 << header.BitsPerPixel;
+	    }
+
+	int[] redp = new int[cols];
+	int[] greenp = new int[cols];
+	int[] bluep = new int[cols];
+
+ 	for (int i=0; i<cols; i++)
+	    {
+		bluep[i] = in.readUByte();
+		greenp[i] = in.readUByte();
+		redp[i] = in.readUByte();
+		in.readUByte();
+	    }
+
+	//	System.out.println("Palette read!");
+
+	//
+	// Set the image
+
+	//int xorbytes = (((int)header.Height/2) * (int)header.Width);
+	int readbytes = 0;
+
+	for (int y=(int)(header.Height/2)-1; y>=0; y--)
+	    {
+		for (int x=0; x<header.Width; x++)
+		    {
+			switch(header.BitsPerPixel)
+			    {
+			    case 4:
+				{
+				    int pix = in.readUByte();
+				    readbytes++;
+
+				    int col1 = (pix>>4) & 0x0F;
+				    int col2 = pix & 0x0F;
+				    image.setRGB(x, y, (0xFF<<24) | (redp[col1]<<16) | (greenp[col1]<<8) | bluep[col1]);
+				    image.setRGB(++x, y, (0xFF<<24) | (redp[col2]<<16) | (greenp[col2]<<8) | bluep[col2]);
+				}
+				break;
+			    case 8:
+				{
+				    int col1 = in.readUByte();
+				    readbytes++;
+
+				    image.setRGB(x, y, (0xFF<<24) | (redp[col1]<<16) | (greenp[col1]<<8) | bluep[col1]);
+				}
+				break;
+			    }
+		    }
+	    }
+	//	System.out.println("XOR data read (" + readbytes + " bytes)");
+
+	int height = (int)(header.Height/2);
+
+	int rowsize = (int)header.Width / 8;
+	if ((rowsize%4)>0)
+	    {
+		rowsize += 4 - (rowsize%4);
+	    }
+
+	//	System.out.println("rowsize = " + rowsize);
+	int[] andbytes = new int[rowsize * height ];
+
+	for (int i=0; i<andbytes.length; i++)
+	    andbytes[i] = in.readUByte();
+
+
+	for (int y=height-1; y>=0; y--)
+	    {
+		for (int x=0; x<header.Width; x++)
+		    {
+			int offset = ((height - (y+1))*rowsize) + (x/8);
+			if ( (andbytes[offset] & (1<<(7-x%8))) != 0)
+			    {
+				image.setRGB(x, y, 0);
+			    }
+		    }
+	    }
+
+	// 	for (int i=0; i<andbytes; i++)
+	// 	    {
+	// 		int pix = in.readUByte();
+	// 		readbytes++;
+
+	// 		int xb = (i*8) % (int)header.Width;
+	// 		int yb = ((int)header.Height/2) - (((i*8) / (int)header.Width)+1);
+
+	// 		for (int offset=7; offset>=0; offset--)
+	// 		    {
+	// 			//
+	// 			// Modify the transparency only if necessary
+	// 			//
+	// 			System.out.println("SET AND (" + xb + "," + yb + ")-" + (7-offset));
+
+	// 			if (((1<<offset) & pix)!=0)
+	// 			    {
+	// 				int argb = image.getRGB(xb+(7-offset), yb);
+	// 				image.setRGB(xb+(7-offset), yb, argb & 0xFFFFFF);
+	// 			    }
+	// 		    }
+	// 	    }
+
+	//	System.out.println("AND data read (" + readbytes + " bytes total)");
+    }
+
+    static private void loadTrueColorImage(BinaryInputStream in, IconEntry entry, IconHeader header, BufferedImage image) throws Exception
+    {
+	//	System.out.println("Loading image...");
+
+	//
+	// Set the image
+
+	//int xorbytes = (((int)header.Height/2) * (int)header.Width);
+	int readbytes = 0;
+
+	for (int y=(int)(header.Height/2)-1; y>=0; y--)
+	    {
+		for (int x=0; x<header.Width; x++)
+		    {
+			switch(header.BitsPerPixel)
+			    {
+			    case 32:
+				{
+				    int b = in.readUByte();
+				    int g = in.readUByte();
+				    int r = in.readUByte();
+				    int a = in.readUByte();
+				    readbytes++;
+
+				    image.setRGB(x, y, (a<<24) | (r<<16) | (g<<8) | b);
+				}
+				break;
+			    case 24:
+			    {
+				    int b = in.readUByte();
+				    int g = in.readUByte();
+				    int r = in.readUByte();
+				    readbytes++;
+
+				    image.setRGB(x, y, (0xFF<<24) | (r<<16) | (g<<8) | b);
+				}
+			    break;
+			    }
+		    }
+	    }
+	//		System.out.println("XOR data read (" + readbytes + " bytes)");
+
+    if (header.BitsPerPixel < 32) {
+		int height = (int)(header.Height/2);
+
+		int rowsize = (int)header.Width / 8;
+		if ((rowsize%4)>0)
+		    {
+			rowsize += 4 - (rowsize%4);
+		    }
+
+		//		System.out.println("rowsize = " + rowsize);
+		int[] andbytes = new int[rowsize * height ];
+
+		for (int i=0; i<andbytes.length; i++)
+		    andbytes[i] = in.readUByte();
+
+
+		for (int y=height-1; y>=0; y--)
+		    {
+			for (int x=0; x<header.Width; x++)
+			    {
+				int offset = ((height - (y+1))*rowsize) + (x/8);
+				if ( (andbytes[offset] & (1<<(7-x%8))) != 0)
+				    {
+					image.setRGB(x, y, 0);
+				    }
+			    }
+		    }
+	}
+
+	//	System.out.println("AND data read (" + readbytes + " bytes total)");
+    }
+
+    static public void main(String[]args) throws Exception
+    {
+	File f = new File(args[0]);
+	Image img = IcoCodec.loadImages(f)[0];
+	//	System.out.println("img = " + img);
+
+	javax.swing.JFrame jf = new javax.swing.JFrame("Test");
+	javax.swing.JButton button = new javax.swing.JButton(new javax.swing.ImageIcon(img));
+	jf.getContentPane().add(button);
+	jf.pack();
+	jf.setVisible(true);
+    }
+
+}
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..898b4a5
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,355 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>davmail</groupId>
+    <artifactId>davmail</artifactId>
+    <packaging>jar</packaging>
+    <version>3.9.9</version>
+    <name>DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway</name>
+    <organization>
+        <name>Mickaël Guessant</name>
+    </organization>
+    <url>http://www.sourceforge.net/projects/davmail</url>
+    <inceptionYear>2001</inceptionYear>
+    <description>
+        Ever wanted to get rid of Outlook ? DavMail is a POP/IMAP/SMTP/Caldav/Carddav/LDAP exchange gateway
+        allowing users to use any mail/calendar client (e.g. Thunderbird with Lightning or Apple iCal) with
+        an Exchange server, even from the internet or behind a firewall through Outlook Web Access.
+        DavMail now includes an LDAP gateway to Exchange global address book and user personal contacts
+        to allow recipient address completion in mail compose window and full calendar support
+        with attendees free/busy display.
+        The main goal of DavMail is to provide standard compliant protocols in front of proprietary
+        Exchange. This means LDAP for address book, SMTP to send messages, IMAP to browse messages
+        on the server in any folder, POP to retrieve inbox messages only and Caldav for calendar support.
+        Thus any standard compliant client can be used with Microsoft Exchange.
+        DavMail gateway is implemented in java and should run on any platform. Releases are tested on Windows,
+        Linux (Ubuntu) and Mac OSX. Tray does not work on MacOS and is replaced with a full frame.
+        Tested successfully with the Iphone (gateway running on a server).
+        DavMail gateway is implemented in java and should run on any platform. Releases are tested
+        on Windows, Linux (Ubuntu) and Mac OSX. Tested successfully with the Iphone
+        (gateway running on a server).
+    </description>
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+    <developers>
+        <developer>
+            <name>Mickaël Guessant</name>
+            <id>mguessan</id>
+            <email>mguessan at free.fr</email>
+            <url>http://mguessan.free.fr</url>
+            <roles>
+                <role>Project Founder</role>
+                <role>Java Developer</role>
+            </roles>
+        </developer>
+    </developers>
+    <contributors>
+        <contributor>
+            <name>Dfoody</name>
+            <email>dfoody at users.sourceforge.net</email>
+            <url>http://sourceforge.net/users/dfoody</url>
+            <roles>
+                <role>Main Java Contributor</role>
+            </roles>
+        </contributor>
+        <contributor>
+            <name>John Ahern</name>
+            <email>jahern2502 at users.sourceforge.net</email>
+            <url>http://sourceforge.net/users/jahern2502</url>
+            <roles>
+                <role>Java Contributor</role>
+            </roles>
+        </contributor>
+        <contributor>
+            <name>Eivind Tagseth</name>
+            <email>eivindt at users.sourceforge.net</email>
+            <url>http://sourceforge.net/users/eivindt</url>
+            <roles>
+                <role>Java Contributor</role>
+            </roles>
+        </contributor>
+        <contributor>
+            <name>Mitchell V. Oliver</name>
+            <email>pianoman113 at users.sourceforge.net</email>
+            <url>https://sourceforge.net/users/pianoman113</url>
+            <roles>
+                <role>Java Contributor</role>
+            </roles>
+        </contributor>
+        <contributor>
+            <name>Gellule</name>
+            <email>gellule at users.sourceforge.net</email>
+            <url>https://sourceforge.net/users/gellule</url>
+            <roles>
+                <role>Java Contributor</role>
+            </roles>
+        </contributor>
+        <contributor>
+            <name>Jeremiah Albrant</name>
+            <url>http://sourceforge.net/users/?user_id=2903536</url>
+            <roles>
+                <role>Java Contributor</role>
+            </roles>
+        </contributor>
+        <contributor>
+            <name>Henning Holtschneider</name>
+            <email>hehol at users.sourceforge.net</email>
+            <url>http://sourceforge.net/users/hehol</url>
+            <roles>
+                <role>Tester</role>
+            </roles>
+        </contributor>
+        <contributor>
+            <name>Shocker</name>
+            <email>gkwait at users.sourceforge.net</email>
+            <url>http://sourceforge.net/users/gkwait</url>
+            <roles>
+                <role>Tester</role>
+            </roles>
+        </contributor>
+        <contributor>
+            <name>Guido</name>
+            <email>guido2 at users.sourceforge.net</email>
+            <url>http://sourceforge.net/users/guido2</url>
+            <roles>
+                <role>Tester</role>
+            </roles>
+        </contributor>
+    </contributors>
+    <licenses>
+        <license>
+            <name>GNU General Public License</name>
+            <url>http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt</url>
+            <distribution>repo</distribution>
+        </license>
+    </licenses>
+    <mailingLists>
+        <mailingList>
+            <name>DavMail User List</name>
+            <post>davmail-users at lists.sourceforge.net.</post>
+            <subscribe>http://lists.sourceforge.net/lists/listinfo/davmail-users</subscribe>
+            <unsubscribe>http://lists.sourceforge.net/lists/listinfo/davmail-users</unsubscribe>
+            <archive>http://sourceforge.net/mailarchive/forum.php?forum_name=davmail-users</archive>
+        </mailingList>
+    </mailingLists>
+    <scm>
+        <connection>scm:svn:https://davmail.svn.sourceforge.net/svnroot/davmail/trunk</connection>
+        <developerConnection>scm:svn:https://davmail.svn.sourceforge.net/svnroot/davmail/trunk</developerConnection>
+        <tag>HEAD</tag>
+        <url>http://davmail.svn.sourceforge.net/viewvc/davmail/trunk/</url>
+    </scm>
+    <issueManagement>
+        <system>SourceForge</system>
+        <url>http://sourceforge.net/tracker/?group_id=184600</url>
+    </issueManagement>
+    <repositories>
+        <repository>
+            <id>central</id>
+            <name>Maven Repository Switchboard</name>
+            <layout>default</layout>
+            <url>http://repo1.maven.org/maven2</url>
+            <snapshots>
+                <enabled>false</enabled>
+            </snapshots>
+        </repository>
+        <repository>
+            <id>xwiki</id>
+            <layout>default</layout>
+            <url>http://maven.xwiki.org/externals</url>
+            <snapshots>
+                <enabled>false</enabled>
+            </snapshots>
+        </repository>
+    </repositories>
+    <dependencies>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>3.8.1</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.jackrabbit</groupId>
+            <artifactId>jackrabbit-webdav</artifactId>
+            <version>1.4</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.jackrabbit</groupId>
+                    <artifactId>jackrabbit-jcr-commons</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>xml-apis</groupId>
+                    <artifactId>xml-apis</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+            <version>1.3.1</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-httpclient</groupId>
+            <artifactId>commons-httpclient</artifactId>
+            <version>3.1</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+            <version>1.3</version>
+        </dependency>
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+            <version>1.2.16</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>javax.jms</groupId>
+                    <artifactId>jms</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>com.sun.jdmk</groupId>
+                    <artifactId>jmxtools</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>com.sun.jmx</groupId>
+                    <artifactId>jmxri</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>javax.mail</groupId>
+            <artifactId>mail</artifactId>
+            <version>1.4.3</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.activation</groupId>
+            <artifactId>activation</artifactId>
+            <version>1.1.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse</groupId>
+            <artifactId>swt</artifactId>
+            <version>3.7.0</version>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <version>2.4</version>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>net.sourceforge.htmlcleaner</groupId>
+            <artifactId>htmlcleaner</artifactId>
+            <version>2.1</version>
+        </dependency>
+        <!-- included in Java 1.6, needed with Java 1.5 -->
+        <dependency>
+            <groupId>org.codehaus.woodstox</groupId>
+            <artifactId>woodstox-core-asl</artifactId>
+            <version>4.1.2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.samba.jcifs</groupId>
+            <artifactId>jcifs</artifactId>
+            <version>1.3.14</version>
+        </dependency>
+        <dependency>
+            <groupId>net.freeutils.charset</groupId>
+            <artifactId>jcharset</artifactId>
+            <version>1.3</version>
+        </dependency>
+        <dependency>
+            <groupId>org.boris.winrun4j</groupId>
+            <artifactId>winrun4j</artifactId>
+            <version>0.4.4</version>
+        </dependency>
+    </dependencies>
+    <pluginRepositories>
+        <pluginRepository>
+            <id>apache.snapshots</id>
+            <url>http://repository.apache.org/snapshots</url>
+            <releases>
+                <enabled>false</enabled>
+            </releases>
+        </pluginRepository>
+    </pluginRepositories>
+    <build>
+        <sourceDirectory>src/java</sourceDirectory>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.5</source>
+                    <target>1.5</target>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-site-plugin</artifactId>
+                <version>2.2</version>
+            </plugin>
+        </plugins>
+    </build>
+    <distributionManagement>
+        <site>
+            <id>sourceforge.net</id>
+            <url>scp://web.sourceforge.net/home/groups/d/da/davmail/htdocs</url>
+        </site>
+    </distributionManagement>
+    <reporting>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-project-info-reports-plugin</artifactId>
+                <reportSets>
+                    <reportSet>
+                        <reports>
+                            <report>dependencies</report>
+                            <report>dependency-convergence</report>
+                            <report>project-team</report>
+                            <report>mailing-list</report>
+                            <report>issue-tracking</report>
+                            <report>license</report>
+                            <report>scm</report>
+                            <report>index</report>
+                        </reports>
+                    </reportSet>
+                </reportSets>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-javadoc-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-changelog-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>findbugs-maven-plugin</artifactId>
+                <version>1.2</version>
+                <configuration>
+                    <threshold>Normal</threshold>
+                    <effort>Default</effort>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-checkstyle-plugin</artifactId>
+                <version>2.4</version>
+                <configuration>
+                    <configLocation>src/checkstyle/checkstyle-configuration.xml</configLocation>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jxr-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </reporting>
+</project>
diff --git a/releaseguide.txt b/releaseguide.txt
new file mode 100644
index 0000000..3f18c1e
--- /dev/null
+++ b/releaseguide.txt
@@ -0,0 +1,22 @@
+Release guide :
+- update roadmap
+- update releasenotes.txt from svn log
+- change version in build.xml and pom.xml, commit
+- update staging (clean checkout) directory
+- launch ant release, check generated packages
+- upload packages: ant upload-release
+- make sure swt and jcifs are available in local maven repository:
+mvn install:install-file -DgroupId=org.eclipse -DartifactId=swt -Dversion=3.7.0 -Dpackaging=jar -Dfile=lib/swt-3.7-win32-x86.jar
+mvn install:install-file -DgroupId=org.samba.jcifs -DartifactId=jcifs -Dversion=1.3.14 -Dpackaging=jar -Dfile=lib/jcifs-1.3.14.jar
+mvn install:install-file -DgroupId=javax.mail -DartifactId=mail -Dversion=1.4.3 -Dpackaging=jar -Dfile=lib/mail-1.4.3.jar
+mvn install:install-file -DgroupId=net.freeutils.charset -DartifactId=jcharset -Dversion=1.3 -Dpackaging=jar -Dfile=lib/jcharset-1.3.jar
+mvn install:install-file -DgroupId=org.boris.winrun4j -DartifactId=winrun4j -Dversion=0.4.4 -Dpackaging=jar -Dfile=lib/winrun4j-0.4.4.jar
+- generate site: mvn site
+- upload site: ant upload-site
+- upload releasenotes.txt to sourceforge
+- create tag in subversion
+- create news message at https://sourceforge.net/news/admin/?group_id=184600
+- send user mailing list message
+- change default download files on frs
+- upload version.txt with ant upload-version
+- create new version in sourceforge bug tracker
diff --git a/releasenotes.txt b/releasenotes.txt
new file mode 100644
index 0000000..f95554c
--- /dev/null
+++ b/releasenotes.txt
@@ -0,0 +1,2007 @@
+** DavMail 3.9.9 released **
+Bugfix release with major IMAP changes to improve sync performance,
+many Caldav enhancements and bugfixes and some documentation updates.
+
+Caldav:
+- Caldav: encode ? in urlcompname
+- Caldav: fix 3534615, patch allday dates only on Exchange 2007
+- Caldav: implement full contact folder dump at /users/<email>/contacts/
+- Caldav: implement task priority over EWS
+- Caldav: remove unsupported attachment reference to avoid iPhone/iPad crash
+- Caldav: reintroduce davmail.caldavDisableTasks setting to disable tasks support
+- Caldav: fix encode pipe | to %7C in urlcompname
+- Caldav: encode pipe | to %7C in urlcompname
+- CalDav: Fix 3512857, avoid double path encoding in DavExchangeSession.loadVtimezone()
+- Caldav: improve Exchange 2007 EWS meeting support
+- Caldav: rebuild meeting attendees only for Exchange 2007, Exchange 2010 ics parser is correct
+
+Enhancements:
+- Fixes from audit
+- store davmail.log in user home folder to avoid crash on first start when current directory is not writable by user
+- Add WinRun4J to Maven POM and update windows service documentation
+- Switch to WinRun4J for Windows service wrapper
+- Fix 3494770: Add missing antlr runtime
+- Upgrade svnkit for subversion 1.7 compatibility
+
+IMAP:
+- IMAP: Fix 3534801, workaround for missing From header
+- IMAP: fix 3441891, workaround for Exchange 2003 ActiveSync bug
+- IMAP: experimental implementation of header only FETCH, do not download full message content and send approximate RFC822.SIZE (MAPI size)
+- IMAP: avoid full message download on OSX Lion flags request with content-class header
+- IMAP: exclude IDLE from infinite loop detection
+- IMAP: add date header to rebuilt message
+- IMAP: Force UTF-8 on message rebuild
+- IMAP: implement RFC822 fetch request
+
+GUI:
+- GUI: force alwaysOnTop on dialogs to make sure they are visible
+- GUI: always bring dialog windows to front
+
+Documentation:
+- Doc: add a new FAQ entry on shared mailbox access over IMAP
+- Doc: Update doc to include Java 7
+- Doc: small fix in Linux setup doc
+- Doc: Update Linux instructions for Ubuntu 12 Natty
+- Doc: New review
+- Doc: update Thunderbird POP account setup doc
+- Doc: Update SSL setup documentation on PKCS12 passwords
+- Doc: add a note on hidden folders on OSX Lion
+- Doc: Fix new thunderbird doc
+
+OSX:
+- OSX: new hide from Dock setting available directly in UI (DavMail restart needed)
+
+Carddav:
+- Carddav: Fix 3511472, implement fileas over EWS
+- Carddav: Skip carriage return in ICSBufferedWriter
+
+EWS:
+- EWS: disable gzip encoding if WIRE logging is at DEBUG level
+- EWS: fix 3263905 ErrorInvalidPropertyRequest, do not update message:IsRead on appointments
+- EWS: make isMainCalendar case insensitive
+- EWS: revert chunked inputstream inside gzip and create new setting davmail.acceptEncodingGzip
+- EWS: handle chunked inputstream inside gzip
+- EWS: improve error message handling, log error description
+- EWS: improve error handling on socket exception
+- EWS: avoid NullPointerException in broken message rebuild
+
+WebDav:
+- Dav: decode permanenturl to avoid double urlencoding issue
+- Dav: decode url returned on saveappt cmd in DavExchangeSession.loadVtimezone()
+
+** DavMail 3.9.8 released **
+Prepare 4.0 release with improved Exchange 2010 support, added IMAP MOVE extension support,
+include a new windows noinstall package and implement captcha authentication support.
+
+Documentation:
+- Doc: update roadmap
+- Doc: add a statement on adding NSIS to system path in build instructions
+- Doc: update Thunderbird IMAP setup instructions for Thunderbird 10
+- Doc: update java package reference
+- Doc: update address book setup instructions for OSX Lion
+- Doc: add Growl reference in OSX setup
+
+Enhancements:
+- Fix nsis script: delete stax api jar on uninstall
+- Fixes from audit
+- New redline ant task definition fix
+- Exclude Junit from binary packages
+- Create Windows noinstall package
+- Implement a new davmail.clientSoTimeout setting to adjust or disable connection timeout
+- Improve message on invalid OWA uri
+- Fix notification dialog test
+- Improve Pinsafe captcha display
+- workaround for broken form with empty action
+- Implement ISA server PINsafeISAFilter support (captcha image)
+- Upgrade Redline RPM
+- Add StreamScanner.java from Woodstox 4.1.2
+- Upgrade to Woodstox 4.1.2
+- Fix 3454332: davmail.sh script missing shebang 
+- add trust=true in upload-site
+
+IMAP:
+- IMAP: fix search date format for Exchange 2010 support (ErrorInvalidValueForProperty)
+- IMAP: implement SEARCH TEXT on from, to, cc, subject and body
+- IMAP: send error on COPY/MOVE when message iterator is empty
+- IMAP: implement MOVE RFC draft http://tools.ietf.org/id/draft-krecicki-imap-move-00.html
+- IMAP: fix 3480516, () instead of NIL on empty envelope header
+- IMAP: Fix 3479993, backslash in header
+
+SMTP:
+- SMTP: fix 3489007, Sparrow AUTH PLAIN authentication support
+
+Caldav:
+- Caldav: force context Timezone on Exchange 2010
+- Caldav: add missing timezones from Exchange 2007 over WebDav
+- Caldav: let users edit outgoing notifications for meeting requests
+- Caldav: fix NullPointerException on addressbook request
+- Caldav: workaround for broken items with \n as first line character
+
+POP:
+- POP: add a new setting to mark messages read after RETR
+
+EWS:
+- EWS: fix ErrorInvalidValueForProperty on search undeleted with Exchange 2010, set type Integer on PidLidImapDeleted and junk 0x1083
+- EWS: new fix to improve failover on error retrieving MimeContent
+- EWS: improve failover on error retrieving MimeContent
+- EWS: Fix 3471671, workaround for Exchange invalid chars
+
+LDAP:
+- LDAP: improve invalid dn message
+
+OSX:
+- OSX: make nodock mode the default
+- OSX: make sure davmail.jar is first in classpath
+
+DAV:
+- Dav: set contact email type to SMTP
+- Dav: add email type MAPI properties
+
+Carddav:
+- Carddav: avoid NullPointerException on broken contact
+- Carddav: fix regression on address book handling on Snow Leopard
+- Carddav: decode urlcompname before search to retrieve contacts with & in url
+
+
+** DavMail 3.9.7 released **
+Another bugfix release with new stax based webdav search method implementation to reduce memory footprint with large folders,
+exclude non event items from calendar to avoid errors, some EWS fixes on tasks handling and a few documentation updates
+
+WebDav:
+- Dav: fix regression in new Stax implementation
+- Dav: new stax based WebDav requests implementation to reduce memory usage, enabled on Search requests
+- Dav: switch back to mailbox path on Exchange 2003 for CmdBasePath
+
+Caldav:
+- Caldav: Experimental patch to support spaces in calendar or contacts path on OSX, see 3464086
+- Caldav: Create a new davmail.caldavEnableLegacyTasks to allow access to tasks created in calendar folder by previous DavMail versions
+- Caldav: drop davmail.caldavDisableTasks setting, retrieve only events from calendar
+- Caldav: Change field update order for Exchange 2007 over EWS
+- Caldav: apply date filter to tasks
+- Caldav: new timezone for Mexico
+- Caldav: fix 3433584, encode comma in LOCATION field
+
+IMAP:
+- IMAP: fix double slash in folder path
+- IMAP: return all search results uids on a single line for Wanderlust
+- IMAP: new davmail.imapIncludeSpecialFolders setting to access all folders including calendar and tasks over IMAP
+- IMAP: fix wanderlust support, allow lower case fetch params
+
+Documentation:
+- Doc: Added DavMail hangs on 64-bit Linux FAQ entry
+- Doc: add documentation for davmail.logFileSize option
+
+Enhancements:
+- Change default use system proxies value to false
+- Avoid NullPointerException on WebdavNotAvailableException
+- Fix upload-version target site
+
+EWS:
+- EWS: fix Exchange 2010 SP1 support
+- EWS: use archivemsgfolderroot as archive root
+- EWS: enable preemptive authentication on non NTLM endpoints
+- EWS: add Exchange2010_SP1 support for online archive
+
+LDAP:
+- LDAP: avoid NullPointerException during SASL authentication
+
+Carddav:
+- Carddav: encode star in urlcompname
+
+
+** DavMail 3.9.6 released **
+Another bugfix release to improve iPad 2 and Debian based Linux support. 
+Also includes new protocol mode options (EWS, WebDav or Auto), experimental Exchange online archive 
+support, IMAP UTF-8 search parameter and many Caldav fixes
+
+Enhancements:
+- Add a new upload-version ant target to upload version.txt
+- Workaround for broken servers that send invalid Basic authentication challenge
+- Add exchangecookie to the list of authentication cookies for direct EWS access
+- Add a new auto value to davmail.enableEws setting to avoid unwanted switch from WebDav to EWS on temporary Exchange connection issue
+- Encode # in urlcompname
+- Fix bug on ITEM_PROPERTIES value on EWS/WebDav mode switch
+- Add new Default button to reset log levels
+- Implement a new option to let users disable all GUI notifications
+- Additional exception trace exclusion 
+- Revert 1.7 test on SWT, tray implementation is still broken on Linux
+
+Documentation:
+- Doc: reformat urls in FAQ
+- Doc: add a note to help users with broken Unity desktop manager on Ubuntu
+- Doc: Fix typo in project description
+- Doc: additional note on Caldav setup in Thunderbird and new external review
+- Doc: document new disable balloon notifications setting
+- Doc: Update roadmap
+- Doc: New reviews
+- Doc: Update iCal doc to match both Snow Leopard and Lion
+- Doc: Update FAQ
+- Doc: Update DavMail settings screenshot
+
+Linux
+- Allow openjdk-7-jre dependency in deb package 
+- Fix 3418960: Update dependencies for Ubuntu 11.10, add libswt-gtk-3-java
+
+Caldav:
+- Caldav: apply iCal 5 workaround to iOS 5
+- Caldav: new timezone in rename table
+- Caldav: try to merge Exchange 2010 and 2007 filters
+- Caldav: additional unit tests
+- Caldav: fix 3426148 decode and encode comma in RESOURCES field value
+- Caldav: Fix complex timezones sent by clients, leave only latest STANDARD and DAYLIGHT definition
+- Caldav: Fix 3420240, retrieve description from tasks over Dav
+
+EWS:
+- EWS: add new DistinguishedFolderId value for Exchange archive support
+- EWS: throw exception on 400 Bad request answer
+
+IMAP:
+- IMAP: fix 3426383, implement CHARSET in SEARCH command, allow ASCII and UTF-8
+- IMAP: fix 3353862, long file names encoding in BODYSTRUCTURE
+
+SMTP:
+- SMTP: adjust workaround for misconfigured Exchange server that return 406 Not Acceptable on draft message creation, look inside multipart messages
+
+** DavMail 3.9.5 released **
+Bugfix release to avoid Growl plugin crash on OSX, make DavMail work with both
+Snow Leopard and Lion. Also includes DIGEST-MD5 implementation for OSX Lion
+Directory Utility support, however iCal attendee completion is still broken.
+
+OSX:
+- Fix crash in Growl plugin on OSX: do not sent SSL content to Growl
+
+EWS:
+- EWS: rebuild broken message (null MimeContent) from properties
+- EWS: improve error logging on invalid character
+- EWS: fix tasks field order, send Extended Properties first to match EWS schema
+- EWS: replace extension before looking for items in task folder
+- EWS: Fix 3407395, do not set mailbox on FolderIds returned by Exchange
+
+LDAP:
+- LDAP: fix DIGEST-MD5 authentication and adjust dn context for OSX Lion, still experimental
+- LDAP: fix DIGEST-MD5 SASL authentication for OSX Lion
+- LDAP: experimental SASL DIGEST-MD5 implementation for OSX Lion Directory Utility support
+
+Caldav:
+- Caldav: need to send principal-URL on principal path, only for OSX Lion
+- Caldav: allow direct access to task folder
+
+Enhancements:
+- Force toFront and requestFocus to bring windows to front
+- Additional proxy selector logging
+- Fixes from audit
+- Upgrade Log4J to 1.2.16
+
+Documentation
+- Doc: Add saveInSent reference in FAQ
+
+** DavMail 3.9.4 released **
+First release with full Exchange tasks (VTODO) support, automatically convert VTODOs to tasks
+inside default task folder. Also includes many bugfixes on iCal OSX Snow Leopard support
+(note: need to recreate the calendar to fix sync), mixed Exchange 2010/2003 architecture support and
+IMAP DRAFT and UNDRAFT search condition support
+
+Caldav:
+- Caldav: fix regression
+- Caldav: implement update folder
+- Caldav: fix regression on Snow Leopard
+- Caldav: more general fix for misconfigured Exchange server, replace host name in url also over Caldav
+- Caldav: additional fix for CRLF in urlcompname
+- Caldav: additional task fields over EWS and fix urlcompname decoding
+- Caldav: implement task categories over EWS
+- Caldav: update additional MAPI properties for tasks
+- Caldav: implement startdate and duedate on tasks
+- Caldav: implement task categories
+- Caldav: Need to encode % in urlcompname
+- Caldav: implement task percent complete and status over WebDav
+- Caldav: improve task support over WebDav, rename .ics to .EML and implement priority (importance)
+- Caldav: do not try to get ICS content from tasks
+- Caldav: encode @ in path only for iCal 5 (OSX Lion)
+- Caldav: implement supported-report-set
+
+Enhancements:
+- Do not always log stacktrace in handleNetworkDown
+- Fix IllegalArgumentException in fixClientHost when scheme is null
+- Temporary fix: log exception stack trace in handleNetworkDown
+- Temporary fix: log exception in handleNetworkDown
+- Another init script
+- Small failover fix
+- Improve client host update fix
+- Test various event count
+
+IMAP:
+- IMAP: implement DRAFT and UNDRAFT search conditions, fix 3396248
+- IMAP: fix failover for misconfigured Exchange server, replace host name in url
+- IMAP: fix regression in EwsExchangeSession.createMessage
+- IMAP: Fix 3383832, set ItemClass to send read receipt over EWS to avoid ErrorObjectTypeChanged 
+
+EWS:
+- EWS: Allow null value in StringUtil.decodeUrlcompname
+- EWS: use isrecurring with Exchange 2010 and instancetype with Exchange 2007
+- EWS: revert 3317867 XML1.1 header workaround to fix 3385308
+
+DAV:
+- Dav: check checkPublicFolder calls
+- Dav: Avoid returning null in getCmdBasePath
+
+Documentation:
+- Doc: fix OSX iCal setup documentation for OSX Lion
+
+LDAP:
+- LDAP: send error on DIGEST-MD5 bind request
+
+** DavMail 3.9.3 released **
+New release with improved iCal 5 (OSX Lion) support, partial VTODO conversion to Outlook tasks
+ and many other bugfixes including event move item url encoding and event filter over EWS fix
+
+Caldav:
+- Caldav: new fix for @ encoding
+- Caldav: fix regression, do not encode @ in calendar-user-address-set
+- Caldav: fix principal-URL response
+- Caldav: encode @ in current-user-principal
+- Caldav: force @ encode to %40 for iCal 5
+- Caldav: new CRLF in urlcompname patch for EWS, use _x000D__x000A_ as encoded value
+- Caldav: implement merged folder ctag over WebDav
+- Caldav: fix noneMatch handling over WebDav
+- Caldav: implement tasks delete over WebDav
+- Caldav: implement tasks support over WebDav
+- Caldav: send principal-URL for OSX Lion
+- Caldav: first duedate implementation on tasks, fix delete task and concat ctag to detect changes on both calendar and tasks folders
+- Caldav: implement percent complete and status VTODO updates
+- Caldav: implement task support over EWS
+- Caldav: decode destination path on move
+- Caldav: encode LF to %0A in urlcompname
+- Caldav: check Depth before search
+- Caldav: Task folder flag
+- Caldav: move remove quotes for Evolution to EWS mode only
+- Caldav: fix regression on iCal FreeBusy handling
+- Caldav: implement PROPFIND on single item
+- Caldav: remove quotes on etag for Evolution
+- Caldav: first Task (VTODO) implementation step
+- Caldav: allow infinity as Depth value
+
+Enhancements:
+- Fix DoubleDotInputStream
+- Improve system proxies and move item logging
+- Refactor buildSessionInfo to use /public first and mailbox path as failover for galfind requests
+- Fix bug in removeQuotes
+- Fix 3315942, patch cleanup
+- Fix server certificate label
+- Fixes from audit
+- Upgrade SWT to 3.7
+- Fix 3315942, merge patch provided by Jeremiah Albrant: Ask user to select client certificate
+- Improve message download progress logging, switch icon every 100KB
+- Remove unused SwtAwtEventQueue class
+- Implement davmail.smtpSaveInSent option and reorganize tabs
+- Fix 3153691: Username with apostrophe
+- Patch by Manuel Barkhau: exclude private events flag
+- Reformat and fixes from audit
+
+EWS:
+- EWS: new recurring event filter implementation, exclude recurrence exception in results
+- EWS: fix new Exchange 2010 ItemId length support
+- EWS: Fix for some Exchange 2010 ItemIds different length
+- EWS: workaround for Exchange bug, replace xml 1.0 header with xml 1.1 and log message download progress
+- EWS: implement gzip encoding on response
+
+Dav:
+- Dav: update httpClient host after login
+
+IMAP:
+- IMAP: need to include base folder in recursive search, except on root
+- IMAP: Fix 3151800, force UTF-8 in appendEnvelopeHeaderValue
+
+Documentation:
+- Doc: Add davmail.smtpSaveInSent description in doc
+
+
+** DavMail 3.9.2 released **
+This release includes some documentation updates, implement IMAP Recent flag, 
+Caldav support enhancements, 64 bits wrapper on windows, hanging issue with SWT 
+on Linux 64 and many other bugfixes.
+
+LDAP:
+- LDAP: cancel search threads on connection close
+
+Enhancements:
+- Adjust system proxy log statement
+- Jsmooth patch with 64 bits skeletons
+- Additional statement on proxy load
+- SWT: register error handler early
+- Serialize session creation in workstation mode to avoid multiple OTP requests
+- SWT: register error handler to avoid application crash on concurrent X access from SWT and AWT
+- Revert LookAndFeel changes, switch to System.setProperty to set default LAF
+- SWT: make sure we don't start AWT threads too early
+- Update Jsmooth patch with 64bits exe support
+- SWT: delayed AWT frames creation to reduce memory usage
+- Experimental 64 bits windows exe
+- Add a log file size field in UI
+
+DAV:
+- Dav: back to old path in Destination header behavior
+- DAV: switch icon on large message download
+- Dav: Log message download progress
+- Dav: new patch to reset session timeout with a GET method on /owa/
+- Dav: experimental, try to reset session timeout with a GET method
+- Dav: do not try property update failover on 507 SC_INSUFFICIENT_STORAGE
+
+OSX:
+- OSX: Avoid sending empty message to Growl
+
+IMAP:
+- IMAP: test custom header search
+- IMAP: workaround for Exchange 2003 search deleted support
+- IMAP: fix 3303767, do not send line count for non text bodyparts
+- IMAP: another fix for 3297849, ENVELOPE formating error/bogus quotes
+- IMAP: fix 3297849, ENVELOPE formating error/bogus quotes
+- IMAP: Fix nullpointer in broken message handling
+- IMAP: fix infinite loop detection
+- IMAP: detect infinite loop on the client side
+- IMAP: implement Recent flag on new messages based on read flag and creation/modification date
+- IMAP: fix 3223513 default flags on append
+
+Documentation:
+- Doc: How to run multiple instances of DavMail
+- Doc: FAQ note, iCal does not support folder names with spaces or special characters
+- Doc: Add reference to default windows domain setting in FAQ
+- Doc: additional Thunderbird and DavMail review
+- Doc: add Duplicate messages in Sent folder FAQ entry
+- Doc: add Piwik code to DavMail site
+- Doc: New (french) review
+- Doc: document custom certificate authority handling
+- Doc: improve initial setup documentation
+- Doc: describe the usual paths to use in OWA url field
+- Doc: update imapAutoExpunge flag doc
+- Doc: update roadmap
+- Doc: document public folder access in Lightning
+
+Caldav:
+- Caldav: allow tab as folding character, see RFC2445
+- Caldav: Fix NullPointerException in getTimezoneIdFromExchange
+- Caldav: instancetype is null on Exchange 2010, switch to isrecurring in EWS FindItem
+- Caldav: Disable schedule-inbox for all Lightning versions
+- Caldav: prepare xmoz custom property support over DAV
+
+EWS:
+- EWS: fix UID and RECURRENCE-ID, broken at least on Exchange 2007 with recurring events
+- EWS: fix 3105534 GetUserAvailability default timezone compatibility with Exchange 2010
+- EWS: new failovers on Timezone settings: use davmail.timezoneId setting or default to GMT Standard Time
+
+
+** DavMail 3.9.1 released **
+Another bugfix release, mainly on EWS Caldav support (fix 404 not found).
+Also implemented Microsoft Forefront Unified Access Gateway support.
+
+Documentation:
+- Doc: additional FAQ entry on shared calendars
+
+Caldav:
+- Caldav: first check that email address is valid to avoid InvalidSmtpAddress error on FreeBusy request and new timezone name mapping
+- Caldav: New fix for fix 3190219, regression on quote encoding since 3165749 fix
+- Caldav: rethrow SocketException to avoid event not available on client connection close or DavMail listener restart
+- Caldav: Fix timezone name
+- Caldav: fix 3190219, regression on quote encoding since 3165749 fix
+
+Exchange Web Services:
+- EWS: fix 3190774, LDAP galfind email address handling, use Mailbox value instead of EmailAddress1/2/3
+- EWS: fix NullPointerException in item getContent
+- EWS: fix 404 not found with Exchange 2010 calendars
+
+Enhancements
+- Convert shell script to unix LF
+- Implement Microsoft Forefront Unified Access Gateway logon form compatibility
+
+IMAP:
+- IMAP: fix 3201374 envelope superflous space
+- IMAP: fix LOGOUT implementation to improve SquirrelMail compatibility
+
+OSX:
+- OSX: Avoid sending null message to Growl
+
+
+** DavMail 3.9.0 released **
+Making progress towards 4.0 and full EWS support, some issues remaining on recurring
+events. This is mainly a bugfix release, with some Caldav enhancements, huge memory usage fix
+on IMAP and a workaround for Linux 64 bits futex issue (deadlock on first connection).
+
+POP:
+- POP: test new double dot implementation
+- POP: Fix from Stefan Guggisberg, handle invalid CR or LF line feeds in DoubleDotOutputStream
+
+Caldav:
+- Caldav: additional timezone names in table
+- Caldav: 3132513, implement well-known url, see http://tools.ietf.org/html/draft-daboo-srv-caldav-10
+- Caldav: implement a new setting to disable task (VTODO) support: davmail.caldavDisableTasks and probably exclude most broken events
+- Caldav: throw exception on empty event body (EWS)
+- Caldav: fix multivalued param support in VProperty and always quote CN values
+
+Documentation:
+- Doc: Update release guide
+- Doc: Additional FAQ entry on public calendar access with iCal
+- Doc: Add Manchester wiki review
+
+LDAP:
+- LDAP: dump BER content on error
+- LDAP: fix 3166460, do not fail on NOT (0xa2) filter
+
+Bugfix:
+- New workaround for bug 3168560, load system proxy settings in static block
+- Fix 3161913 klauncher says davmail.desktop misses trailing semicolon
+- Restore stax-api jar for Java 1.5 compatibility
+- Fix 3150426 huge memory usage with IMAP
+- Workaround for bug 3168560, synchronize system proxy access
+- New NTLMv2 patch: provide fake workstation name and adjust Type3 message flags
+
+EWS:
+- EWS: Fix 3165749, exception with quotes in meeting subject and EWS
+
+Webdav:
+- Dav: log search response count
+
+
+** DavMail 3.8.8 released **
+Yet another bugfix release with many EWS support enhancements and fixes,
+many documentation improvements (still need to update Thunderbird/OSX instructions
+to latest versions though).
+
+Documentation:
+- Doc: change Maven site plugin version
+- Doc: additional external links
+- Doc: Add anew reviews page
+- Doc: Update POM and release guide
+- Doc: move advanced settings to a separate page to keep getting started page simple
+- Doc: add SWT bug reference to FAQ, on Ubuntu, notify text conflicts with default theme
+- Doc: full iPhone setup instructions
+- Doc: update war deployment description
+- Doc: Additional smartcard PKCS11 setup instructions with NSS and Coolkey examples
+
+Caldav:
+- Caldav: Additional timezone mappings
+- Caldav: workaround for Exchange 2010 bug, \n in timezone name generates invalid ICS content
+- Caldav: improve timezone rename error message
+- Caldav: fix floating timezone in iCal: rename TZID for maximum iCal/iPhone compatibility
+- Do not send Exchange 2003 appointment creation request to Exchange 2007
+
+EWS:
+- EWS: return HttpNotFoundException on event not found to trigger Lightning workaround
+- EWS: fix instancetype field definition: Integer instead of String (fix Caldav filter over EWS)
+- EWS: improved email/alias failover fix
+- EWS: fix regression in comment
+- EWS: additional failover mail build on logon form failure
+- EWS: store X-MOZ-SEND-INVITATIONS property to fix no notification issue with Lightning
+- EWS: fix Caldav inbox handling over EWS
+- EWS: improve timezone handling
+- EWS: Update Field list
+- EWS: fix 3098008, implement result paging to handle message folders with more than 1000 messages 
+- EWS: exclude non message types from searchMessages
+- EWS: fix email mapping on LDAP response
+- EWS: add BusinessCountryOrRegion contact field
+
+Enhancements
+- Additional session create log statement
+- New multiple user fields implementation: expect userid|username as login value
+- Improve connection pool handling: do not pool simple checkConfig and getVersion connections.
+- Implement OTP form with multiple username fields (username and userid)
+- Contribution from Geert Stappers: start/stop script
+- Improve NTLM authentication detection
+- Always use private connection manager to avoid session conflict
+- Fixes from audit
+- Update javamail to 1.4.3
+- Adjust Mime decoder settings (fix)
+- Adjust Mime decoder settings
+- Workaround for space in cookie name
+- Use a_sLgnQS instead of a_sLgn first to support new OWA 8.3.83.4
+- Additional NTLM flags to match Firefox flags
+- Add UTF-7 support with jcharset
+- Failover for misconfigured Exchange server, replace host name in url
+
+SMTP:
+- SMTP: fix 3132569, always remove From header to avoid 403 error on send
+- SMTP: workaround for misconfigured Exchange servers: failover on Draft message creation through properties. Warning: attachments are lost
+
+IMAP:
+- IMAP: Fix 3137275 Imap header fetch bug
+
+WebDav:
+- Dav: make sure Destination contains full url and not only path, may fix SMTP send and IMAP copyMessage on Exchange 2003
+
+Carddav
+- Carddav: Update contact test
+
+** DavMail 3.8.7 released **
+Bugfix and performance release with new Woodstox parser to reduce memory
+footprint in EWS mode, more Caldav broken events fixes and IMAP regression
+fixes.
+
+Documentation:
+- Doc: Update Carddav setup doc
+- Doc: ssl setup doc update from kerstkonijn
+
+Enhancements
+- Unzip contribs content
+- Update rpm ant task parameters to create valid rpm package
+- Workaround for malformed cookies with space in name
+- From Geert Stappers: add includeantruntime="false" to avoid ant 1.8 warning
+- Workaround for invalid redirect location
+- Improve error handling: detect redirect to reason=0 as session expired
+- Suggestion from Geert Stappers: add svn:ignore property
+- RPM spec from Marko Myllynen
+
+Caldav:
+- Caldav: Fix timezone support with Exchange 2010 SP1
+- Caldav: use rebuild event from MAPI properties failover in all error cases
+- Caldav: add requestFocus() to bring notification dialog to foreground
+- Caldav: added edit notifications checkbox in settings frame
+
+IMAP:
+- IMAP: include current folder in recursive search
+- IMAP: encode source path in copyMessage
+- IMAP: new test case to show Thunderbird perf issue
+- IMAP: Fix 3109303 Handle null string during mail fetch
+- IMAP: fix nullpointerException in header fetch
+- IMAP: fix 3106803, IMAP client stuck scanning Inbox, fix header and body fetch in same request
+- IMAP: throw error on 440 Login Timeout to avoid message corruption
+
+LDAP:
+- LDAP: do not log error on OSX groups request
+
+EWS:
+- EWS: Upgrade woodstox version to use enhanced base64 conversion (reduced memory usage)
+- EWS: allow autodiscover after authentication failure
+- EWS: fix contact email update
+
+OSX:
+- OSX: search and replace on existing file, spotted by Geert Stappers
+
+** DavMail 3.8.6 released **
+First release with automatic EWS mode detection, also includes many bugfixes
+on LDAP support over EWS, IMAP enhancements, Exchange 2010 SP1 cookie bug workaround
+and a brand new UI frame to let users edit Caldav notifications.
+
+LDAP:
+- LDAP: fix galfind search: add uid in response and use cn in fullsearch filter
+- LDAP: additional EWS attributes
+- LDAP: additional attributes for iPad
+
+Enhancements:
+- Fix 3103349: Cannot login if display name contains [brackets], regression after first patch
+- Fix 3103349: Cannot login if display name contains [brackets]
+- configFilePath is null in some test cases
+- Added passcode as token field for RSA support
+- Add DavMail version in welcome IMAP and SMTP header
+- Update test case
+- Handle exceptions on invalid UTF-8 characters or unexpected content triggered by XmlStreamReader.getElementText (based on patch 3081264)
+- Add exchange 2010 PBack cookie in compatibility mode
+- Novell iChain workaround
+
+POP:
+- POP: add version in welcome banner
+
+Caldav:
+- Caldav: Fix bug in Dav mode with broken events dtstart -> dtend
+- Caldav: fix french notification message
+- Caldav: protect ':' in VCALENDAR property params
+- Caldav: initial edit notification implementation
+- Caldav: Create fake DTEND on broken event
+- Caldav: fix nullpointer in VCalendar on missing DTEND
+- Caldav: implement main calendar folder rename
+- Caldav: use i18n calendar name as display name for iCal
+- Caldav: avoid renaming default calendar to null
+
+EWS:
+- Ews: improve ResolveNames implementation, parse addresses and phone attributes
+- EWS: implement failover on OWA authentication failure (e.g. with outlook.com)
+- Ews: improve invalid item in calendar error handling
+- EWS: improve resolveNames logging
+- EWS: add enableEws flag in UI settings frame
+- EWS: automatically detect Webdav not available and set davmail.enableEws flag
+
+IMAP:
+- IMAP: failover in message copy on 404 not found
+- IMAP: Fix append with no optional parameters
+- IMAP: additional test cases
+- IMAP: fix from kolos_dm: implement fake line count in BODYSTRUCTURE and [] block in IMAPTokenizer
+- IMAP: fix from kolos_dm: implement attachment name in BODYSTRUCTURE
+- IMAP: improve logging, do not log message content on 404 or 403
+- IMAP: fix from kolos_dm: In-Reply-To is not email header and unfold header to remove CRLF in ENVELOPE response
+- IMAP: merge fix from Kolos, search command with message sequence set
+- IMAP: implement index (non uid) COPY
+- IMAP: workaround for broken message (500 error), rebuild mime message from properties
+- IMAP: send error on idle command without selected folder (Outlook)
+
+Documentation:
+- Doc: fixes and updates on ssl setup and build
+- Doc: update roadmap
+- Doc: Update architecture image
+- Doc: update ssl server certificate doc
+- Doc: Document PKCS12 self signed certificate creation to enable SSL in DavMail
+- Doc: iPhone screenshots
+
+SWT:
+- SWT: Custom AWT event queue to trap X errors and avoid application crash
+- SWT:enable debug mode
+
+** DavMail 3.8.5 released **
+Includes much progress on Caldav over EWS support, a few regression fixes 
+and improved IMAP BODYSTRUCTURE implementation for complex messages.
+
+Bugfixes:
+- Fix regression in Exchange 2007 over Dav session
+
+Enhancements:
+- Detect and submit language selection automatically
+- More fixes from audit
+- Fixes from audit
+- Restore cookies on error
+- Improve buildSessionInfo failover
+- Fix ssl trustmanager error handling
+- Enable Webdav/Galfind failover on Exchange 2007
+- Workaround for basic authentication on /exchange and form based authentication at /owa
+
+Caldav:
+- Caldav: detect invalid events with empty dtstart property
+- Caldav: implement mozilla alarm flags X-MOZ-LASTACK and X-MOZ-SNOOZE-TIME over EWS
+- Caldav: EWS, rebuild attendee list from properties
+- Caldav: test principal request
+- Caldav: fix 3067915 getRangeCondition too restrictive
+- Caldav: implememnt Busy flag over EWS and refactor create code
+- Caldav: fix create allday event over EWS and check if current user is organizer
+- Caldav: Fixed regression in allday event handling
+- Caldav: improve EWS implementation
+- Caldav: improve timezone error handling
+- Caldav: remove empty properties
+- Caldav: avoid invalid X-CALENDARSERVER-ACCESS and CLASS
+- Caldav: avoid empty X-CALENDARSERVER-ACCESS and CLASS
+- Caldav: reinsert the deleteBroken check
+- Caldav: fix VProperty parser
+- Caldav: additional VCalendar properties for rebuilt item: VALARM (reminder)
+- Caldav: additional VCalendar properties for rebuilt item: RRULE, EXDATE, CLASS
+- Caldav: failover for broken event, rebuild VCalendar content from raw properties
+- Caldav: fix 3063407, regression in sendPrincipal
+
+Carddav:
+- Carddav: fix null value in email address
+- Carddav: fix email address handling over EWS
+
+Exchange Web Services:
+- EWS: fix 3047563 double inbox
+- EWS: more caldav ews fixes
+
+SMTP:
+- SMTP: rewrite getAllRecipients to disable strict header check
+- SMTP: new try at encoding fix: set mailOverrideFormat and messageFormat
+
+Documentation:
+- Upgrade maven site-plugin and update release guide
+
+IMAP:
+- IMAP: fix 3072497 Imap server too picky about case
+- IMAP: improve BODYSTRUCTURE implementation, make it recursive
+- IMAP: implement partial header fetch
+
+LDAP:
+- LDAP: new attribute mapping
+- LDAP: cache current hostname value in sendComputerContext to improve iCal address completion performance
+- LDAP: additional ignore attributes
+- LDAP: add gidnumber to attribute ignore list
+- LDAP: fix regression on iCal 3 search completion
+
+SWT:
+- SWT: allow libswt-gtk-3.6-java on debian, available from ppa:aelmahmoudy/ppa
+
+** DavMail 3.8.4 released **
+Yet another bugfix release with more regressions fixes on SMTP,
+a few LDAP fixes and a caldav timezone update. 
+
+Documentation:
+- Doc: Update release guide
+- Doc: Update swt version in maven pom
+
+SMTP:
+- SMTP: try to force IMS encoding mode according to message contenttype
+- SMTP: switching back to Draft then send mode over DAV for calendar messages
+- SMTP: switching back to Draft then send mode over DAV
+- SMTP: new duplicate message-id detection implementation, no need to search Sent folder
+
+LDAP:
+- LDAP: improve EWS filter support
+- LDAP: another gallookup detection fix to improve address completion in thunderbird
+
+Carddav:
+- Carddav: improve OSX client detection
+
+Enhancements
+- Fixes from audit
+
+Caldav:
+- Caldav: accept login as alias in caldav principals path
+- Caldav: basic move item implementation
+- Caldav: adjust Lightning bug workaround
+- Caldav: yet another timezone fix, adjust Outlook created event time before allday conversion
+- Caldav: fix regression on meeting response subject
+
+** DavMail 3.8.3 released **
+Another bugfix release with major regressions fixed:
+missing calendar meeting messages and delivery status notification on
+some external addresses. Also includes improved autodiscover support.
+
+Enhancements:
+- Disable broken rpm generation
+- Fix test cases
+- Upgrade swt to 3.6
+- workaround for TLS Renegotiation issue, 
+  see http://java.sun.com/javase/javaseforbusiness/docs/TLSReadme.html    
+- Switch back to StreamReader.next instead of nextTag
+- Fix autodiscover support
+- Merge patch 3053324: Implement per service SSL flag (patch provided by scairt)
+- Fix XMLStreamUtil regression
+- Refactor XMLStreamUtil
+
+Exchange Web Services:
+- EWS: improve autodiscover implementation
+- EWS: fix possible NullPonterException
+- EWS: implement autodiscover to find actual EWS endpoint url
+
+Caldav:
+- Caldav: extend Lightning broken tests to all 1.* versions
+- Caldav: switch back to contentclass to get calendarmessages over webdav
+- Caldav : revert previous changes and fix meeting cancel support (IPM.Schedule.Meeting.Canceled)
+- Caldav: move to trash on processItem
+- Caldav: fix request parser regression on nextTag
+- Caldav: improve filter handling, support VTODO/VEVENT comp-filter
+- Caldav: make timezone name retrieval more robust
+
+SMTP:
+- SMTP: make duplicates check optional with davmail.smtpCheckDuplicates setting
+- SMTP: always remove From header with Exchange 2007 and 2010
+- SMTP: Improve message on MAIL FROM without authentication
+- SMTP: experimental, advertise 8BITMIME
+
+IMAP:
+- IMAP: implement shared mailbox access
+
+Documentation:
+- minor doc fix
+- Doc: Additional Exchange Webdav setup documentation
+- Add ohloh widget on home page
+- Doc: a few doc fixes and update roadmap
+
+** DavMail 3.8.2 released **
+Bugfix release with improved Exchange 2010 IMAP support, CardDav fixes and
+improved error handling
+
+Enhancements:
+- Disable SWT on Java 7
+- Update debian package description and categories
+- fix 2995990: Add support for already authenticated users
+- Fix missing hide password in log over IMAP
+- More session creation enhancements, fix public folder test when /public is 403
+- Refactor email and alias retrieval: always use options page with Exchange 2007
+- Improve socket closed error handling
+- Try default form url on authentication form not found
+- Add Java Service Wrapper contribution from Dustin Hawkins
+
+Caldav:
+- Caldav: move delete broken event logic to DavExchangeSession
+- Caldav: delete broken events when davmail.deleteBroken is true
+- Caldav: improve event logging, include subject
+
+IMAP:
+- IMAP: handle 507 InsufficientStorage error
+- IMAP: fix regression in NOT DELETED filter
+
+Documentation:
+- Doc: Update OSX directory setup documentation
+
+DAV:
+- DAV: Encode apos in urlcompname used in DAV search request
+
+EWS:
+- EWS: fix single message in folder with Exchange 2010 bug
+- EWS: implement loadVTimezone for Exchange 2010
+
+SMTP:
+- SMTP: fix regression on bcc handling
+- SMTP: convert Resent- headers, see 3019708
+
+LDAP:
+- LDAP: avoid galLookup in iCal searches
+
+Carddav:
+- Carddav: Fix email update over EWS
+
+** DavMail 3.8.1 released **
+Includes a full refactoring of Vcalendar content handling, much progress on
+Exchange Web Services support, LDAP optimizations and many other bufixes.
+
+Exchange Web Services:
+- EWS: hard method: delete/create on update
+- EWS: Fix DeleteItem for CalendarItem
+- EWS: implement loadVtimezone, get user timezone id from OWA settings
+- EWS: Fix FieldURIOrConstant test
+- EWS: separate domain from userName in NTLM mode
+- EWS: MultiCondition galFind
+- EWS: implement basic galFind search
+- EWS: implement resolvenames response parsing
+- EWS: fix subfolder search on Exchange 2010
+- EWS: implement user availability (freebusy) and shared folder access
+- EWS: implement sendEvent
+- EWS: force urlcompname only on create
+- EWS: implement ResolveNames method
+- EWS: Apply workaround to events
+- EWS: workaround for missing urlcompname on Exchange 2010, use encoded ItemId instead
+- EWS: rename equals to isEqualTo and format search date
+- EWS: dynamic version detection
+- EWS: Exchange 2010 message handling
+- EWS: Exchange 2010 folder handling
+- EWS: Exchange 2010 compatibility: add test cookie, access /ews/exchange.asmx endpoint
+
+Caldav:
+- Caldav: Fix missing TZID in DTSTART from iPhone
+- Caldav: return reoccuring events on time-range request
+- Caldav: Fix METHOD on create from iPhone
+- Caldav: need to encode colon (:) in urlcompname search, implement a last failover on item search
+- Caldav: implement 2899430, change the subject line when replying to invites
+- Caldav: workaround for Lightning 1.0b2 bug
+- Caldav: disable caldav inbox with Lightning 1.0b2
+- Caldav: fix regression in fixVCalendar (missing organizer)
+- Caldav: skip empty lines
+- Caldav: Fix regressions in Vcalendar handling
+- Caldav: fix nullpointer in VCalendar
+- Caldav: fix regressions and do not filter on outlookmessageclass
+- Caldav: major refactoring of event content handling and notifications
+- Caldav: switch to new VCalendar parser/patcher
+- Caldav: implement VALARM in VCalendar
+- Caldav: more vcalendar patches
+- Caldav: start new VCalendar fixICS implementation
+- Caldav: call fixICS on download
+- Caldav: reenable Lightning 1.0b2 bug workaround
+- Caldav: failover for 404 not found on items containing '+' in url, search item by urlcompname to get permanenturl
+
+LDAP:
+- LDAP: create a separate thread only for person/contact searches
+- LDAP: implement galFind MultiCondition over webdav and improve search by mail
+- LDAP: need to galLookup when search attribute is not in galfind result
+- LDAP: another search attribute mapping fix
+- LDAP: code cleanup and some galfind search fixes
+- LDAP: fix 3043659, include entries starting with Z
+- LDAP: Improve sizeLimit handling and ignore attributes
+- LDAP: a few more attribute fixes
+- LDAP: move galLookup to DavExchangeSession
+- LDAP: progress on EWS LDAP implementation and refactoring
+- LDAP: fix regression on OSX directory request on iCal start: filter invalid imapUid condition
+- LDAP: use sizeLimit in contactFind
+- LDAP: Fix OSX directory search on uid
+
+Enhancements:
+- Improve error handling
+- Add custom cookie policy to support extended host name
+- Fixes from audit
+
+Bugfixes:
+- Fix regression in getAliasFromMailboxDisplayName
+- Deb: Fix regression in debian desktop link
+
+Dav:
+- Dav: disable galFind on error
+
+SMTP:
+- SMTP: compare actual email address, not email with alias
+- SMTP: no need to remove From header with new sendMessage implementation
+
+SWT:
+- SWT: fix 2992428, hide instead of dispose on close
+
+Carddav:
+- Carddav: refactor VCard handling to merge with VCalendar code
+- Carddav: disable contact picture handling on Exchange 2007
+- Carddav: implement range search
+
+** DavMail 3.8.0b2 released **
+Fixes the most obvious regressions in 3.8.0b1 and some documentation
+updates on Carddav. Note for EWS only users: add davmail.enableEws=true in
+davmail.properties
+
+Caldav: fix sendEvent regression, conflict on outbox notifications
+Caldav: improve HttpNotFound message
+Caldav: Refactor getItem
+Doc: fix carddav thunderbird doc
+Doc: update left menu
+Doc: set source encoding to UTF-8 in maven pom
+Doc: update roadmap
+Doc: Basic OSX setup instructions
+Doc: thunderbird carddav setup with SOGO connector
+Doc: Update homepage and project description
+Carddav: additional TEL properties
+Caldav: fix MKCALENDAR http status code: return 201 instead of 207
+Carddav: add fburl field
+Caldav: Another request parsing bug: handle empty elements
+Caldav: fix regression in REPORT requests parsing
+
+** DavMail 3.8.0b1 released **
+First public release after major refactoring to implement Exchange 2010 and Exchange 2007 without
+Webdav support. This implementation is based on Exchange Web Services. EWS support is not yet
+complete: global address list search and free/busy support is missing.
+This release includes the new Carddav service sponsored by French Defense / DGA through 
+project Trustedbird. OSX notifications will now use Growl if available.
+
+Carddav:
+- Carddav: another urlcompname encoding fix
+- Carddav: generate OSX compatible VCARD photo and change addressbook-home-set with OSX Address Book
+- Carddav: use new ExchangePropPatchMethod in full contact create/update
+- CardDav: use new ExchangePropPatchMethod to create haspicture boolean property
+- Carddav: improve error logging on photo update failure
+- Carddav: use email1 as default email on update
+- Carddav: fix multiple mail MAPI properties handling
+- Carddav: fix GET request on folder support for SOGO
+- Carddav: encode contact picture url
+- Carddav: return 404 not found on missing folder
+- Carddav: fix line folding in generated VCARD
+- Carddav: Fix regression in single value multiline properties
+- Carddav: add gender property
+- Carddav: adjust bday to timezone
+- Carddav: another anniversary property candidate
+- Carddav: Add Anniversary support
+- Carddav: Fix bday generation
+- Carddav: fix iPhone BDAY parser
+- Carddav: adjust fields accepting multiple values
+- Carddav: fix semicolon encoding in compound value
+- Carddav: workaround for iPhone categories encoding
+- CardDav: do not encode simple (not compound) properties
+- Carddav: fix regression in VCardWriter
+- Carddav: always encode values
+- Carddav: protect semicolon
+- Carddav: iPhone personalHomePage support
+- Carddav: ignore key prefix in VCARD
+- Carddav: resize contact picture
+- Carddav: Fix lower case param names
+- Carddav: add contact create or update log statement
+- Carddav: handle param values as parameter list
+- Carddav: encode photo href
+- Carddav: fix regression on VCARD photo detection
+- Carddav: use urlcompname value instead of path to get contact details
+- Carddav: fix case insensitive param values
+- Carddav: add haspicture to test case
+- Carddav: Implement picture delete and private flag over EWS
+- Carddav: handle picture delete
+- Carddav: fix boolean field handling
+- Carddav: Remove missing properties on update
+- Carddav: implement CLASS (private) flag
+- Carddav: convert image to jpeg over EWS
+- Carddav: implement photo update over WebDav
+- Carddav: implement photo handling over EWS
+- Carddav: implement categories support in EWS mode
+- Carddav: implement categories
+- Carddav: get SMTP email address
+- Carddav: move value decoding back to VCardReader
+- Carddav: decode multiline values
+- Carddav: encode comma and \n in values
+- CardDav: make getContactPhoto more robust
+- Carddav: iPhone iOS4 compatibility
+- Carddav: implement contact photo support (readonly)
+- Carddav: implement quoted param value support
+- Carddav: bday, assistant, manager and spouse properties
+- Carddav: other address and homeposteofficebox properties
+- Carddav: instant messaging and role properties
+- Carddav: more properties
+- Carddav: Implement phone, address and email properties
+- Carddav: handle multiple values on a single line and add new properties
+- CardDav: fix contact folder path handling and add create contact unit test
+- Carddav: refactor Contact creation and create VCardReader
+- CardDav: move Contact getBody to ExchangeSession and add more attributes support
+- CardDav: map contact fields
+- CardDav: improve automatic address book setup for OSX
+- CardDav: implement OSX AddressBook requests: current-user-privilege-set property, current-user-principal on root request, addressbook-home-set on principal request, addressbook-multiget REPORT request with address-data response, urn:ietf:params:xml:ns:carddav namespace
+
+Enhancement:
+- Disable preemptive authentication when adding NTLM scheme
+- Fixes from audit
+- Force log file encoding to UTF-8
+- Add new davmail.logFileSize setting
+- Use linux friendly path separator in jsmooth config files
+- Fixes from audit
+- Major refactoring: use straight inpustream instead of reader everywhere
+- Disable ConsoleAppender in gui mode
+- Add missing Junit jar
+- Cleanup: System.setProperty of httpclient.useragent no longer needed
+- Improve item not found logging
+- Log gateway stop at info level
+- Improve empty keystore password handling to avoid NullPointerException
+- Fix 2999717 redirect console to /dev/null in desktop file
+
+Exchange Web Services:
+- EWS: fix urlcompname encoding issues
+- EWS: fix folder name ampersand encoding issue
+- EWS: return 403 forbidden on ErrorAccessDenied
+- EWS: xml encode values
+- EWS: use UTF-8 to decode request on error
+- EWS: send extended properties first on update
+- EWS: format datereceived date
+- EWS: fix bug in UnindexedFieldURI
+- EWS: update createMessage bcc handling to match sendMessage 
+- EWS: implement bcc support in sendMessage
+- EWS: implement send message (SMTP)
+- EWS: fixes from audit
+- EWS: fix CalendarItem creation, no need to wrap ics in a MIME message
+- EWS: implement calendar event create or update, processed field, subfolder path handling 
+- EWS: fix internaldate conversion
+- EWS: convert read flag to boolean and noneMatch/etag to detect create or update on items
+- EWS: use UnindexedFieldURI for read flag
+- EWS: fixes for Caldav and Carddav compatibility
+- EWS: fix folder id regression
+- EWS: fix country contact property mapping
+- EWS: implement getItem and various contact handling fixes
+- EWS: map all contact properties
+- EWS: implement more contact and event methods
+- EWS: implement copy method
+- EWS: datereceived flag support
+- EWS: handle bcc field
+- EWS: various flag handling fixes, implement message delete
+- EWS: implement getContent
+- EWS: fix iconIndex flag property
+- EWS: implement create and update message
+- EWS: fix single value in MultiCondition handling
+- EWS: rely on uid (PR_SEARCH_KEY) instead of permanentUrl to detect imap uid changes
+- EWS: implement searchMessages
+- EWS: fix bug in MultiCondition search
+- EWS: fix from audit
+- EWS: implement folder handling, including the new MoveFolderMethod
+- EWS: move mailbox folder urls to DavExchangeSession
+- EWS: use searchContacts in contactFind
+- EWS: fix regression in deleted flag handling
+- EWS: refactor contactFind, use new Condition API
+- EWS: still more WebDav code to DavExchangeSession
+- EWS: move more WebDav code to DavExchangeSession
+- EWS: Various fixes after refactoring on DASL request generation
+- EWS: in progress refactoring of contacts and events handling
+- EWS: implement folder ctag, remove deprecated foldername property
+- EWS: move WebDav message write and delete to DavExchangeSession
+- EWS: move WebDav code to DavExchangeSession
+- EWS: refactor IMAP search, use Conditions classes instead of string search filder
+- EWS: Use int values to create ExtendedFieldURI propertyTags
+- EWS: map folder path to and from IMAP
+- EWS: implement NotCondition and public folder access
+- EWS: implement IndexedFieldURI and InternetMessageHeader
+- EWS: refactor search to use classes instead of String filters
+- EWS: implement MultipleOperandBooleanExpression (And, Or, Not conditions)
+- EWS: refactor folder search, create abstract getFolder methods
+- EWS: start ExchangeSession refactoring to extract Dav calls
+- EWS: refactor options, use enums
+- EWS: implement basic SearchExpression restriction
+- EWS: Implement CreateFolder, DeleteFolder and CreateItem, refactor options
+- EWS: retrieve and decode MIME content
+- EWS: add standard field additional property, implement IncludeMimeContent in GetItem, add DeleteItemMethod
+- EWS: Generic item property mapping
+- EWS: refactor EWS code
+- EWS: experimental HttpClient based EWS methods
+
+Caldav:
+- Caldav: fix time-range filter support in EWS mode
+- Caldav: move calendar on displayname update
+- Caldav: partial MKCALENDAR implementation
+- Caldav: implement time-range request
+- Caldav: add missing dtstart field
+- Caldav: improve 404 error handling
+- Caldav: fix regression in processItem
+- Caldav: UTF-8 encode report body
+- Caldav: catch any exception in reportItems
+- Caldav: Process request before sending response to avoid sending headers twice on error
+- Caldav: Workaround for Lightning/1.0b2 href encoding bug in REPORT requests
+- Caldav: move processItem logic back to CaldavConnection
+- Caldav: Workaround for emClient broken href encoding
+- Caldav: remove buildCalendarPath method
+- Caldav: allows mixed case contentType in event MIME message (fix Unable to get event error)
+- Caldav: fix 3014204 missing timezone
+- Caldav: fix 2902372 private flag handling undex iCal 4 (OSX 10.6 Snow Leopard)
+- Caldav: send current-user-principal on principals folder for iCal
+- Caldav: workaround for iCal bug: do not notify if reply explicitly not requested
+- Caldav: add CRLF after END:VCALENDAR to comply with RFC
+- Caldav: fix regression in getItem, allow urn:content-classes:calendarmessage contentClass
+- Caldav: Fix Carddav etag handling (additional Head request) and implement card delete
+- Caldav: Implement Carddav create (only a few attributes mapped)
+- Caldav: Implement basic Carddav search requests
+
+Dav:
+- Dav: more property update fixes
+- Dav: patch filter on invalid Exchange Webdav response
+- Dav: new ExchangePropPatchMethod to handle custom exchange propertyupdate and invalid response tag names
+- Dav: refactor getContentReader and fix regression on null date value
+- Dav: fix nullpointer in DavExchangeSession
+- Dav: handle null properties with new createMessage
+- Dav: another datereceived fix
+- Dav: switch back to DAV:uid, used mainly in POP service (case sensitive)
+- Dav: fix bug 3022451 in new search filter implementation with empty sub conditions
+- Dav: Add folder unit tests
+- Dav: add private and sensitivity fields
+- Dav: implement timezone mapping for Exchange 2007, should fix the allday issue with Outlook
+- Dav: use search expression to request ishidden
+- Dav: fix regression in deleteItem
+- Dav: fix regression 3020385 on folder handling
+- Dav: Refactor folder search to use searchItems
+- Dav: use Email1EmailAddress mapi property to get mail attribute, add uid attribute
+- Dav: fix from audit
+- Dav: add unit tests, move buildCalendarPath logic to getFolderPath
+
+Bug fixes:
+- Use private MultiThreadedHttpConnectionManager with NTLM to avoid persistent authentication on connection issues
+- Fix regression in AbstractConnection: return null instead of empty string on closed connection
+- Fix 3001579: improve NTLM support
+
+IMAP:
+- IMAP: add uidNext MAPI property (not available under Exchange 2003)
+- IMAP: fix deleted flag handling over Webdav
+- IMAP: fix flag handling in createMessage
+- IMAP: new seen flag test case
+- IMAP: fix regression on imap uid restore
+- IMAP: fix 3023386, support BODY.PEEK[1.MIME] partial fetch
+- IMAP: new unit tests and fix $Forwarded flag removal
+- IMAP: implement deleted/undeleted search as condition instead of post filter
+- IMAP: add IMAP unit test
+- IMAP: fix 3014787 remove property over WebDav
+- IMAP: implement last message (simple *) fetch range
+- IMAP: send required "* SEARCH" on empty search response
+- IMAP: Add a new hidden davmail.deleteBroken setting to delete broken messages
+- IMAP: implement a new imapAutoExpunge setting to delete messages immediately over IMAP
+
+SMTP:
+- SMTP: send message directly without creating a Draft message to preserve Message-id
+- SMTP: fix log message
+- SMTP: fix 3024482, avoid duplicate messages with gmail
+- SMTP: Fix DoubleDotInputStream pushback size
+- SMTP: last CRLF is not included in message content
+
+Documentation:
+- Doc: javadoc and code cleanup
+- Doc: fix default domain label
+- Doc: new FAQ entry on OSX auto start "Login Items"
+- Doc: typos fixes from Raphael Fairise
+- Doc: update release guide
+- Doc: add a new mail.strictly_mime FAQ entry to enable quoted-printable
+
+POP:
+- POP: fix regression in TOP command
+- POP: fix message termination, append CRLF only when necessary
+- POP: replace deprecated write method, use DoubleDotOutputStream instead
+- POP: allow space in username
+
+LDAP:
+- LDAP: fix contact attributes reverse mapping
+- LDAP: improve contact attribute mapping and add a few new properties
+- LDAP: fix attribute map
+- LDAP: fix regression after EWS refactoring
+- LDAP: use imap uid as ldap uid
+- LDAP: use PR_SEARCH_KEY instead of DAV:uid as uid string
+
+OSX:
+- Exclude growl from non OSX packages
+- Fix growl build project name
+- OSX: implement growl support
+- include jnilib in OSX package
+- libgrowl-0.2 with libgrowl.jnilib compiled on OSX Snow Leopard
+- set libgrowl version to 0.2
+- rename generated jar with version, exclude test classes and create Manifest with Michael Stringer author
+- Improve Growl exception handling, remove System.out and a few fixes from audit
+- Initial growl import from http://forums.cocoaforge.com/viewtopic.php?f=6&t=17320
+
+** DavMail 3.6.6 released **
+This release is mainly focused on IMAP enhancements, including IDLE (RFC2177)
+aka "Push Mail" support and other protocol compliance fixes, particularly on
+partial fetch. NTLMv2 is also supported thanks to the JCIFS library.
+
+Documentation:
+- Doc: update doc and roadmap
+- Doc: adjust settings message
+- Doc: improve server/client certificates description
+- Doc: new FAQ entry on message deleted over IMAP still visible through OWA
+- Doc: fix maven site generation
+
+IMAP:
+- IMAP: send BAD instead of BYE on exception
+- IMAP: fix 2992976, implement complex index and uid range in SEARCH
+- IMAP: Handle exception during IDLE
+- IMAP: add a new setting to enable/disable IDLE
+- IMAP: use getRawInputStream instead of writeTo to avoid MIME message changes, cache message body in SharedByteArrayInputStream
+- IMAP: poll folder every 30 seconds in IDLE mode, clear cached message
+- IMAP: implement IDLE extension (RFC2177)
+- IMAP: fix 2971184, do not decode content in partial fetch (replace getDataHandler with PartOutputStream)
+
+Enhancements:
+- Exclude redline lib from distribution packages
+- Use https in default Exchange url
+- Make sure log messages are not localized
+- Remove unused messageId field
+- Do not shutdown connection manager on restart
+- Allow Exchange server to use gzip compression
+- Sample SocketAppender configuration
+- Improve NTLM mode detection
+- JCIFS based NTLMv2 implementation
+- Hardcode /owa/ path in getAliasFromOptions and getEmailFromOptions for Exchange 2007, improve failure message
+- Improve xmlEncode, use compiled static patterns
+
+Caldav:
+- Caldav: fix 2992811, missing timezones
+- Caldav: fix 2991030 tasks disappeared
+- Caldav: add VTODO to supported-calendar-component-set response
+- Caldav: fix regression in getAllDayLine()
+- Caldav: make shared calendar test case insensitive
+- Caldav: 0 or no value in caldavPastDelay means no limit
+
+** DavMail 3.6.5 released **
+This release includes a major refactoring of the IMAP FETCH implementation
+to improve performance and provide RFC compliant partial fetch. The Carddav
+support sponsored by french DGA through project TrustedBird is now included
+in the roadmap. Private events filter on shared calendar is also available
+and DavMail can now retrieve proxy settings directly from system configuration.
+
+SMTP:
+- SMTP: implement AUTH LOGIN username (with optional initial-response, see RFC2554)
+
+IMAP:
+- IMAP: Keep a single message in MessageList cache to handle chunked fetch, reenable maxSize in ImapConnection.
+- IMAP: implement subparts partial fetch
+- IMAP: Fix message write, double dot only for POP, not IMAP
+- IMAP: Do not advertise not yet supported custom flags
+- IMAP: fix from audit
+- IMAP: major FETCH implementation refactoring, make code simpler and more efficient
+- IMAP: add BODY.PEEK[index] support
+- IMAP: improve partial fetch support
+- IMAP: fix 2962071, quote folder names in STATUS response
+- IMAP: allow partial part fetch
+- IMAP: fix regression on unknown parameter handling
+- IMAP: implement part fetch (BODY[1]) 
+- IMAP: detect unsupported parameter
+- IMAP: fix 2973213, escape quotes in subject
+- IMAP: fixes to improve JavaMail support
+
+Doc:
+- Doc: move CardDav reference before architecture schema
+- Doc: update project description in Maven pom and ant package
+- Doc: update project description and RoadMap, announce CardDav support sponsored by french DGA through project Trustedbird
+- Doc: update roadmap
+
+Enhancements:
+- Add a new setting to disable startup notification window (contribution from jsquyres)
+- Improve getAliasFromOptions to retrieve alias with custom dn
+- Workaround for NTLM authentication only on /public
+- Add a new setting to retrieve proxies from system configuration
+- Fix empty setting behavior: return null instead of empty string
+- Sort properties file
+- Fix new RPM ant task definition
+- Improve public folder url check
+- Experimental rpm package build
+
+Carddav:
+- Carddav: refactor folder handling code to prepart CardDav support
+
+Caldav:
+- Caldav: fix broken inbox, missing instancetype in search request and add is null in search query
+- Caldav: do not try to access inbox on shared calendar (to avoid 440 login timeout errors and session reset)
+- Caldav: exclude private events on shared or public calendar PROPFIND
+- Caldav: fix regression on invalid events handling, just warn on broken events
+- Caldav: drop timezone when converting allday events to client
+
+** DavMail 3.6.4 released **
+Well, yet another bugfix release, with improved IMAP support,
+SMTP enhancements to support Eudora, NTLM proxy authentication
+support and other bug fixes
+
+SMTP:
+- SMTP: fix 2953552, allow RSET in AUTHENTICATED state
+- SMTP: bug id 2953554, implement NOOP
+
+LDAP:
+- LDAP: Enable tray icon on LDAP connection
+
+Bug fixes:
+- Fix regression in 3.6.3: basic authentication broken in checkConfig
+- GUI: Fix client certificate setting switch
+
+Enhancements:
+- Change debian package dependence to accept openjdk-6-jre and libswt-gtk-3.5-java
+- Fix from audit
+- Improve log message on HTTP header error
+- Implement NTLM HTTP proxy support
+- Improve logging of expired sessions
+- Support multiple forms in form based authentication logon page
+- Catch error on SWT exit
+- Enable NTLM on Proxy-Authenticate return code with only NTLM available
+
+Documentation:
+- Doc: Document davmailservice.exe usage
+- Doc: Document Force ActiveSync setting in Getting Started
+- Doc: Add an FAQ entry on DavMail settings location
+- Doc: Update release notes and guide
+
+IMAP:
+- IMAP: new patch from Gellule to fix disappearing messages issue
+- IMAP: rethrow SocketException after error in handleFetch
+
+Caldav:
+- Caldav: new fix for invalid events
+- Caldav: add a hidden davmail.caldavDisableInbox to allow users to disable Caldav Inbox with Thunderbird 3 and Lightning
+- Caldav: improve broken events logging
+- Caldav: Follow redirects on GET with permanentUrl
+
+
+** DavMail 3.6.3 released **
+Another bugfix release, mostly documentation updates, some regressions
+in 3.6.2 in error handling fixed, a new IMAP workaround to completely
+hide the uid change issue, emacs IMAP support and new UI settings for
+previously hidden parameters.
+
+Bug fix:
+- Fix logging settings handling in webapp mode
+
+Enhancements:
+- Improve error handling: detect SocketException to avoid client socket closed errors
+- Implement file based (PKCS12 and JKS) client certificates in addition to smartcard support
+
+Documentation:
+- Doc: update roadmap
+- Doc: remove replace token and search page
+- Doc: added Gellule as Java Contributor
+- Doc: add a security section in the FAQ
+- Doc: update FAQ with Exchange prerequisites details
+- Document client keystore file settings
+
+IMAP:
+- IMAP: brand new IMAP uid workaround and refresh folder on Expunge from Gellule
+- IMAP: implement LIST "" "*%" for emacs
+- IMAP: another fix for the message uid bump issue
+- IMAP: fix 2934922, implement (NOT DELETED) in search filter
+- IMAP: extend thunderbird changed uid workaround to all contexts
+
+GUI:
+- Add new setting davmail.defaultDomain to set default windows domain
+- Prepare new advanced options
+
+Caldav:
+- Caldav: add davmail.forceActiveSyncUpdate option to the settings frame
+- Caldav: add davmail.caldavAlarmSound option to the settings frame (used to force conversion of Caldav alarms to AUDIO supported by iCal)
+- Caldav: fix 2884864, send notifications to all participants on CANCEL
+- Caldav: Fix invalid event handling, exclude events from returned list
+
+SMTP:
+- SMTP: implement RSET (reset) command to avoid connection timeout with Evolution
+
+** DavMail 3.6.2 released **
+New bugfix release, with improved OSX tray icon, Kontact
+support, a new workaround for thunderbird IMAP no message error,
+public folders on a separate server support, improved ActiveSync
+support and some documentation enhancements.
+
+LDAP:
+- LDAP: fix bug 2919463, escape quotes in search filter
+- LDAP: fix Kontact ldap filter parsing, allow LDAP_FILTER_PRESENT in subfilter
+
+Documentation:
+- Doc: fix script replace
+- Doc: new download and build pages
+- Doc: update roadmap
+- Doc: update doc
+- Doc: add search icon
+- Doc: Update roadmap
+- Doc: Add roadmap to site menu
+
+IMAP:
+- IMAP: workaround for thunderbird random issue with no message found, keep previous message list to cope with recent message uid change.
+- IMAP: try to support public folders hosted on a separate server (302 redirect on PROPFIND)
+- IMAP: fix date parsing error, see bug 2878289
+- IMAP: fix 2878289, implement extended MIME header search in http://schemas.microsoft.com/mapi/string/{00020386-0000-0000-C000-000000000046}/ namespace
+- IMAP: improve error logging on 500 internal server error
+- IMAP: Improve error handling, do not fail on message retrieval error, just send error message
+- IMAP: implement EXPUNGE untagged response on NOOP to avoid NO message not found on Exchange message message uid change
+- IMAP: implement RFC822.HEADER for Sylpheed
+
+Caldav:
+- Caldav: do not send fake inbox for public calendars to iCal
+- Caldav: id 2900599, implement optional attendees in notifications
+- Caldav: fix bug 2896135, iCal login fails at iCal startup
+- Caldav: Send root instead of calendar href as inbox to fix iCal regression
+- Caldav: Exclude events with a null instancetype and no ICS content
+- Caldav: Workaround for Lightning 1.0pre public calendar, send calendar href as inbox/outbox urls
+- Caldav: Convert DISPLAY to AUDIO only if user defined an alarm sound in settings (davmail.caldavAlarmSound)
+- Caldav: fix NullPointerException in notifications
+- Caldav: Fix bug 2907350, multiple calendar support issue with iCal
+- Caldav: another timezone fix
+- Caldav: Improve error handling on invalid events
+- Caldav: another timezone fix
+- Caldav: do not return invalid message content
+- Caldav: move failover for Exchange 2007 plus encoding issue to Exchange session
+- Caldav: a brand new ActiveSync fix, set contentclass to make ActiveSync happy, but also set PR_INTERNET_CONTENT to preserve custom properties. Also get etag from updated event.
+- Caldav: major refactoring, move all ICS methods to Event inner class
+- Caldav: fix bug 2902358, encode messageUrl in PropPatch with forceActiveSyncUpdate=true
+- Caldav: improve MIME message headers in createOrUpdateEvent
+
+Bugfixes:
+- Fix last open session failover: do not append @hostname when alias contains @
+- Revert to message url as default, use permanentUrl as failover
+- Always use NTCredentials for proxy authorization
+- Another NTLM fix: activate NTLM only on 401 unauthorized in executeGetMethod
+
+Enhancements:
+- Fix from audit (spelling errors)
+- Add search page and change default package name for default svn builds
+- Improve message logging
+- Fixes from audit
+- Additional Jsmooth settings
+- Force flags parameter to 4 in Form Based Authentication
+- Jsmooth patch to allow -X jvm options
+
+OSX:
+- OSX: replace inverted active icon
+- OSX: new Mac OS X only icons
+
+** DavMail 3.6.1 released **
+This is a bugfix release with fixed regressions from 3.6.0
+and a few enhancements from user feedback.
+
+Documentation:
+- Doc: switch download links to generic link
+- Doc: Update roadmap
+- Doc: add roadmap
+
+Bugfixes:
+- Fix regression in Form Based Authentication: detect Exchange 2007 UserContext cookie
+- Host is mandatory for NTLMScheme, get current hostname for proxy authentication
+
+Enhancements:
+- Experimental: reactivate NTLM authentication but leave authentication preemptive mode to allow basic authentication.
+- Move PKCS11 registration to a separate class to avoid ClassNotFoundException
+- Experimental OTP (token) based authentication
+- Vista png icons support for JSmooth
+- Fix from audit
+- New upload-dist ant task to upload new release files
+
+Caldav:
+- Caldav: failover for Exchange 2007 plus encoding issue, search event by displayname to get permanent Url
+- Caldav: Additional timezones
+- Caldav: Revert commit 765, VTODO events have a null instancetype
+- Caldav: additional timezone
+- Caldav: Remove MAILTO: in addition to mailto: in getFreeBusy
+- Caldav: Bug 2898469 do not UrlEncode draft url twice to avoid 404 not found on send event message
+
+** DavMail 3.6.0 released **
+This release contains a lot of enhancements, both bug fixes
+and new features from user feedback on 3.5.0, including improved
+Evolution LDAP support, LDAP abandon support (faster searches with
+Evolution and OSX), experimental windows service wrapper, improved
+form based authentication support and ENVELOPE IMAP command support.  
+I wish to thank Dan Foody for his valuable contributions on
+OSX Snow Leopard support (attendee completion in iCal and complex
+LDAP filters handling).
+Also added an architecture schema on DavMail home page to quickly
+describe DavMail features.
+
+LDAP:
+- LDAP: implement cn=* filter for Evolution
+- LDAP: run searches in separate threads to implement ABANDON, will make searches faster with some clients (Evolution and OSX address book)
+- LDAP: implement startsWith on Contact search, only objectclass=* is a full search
+- LDAP: fix for iCal4 attendee completion,  send localhost if current socket is local, else send fully qualified domain name
+- LDAP: major refactoring from Dan Foody to improve complex filters handling
+- LDAP: improve contact search, reencode uids to hex to avoid case sensitivity issues
+
+Documentation:
+- Doc: Set Dan Foody as main java contributor
+- Doc: improve DavMail logo
+- Doc: add new Logo, improve internet explorer compatibility
+- Doc: Add an architecture schema on site welcome page
+- Doc: Improve getting started documentation, explain Exchange 2003 and 2007 paths (/exchange/ and /owa/) 
+- Doc: fix site style
+- Doc: fix maven site title
+
+SMTP:
+- SMTP: fix by Marc Macenko, case sensitive RCPT TO: 
+- SMTP: allow lower case commands
+- SMTP: experimental: remove Content-Type on create message to avoid 406 not acceptable with some Exchange servers.
+
+Bugfixes:
+- Fix 2887947: Exchange server with a username hidden field
+
+Enhancements:
+- Check for released version in a separate thread and set timeout to ten seconds
+- Refactor message url encoding
+- Upgrade Jmooth wrappers, add -Xrs jvm option to davmailservice wrapper to avoid service stop on user session logout (http://sourceforge.net/projects/jsmooth/forums/forum/297041/topic/2370742)
+- Fix regression from revision 811
+- Refactor ExchangeSession, use StringUtil to simplify code
+- Remove username duplicate check, as formLogin now resets values before POST
+- Start refactoring: StringUtil class
+- Fix classpath in jsmooth wrappers to use new javamail
+- Allow custom form with userid/pw fields in form based authentication
+- Improve form based authentication, look for Exchange session cookies sessionid and cadata
+- Fix test
+- Upgrade JavaMail to 1.4.1
+- New create folder method
+- Fix FBA authentication, reset query string in getAbsoluteURI()
+- New abstract JUnit test case class
+- Detect redirect form instead of logon form, follow redirect to logon form
+- Add an upload-site ant task
+- Fixes from audit
+- Fix settings default values and update doc
+- Drop icon activity switches under 250ms to avoid fast flickering on OSX, add new switch icon in IMAP fetch iterations
+- Improve script based form redirect to handle more cases
+- Refactor ExchangeSession to allow independent session creation.
+- Allow directory in logFilePath settings, add /davmail.log suffix in this case
+- Allow follow redirects on /public GET requests
+
+Caldav:
+- Caldav: use permanenturl for Caldav to avoid encoding issues
+- Caldav: do not close connection on 401 authorization required, may help iCal authentication
+- Caldav: Additional Allday fix for Exchange 2007 and Outlook, implement a failover with a new davmail.timezoneId setting.
+- Caldav: fix regression on create event, missing CRLF in mime message
+- Caldav: Fix regression on public calendar folders linked to multiple calendar support for iCal
+- Caldav: use chunked response to send calendar folder content as ICS to avoid timeout
+- Caldav: Experimental GET ics on folder and fix regression on public folder access
+- Caldav: get current VTIMEZONE body from OWA to create Allday events compatible with Outlook. Users still need to select the same Timezone in Outlook and OWA.
+- Caldav: Fix Timezone value
+- Caldav: Create a new setting davmail.forceActiveSyncUpdate to let users choose to force ActiveSync event. Note: custom iCal or Lightning ICS properties are lost if this option is enabled.
+- Caldav: Some Exchange servers redirect to a different host for freebusy, use wide auth scope
+- Caldav: Another fix from Dan Foody: improve dumpICS debug option
+- Caldav: need to check session on each request, credentials may have changed or session expired
+- Caldav: fix regression after ActiveSync patch, PROPPATCH on contentClass removes all custom ICS properties
+- Caldav: improve getICSValue, do not return values inside VALARM section
+- Caldav: do not send events with a null instancetype (may be the cause of iCal failure)
+- CalDav: Send sub folders for multi-calendar support under iCal
+- Caldav: fix path translation to Exchange for calendars in sub folders under /calendar
+- Caldav: Added supported-calendar-component-set to calendar response
+- Caldav: added a debug trace when requested calendar is not user calendar (maybe shared, but often url mistake in Caldav client)
+- Caldav: fix Bug 2686125, PROPPATCH event after PUT to trigger activeSync PUSH, tested with iPhone 3 using activeSync
+
+IMAP:
+- IMAP: use permanenturl instead of href to avoid url encoding issues
+- IMAP: Revert convert absolute IMAP path to relative path, breaks Caldav
+- IMAP: Convert absolute IMAP path to relative path and detect ISA server cookie starting with cadata (instead of equals cadata)
+- IMAP: use upper case NIL in ENVELOPE
+- IMAP: improve MimeMessage handling, drop after fetch to avoid keeping full message in memory
+- IMAP: fix new ENVELOPE feature, must return encoded values
+- IMAP: implement store by id and ENVELOPE
+- IMAP: update message flag only if changed to avoid unneeded message uid bump, may fix Evolution and Apple Mail constant reload issue
+- IMAP: implement search by id
+- IMAP: send default BODYSTRUCTURE on MIME encoding error
+- IMAP: improve complex content-type handling in BODYSTRUCTURE
+- IMAP: fix deleted flag handling, switch to official Exchange IMAP property http://schemas.microsoft.com/mapi/id/{00062008-0000-0000-C000-000000000046}/0x8570
+- IMAP: detect HttpNotFoundException on folder select
+- IMAP: improve public folder error handling
+- IMAP: fix space at end of folder name
+- IMAP: Fix regression on LIST INBOX
+- IMAP: experimental public folder access
+- IMAP: switch to http://schemas.microsoft.com/exchange/contentstate to handle deleted flag (DAV:isdeleted did not work with some Exchange servers).
+- IMAP: implement undelete message
+
+** DavMail 3.5.0 released **
+This release improves OSX Snow Leopard support, thank to
+contributions from Dan Foody. Contact searches are also
+available now in addition to global address book searches
+over LDAP. IMAP with iPhone should now work correctly with
+most messages, Evolution IMAP read flag is fixed.
+Also added an experimental windows service support
+and a lot of other bug fixes and enhancements. 
+
+Doc:
+- Doc: Fix FAQ whitespaces
+- Doc: improve javadoc and code cleanup
+- Doc: New OSX settings screenshot
+- Doc: update release guide
+- Doc: improve index and build doc
+- Doc: detailed WIRE debug log file creation
+
+Windows:
+- Windows: Include DavMail service in windows package
+- Windows: create windows service exe
+- Windows: create windows service exe
+
+IMAP:
+- IMAP: test session expiration on each command, get a new session on expiration
+- IMAP: improve error logging on network down
+- IMAP: fix bug 2845530 implement FLAGS.SILENT command
+- IMAP: fix absolute (public) path handling
+- IMAP: fix BODYSTRUCTURE, build message on full buffer, do not rely on partial buffer (header, body, ...)
+- IMAP: fix bug 2835529 FETCH with unordered range
+- IMAP: send default BODYSTRUCTURE on mime parsing failure
+- IMAP: Improve IMAP bodystructure error logging
+- IMAP: Send bodystructure with headers for iPhone request (BODYSTRUCTURE BODY.PEEK[HEADER]) 
+- IMAP: send BODY[TEXT] for BODY.PEEK[TEXT] request, may improve iPhone support
+- IMAP: First fix for bug 2840255, do not follow redirects on message FETCH
+
+Caldav:
+- Caldav: fix conflict between X-MICROSOFT-CDO-BUSYSTATUS, X-MICROSOFT-CDO-ALLDAYEVENT and ORGANIZER ics patches
+- Caldav: check credentials on each request
+- Caldav: Disable broken sub calendar folders code
+- Caldav: Do not fail on Inbox access denied, just return an empty folder
+- Caldav: fix multi calendar Exchange path for sub folders
+- Caldav: Experimental, fix sub calendar folders path
+- Caldav: Experimental, send sub calendar folders on propfind with depth 1
+- Caldav: Handle multi line description in calendar message body
+- Caldav: merged contribution from Dan Foody,
+- Caldav: convert sound to display alarms and back
+- Caldav: remove additional organizer attendee line
+- Caldav: remove RSVP=TRUE if PARTSTAT is not NEEDS-ACTION
+- Caldav: add dump ICS logging feature
+- Caldav: add a text/plain body to calendar messages
+- Caldav: create a subject for calendar messages
+- Caldav: fixed some encoding issues in Dan's code
+- Caldav: Additional timezones
+- Caldav: failover to DAV:comment instead of CALDAV:schedule-state on some Exchange servers
+
+LDAP:
+- LDAP: iCal fix to suit both iCal 3 and 4:  move cn to sn, remove cn
+- LDAP: iCal: do not send LDAP_SIZE_LIMIT_EXCEEDED on apple-computer search by cn with sizelimit 1
+- LDAP: copy uid to apple-generateduid for iCal attendee search 
+- LDAP: Make sure we do not send empty description field, replace " \n" with null
+- LDAP: fix thread name
+- LDAP: exclude non contact entries from search, fiw map key and sn copy for iCal
+- LDAP: fix contact search, do not send unrequested attributes
+- LDAP: improve Contact search filter support
+- LDAP: Additional Contact attributes
+- LDAP: refactor contact find, generic attribute mapping
+- LDAP: experimental contact search support
+
+OSX:
+- OSX: Prepare hide from Dock option
+- OSX: crazy workaround from Dan Foody to fix attendee search on OSX Snow Leopard
+- OSX: iCal4 (OSX Snow Leopard fixes)
+
+Bug Fixes:
+- Fix regression in Form Based Logon: fix script based logon URI creation with path and query
+- Another network down fix: DavMailException is not network down
+- Improve Form Based Logon: fix script based logon URI creation
+- Improve Form Based Logon: use full URI instead of path in PostMethod, also force trusted=4
+- Simplify HttpClient creation to avoid password decoding bug in commons httpclient ('+' in password decoded as ' ')
+
+Enhancements:
+- Improve HttpException error logging
+- Fixes from checkStyle audit
+- Adjust checkStyle settings
+- Improve error handling on invalid URL
+- Various fixes from FindBugs audit
+- Fix from audit: synchronize access to HttpConnectionManager
+- Refactor ExchangeSession: do not follow redirects with GET methods
+- Fix from audit
+- Do not pass DavMailAuthenticationException to handle network down
+- Custom form (txtUserName, tstUserPass) support
+- Another network down fix from Dan Foody
+- Merged another patch from Dan Foody on network down detection
+- New settings method: return log file directory
+
+** DavMail 3.4.0 released **
+This release includes iPhone 3 Caldav support, upgrade to SWT 3.5,
+Palm Pre IMAP fixes, improved shared/public calendar support
+and a lot of bug fixes.
+
+Doc:
+- Doc: Code cleanup and improve javadoc
+- Doc: Update doc
+- Doc: Upgrade maven site plugin and improve style
+- Doc: Update maven pom inceptionYear
+
+Bug:
+- Bug: Do not try to set Nimbus Look And Feel on Linux with Gtk
+
+Enhancements:
+- Remove NTLM authentication, breaks Basic authentication (missing domain in username)
+- Set NTLM as last authentication scheme
+- Experimental: reenable NTLM authentication
+- Upgrade SWT to 3.5
+- Use getFolderPath in getSubFolders
+- Make API more consistent: createMessage must get a folder path, not URL
+- Enhancement: Patch 2826966 from Eivind Tagseth, Make davmail.sh work from any location
+
+IMAP:
+- IMAP: Need to reset index for Palm pre
+- IMAP: case insensitive search operators
+- IMAP: Fix bug 2835529, implement SEARCH ALL SINCE for Palm Pre
+
+Caldav:
+- Caldav: improve error handling, 440 means 403 forbidden on Exchange
+- Caldav: Fix shared calendar support for Lightning
+- Caldav: additional patch for Outlook created recurring events
+- Caldav: set X-MICROSOFT-CDO-BUSYSTATUS according to TRANSP field
+- Caldav: implement a timezone translation table for iPhone 3, revert organizer patch (breaks notifications with Lightning)
+- Caldav: another iPhone fix, remove organizer line if user is organizer
+- Caldav: generic timezone patch for iPhone 3
+- Caldav: remove empty ics properties
+- Caldav: Remove calendar-proxy, only used for delegate calendars
+- Caldav: try to improve responses for iCal
+- Caldav: fix bug 2833044 Event not found error on dismissing reminders with events created in Outlook with a plus sign in subject
+- Caldav: Experimental, add calendar-proxy DAV option and version in server header
+- Caldav: Add missing allow OPTIONS header
+- Caldav: improve public (shared) calendar support, accept calendars at any depth
+- Caldav: set caldav logging to davmail logging level
+- Caldav: updated fix, remove organizer line if event has no attendees for iPhone
+- Caldav: remove organizer line if current user is organizer for iPhone, will not remove line for events with attendees
+- Caldav: Improve principal -> actualPrincipal detection: use session alias instead of login
+- Caldav: fix bug 2819028, case insensitive email in sendPrincipal test
+- Caldav: iPhone compatibility, remove <C:comp name="VTODO"/>
+- Caldav: iPhone workaround: send calendar subfolder
+- Caldav: revert @ encoding, breaks iCal
+- Caldav: iPhone fix, encode @ in Caldav response hrefs
+- Caldav: untested, extended PROPFIND / response for iPhone 3.0
+- Caldav: fix infinite loop with Sunbird 1.0pre with invalid credentials
+
+SMTP:
+- SMTP: fix bug 2791607, do not patch message body (breaks electronic signature), no longer needed with latest Thunderbird
+
+** DavMail 3.3.0 released **
+This is a bug fix release after two beta releases,
+including PKCS11 (smartcard) client certificate support,
+gateway encryption (SSL) support, the new jackrabbit and httpclient libraries
+and I18N support (french and english available).
+
+- Caldav: updated caldav response headers according to gmail, added Expires and Cache-control HTTP headers
+- POP3: implement NOOP command
+- Doc: Update documentation header
+- Doc: Add GPLv2 header to all source files
+- Doc: Remove Apache license from checkstyle config file header
+- IMAP: fix DELETED/UNDELETED SEARCH parameters
+- IMAP: Fix bug 2822625: support index range in IMAP SEARCH
+- Enhancements: Merged network down (with firewall) code from Dan Foody
+- Caldav: Additional properties and ignore cases for Sunbird
+- Caldav: Fix empty organizer field in ICS (active sync support) and another getParticipants bug
+- GUI: Fix OSX menu default ActionListener
+- GUI: Try to set Nimbus Look And Feel on Linux with Gtk
+
+** DavMail 3.3.0b2 released with smartcard (PKCS11) support **
+This is a bug fix release, with nonetheless one main new feature:
+PKCS11 (smartcard) client certificate support !
+Tested with ActivIdentity ActivClient and Mozilla soft token, should
+work with any PKCS11 module.
+
+Security:
+- (RFE 2800206) PKCS11 (smartcard) client certificate support
+
+Server (daemon) mode:
+- Fix server mode: now all listener threads are daemon, avoid main thread exit and add a shutdown hook
+- Name shutdown thread
+
+Caldav:
+- Move wire debug log with headers
+- Fix NoSuchMethodError with Java 5
+- revert supported-calendar-component-set on root and improve logging
+- fix regression on iCal calendar color change
+- only include attendees with RSVP=TRUE or PARTSTAT=NEEDS-ACTION for notifications (avoid iCal additional notifications)
+- Improve error handling on FreeBusy failure
+- add supported-calendar-component-set property requested by iPhone 3.0
+ Sunbird compatibility, exclude events with empty names
+- Fix for iCal: send etag on GET and HEAD requests
+- Send empty response instead of error on freebusy with unknown attendee
+
+IMAP:
+- revert refreshFolder calls that break Outlook
+
+OSX:
+- Remove default trayIcon listener on OSX
+- Fixed logFilePath logic on OSX
+
+Enhancements:
+- Update ExchangeSession test
+- Exclude optional log4j maven dependencies
+- Added a logFilePath setting to set log4j file appender path, this appender is now added dynamically to avoid davmail.log file create failure
+- Upgrade Log4J to 1.2.15
+- Make sure we do exit: catch exceptions before System.exit
+
+Doc: 
+- Document build process in FAQ
+
+Known issues :
+- Does not - yet - work with iPhone 3.0 Caldav
+- Still issues with Exchange activeSync mode
+
+** Davmail 3.3.0b1 released **
+This release is mainly a deep refactoring: replaced deprecated 
+jakarta slide library with jackrabbit and upgrade httpclient.
+Also added client to gateway encryption (SSL) support, started I18N
+(french and english available) and many bug fixes and enhancements.
+
+I18N:
+- I18N: FrameGatewayTray
+- I18N: Format port numbers and add missing message
+- I18N: improve startup log message
+- I18N: remove Locale.ROOT not available under Java 1.5
+- I18N: externalize and translate exception messages
+- I18N: Do not apply i18n on log file
+- I18N: french localization
+- I18N: externalize all DavGatewayTray log statements for i18n
+- I18N: ldap package
+- I18N: davmail package
+- I18N: start internationalization conversion
+
+POP:
+- POP: Defer message list after login phase and load only uid and size attributes
+- POP: make sure the url is encoded correctly on delete
+
+IMAP:
+- IMAP: Detect fetch of a missing (probably deleted) message to avoid infinite loop with Thunderbird
+- IMAP: reset icon after each command
+
+SMTP:
+- SMTP: fixed two bugs, header ignored because of Exchange 2007 from patch and bccbuffer 
+ double xml encoding (=> Delivery status notification)
+
+LDAP:
+- LDAP: improve ldap search logging
+
+Doc:
+- Add Mitchell V. Oliver as Java Contributor
+- SSL certificate settings documentation in getting started
+- Update OSX doc: credentials are mandatory in Directory Utility settings
+- Add Eivind Tagseth as Java Contributor
+
+Enhancements:
+- Check java version in ant build.xml
+- Fix ExchangeSession test
+- Refactor DavProperty handling with new jackrabbit library
+- Close idle connections to exchange after one minute
+- Avoid 401 roundtrips
+- Remove deprecated HttpException api
+- Replace deprecated setRequestBody with setRequestEntity
+- Refactor DavProperty handling with new jackrabbit library
+- Update packaging and Maven POM after library update
+- Major refactoring: replace deprecated jakarta slide with jackrabbit and upgrade httpclient
+- Upgrade svnkit library
+- Sort properties and display version in startup message
+- Use interactive console certificate accept in headless and/or server mode
+- Append svn build number to release name
+- Additional login failover : get email from options page
+- Replace greyscale inactive icon with a color icon
+- Avoid nullpointerexception in Settings.setProperty
+- Reinsert System.exit after clean shutdown to make sure we do exit
+- Make all threads daemon and remove System.exit calls
+- Patch 2790299 by Mitchell V. Oliver: Add support for SSL to client connections
+- Remember previous checkConfig status to detect network down
+- Fixes from audit
+- Refactor email retrieval : do not throw IOException in failovers
+- Implement BundleMessage.toString() for direct usage in Log4J logger
+- Revert to simple class names in thread names
+- Catch unknown host on session login
+- Workaround for post logon script redirect
+- Workaround for Exchange server misconfiguration: move galfind requests to mailPath or /exchange instead of /public
+- Enhancements from audit
+- Fix exchangeSession test class
+- Improve BindException error message
+- Cleanup from audit
+- Improve exception handling
+- Implement a last resort failover to build email from alias and domain name
+- Limit redirects to 10 instead of 100
+- Replace hardcoded strings
+- Refactor SimpleDateFormat usage
+- Reorganize packages
+
+OSX:
+- OSX: replace JavaApplicationStub link with actual file
+- OSX: Move davmail.log to Library/Logs/DavMail on OSX
+- OSX: Improve Mac OSX Java6 support
+- OSX: fix regression on OSX Quit handler
+
+Bugs fixed:
+- Fix bug 2797272: Add disable update check
+- Do not localize port numbers
+- Replaced localhost check with the isLoopbackAddress() method, should be IPV6 compatible
+- Fix regression : /exchange/ does not work for galfind under Exchange 2007
+- Fixed 2717547: Unsupported filter attribute: apple-group-memberguid
+- URI encode alias in getEmail()
+- Fix SSLProtocolSocketFactory with HttpClient 3.1
+- Reenable limited timeout on getReleasedVersion
+- Always exclude NTLM authentication, not only for proxy authorization
+- Fix 2717446 from Eivind Tagseth
+
+Caldav:
+- Caldav: fix unknown recipient message
+- Caldav: do not send freebusy info if attendee is unknown
+- Caldav: Improve calendar-color patch answer
+- Caldav: implement HEAD request
+- Improve network down detection for Caldav
+- Caldav: No need to check connectivity on HTTPS
+- Caldav: Fix Bug 2783595, allow empty lines in ICS content
+- Caldav: Exclude RSVP=FALSE from notifications recipients list for Outlook 2003 compatibility
+- Caldav: exclude invalid attendees address from recipient list
+- Caldav: avoid duplicate / in event path
+- Caldav: implement public shared calendar
+- Caldav: In progress multi calendar support
+- Caldav: fix regression in FreeBusy date handling
+- Caldav: switch icon during event report
+- Caldav: refactor CaldavConnection, prepare /public context
+- Caldav: another special characters handling improvement
+- Caldav: iCal decodes hrefs, not lightning => detect client in CaldavRequest
+- Caldav: replace etag by resourcetag in getCalendarEtag
+- Caldav: Send events back to the client after each get on REPORT request (avoid iCal timeout)
+- Caldav: no inbox/outbox for delegated calendars
diff --git a/src/bin/davmail b/src/bin/davmail
new file mode 100644
index 0000000..f1e0eaf
--- /dev/null
+++ b/src/bin/davmail
@@ -0,0 +1,4 @@
+#!/bin/sh
+export LD_LIBRARY_PATH=/usr/lib/jni
+for i in /usr/share/davmail/lib/*; do export CLASSPATH=$CLASSPATH:$i; done
+java -Xmx512M -cp /usr/share/davmail/davmail.jar:/usr/share/java/swt.jar:$CLASSPATH davmail.DavGateway "$@" 
diff --git a/src/checkstyle/checkstyle-configuration.xml b/src/checkstyle/checkstyle-configuration.xml
new file mode 100644
index 0000000..bc51427
--- /dev/null
+++ b/src/checkstyle/checkstyle-configuration.xml
@@ -0,0 +1,192 @@
+<?xml version="1.0"?>
+<!DOCTYPE module PUBLIC
+        "-//Puppy Crawl//DTD Check Configuration 1.2//EN"
+        "http://www.puppycrawl.com/dtds/configuration_1_2.dtd">
+
+<!--
+
+  Checkstyle configuration that checks the sun coding conventions from:
+
+    - the Java Language Specification at
+      http://java.sun.com/docs/books/jls/second_edition/html/index.html
+
+    - the Sun Code Conventions at http://java.sun.com/docs/codeconv/
+
+    - the Javadoc guidelines at
+      http://java.sun.com/j2se/javadoc/writingdoccomments/index.html
+
+    - the JDK Api documentation http://java.sun.com/j2se/docs/api/index.html
+
+    - some best practices
+
+  Checkstyle is very configurable. Be sure to read the documentation at
+  http://checkstyle.sf.net (or in your downloaded distribution).
+
+  Most Checks are configurable, be sure to consult the documentation.
+
+  To completely disable a check, just comment it out or delete it from the file.
+
+  Finally, it is worth reading the documentation.
+
+-->
+
+<module name="Checker">
+    <!--
+        If you set the basedir property below, then all reported file
+        names will be relative to the specified directory. See
+        http://checkstyle.sourceforge.net/5.x/config.html#Checker
+
+        <property name="basedir" value="${basedir}"/>
+    -->
+
+    <!-- Checks that a package-info.java file exists for each package.     -->
+    <!-- See http://checkstyle.sf.net/config_javadoc.html#JavadocPackage -->
+    <!--module name="JavadocPackage"/-->
+
+    <!-- Checks whether files end with a new line.                        -->
+    <!-- See http://checkstyle.sf.net/config_misc.html#NewlineAtEndOfFile -->
+    <!--module name="NewlineAtEndOfFile"/-->
+
+    <!-- Checks that property files contain the same keys.         -->
+    <!-- See http://checkstyle.sf.net/config_misc.html#Translation -->
+    <module name="Translation"/>
+
+    <!-- Checks for Size Violations.                    -->
+    <!-- See http://checkstyle.sf.net/config_sizes.html -->
+    <!--module name="FileLength"/-->
+
+    <!-- Checks for whitespace                               -->
+    <!-- See http://checkstyle.sf.net/config_whitespace.html -->
+    <!--module name="FileTabCharacter"/-->
+
+    <!-- Miscellaneous other checks.                   -->
+    <!-- See http://checkstyle.sf.net/config_misc.html -->
+    <!--module name="RegexpSingleline">
+        <property name="format" value="\s+$"/>
+        <property name="minimum" value="0"/>
+        <property name="maximum" value="0"/>
+        <property name="message" value="Line has trailing spaces."/>
+    </module-->
+
+    <module name="TreeWalker">
+
+        <!-- Checks for Javadoc comments.                     -->
+        <!-- See http://checkstyle.sf.net/config_javadoc.html -->
+        <!--module name="JavadocMethod"/>
+        <module name="JavadocType"/>
+        <module name="JavadocVariable"/>
+        <module name="JavadocStyle"/-->
+
+
+        <!-- Checks for Naming Conventions.                  -->
+        <!-- See http://checkstyle.sf.net/config_naming.html -->
+        <module name="ConstantName"/>
+        <module name="LocalFinalVariableName"/>
+        <module name="LocalVariableName"/>
+        <module name="MemberName"/>
+        <module name="MethodName"/>
+        <module name="PackageName"/>
+        <module name="ParameterName"/>
+        <module name="StaticVariableName"/>
+        <module name="TypeName"/>
+
+
+        <!-- Checks for Headers                                -->
+        <!-- See http://checkstyle.sf.net/config_header.html   -->
+        <!-- <module name="Header">                            -->
+        <!-- The follow property value demonstrates the ability     -->
+        <!-- to have access to ANT properties. In this case it uses -->
+        <!-- the ${basedir} property to allow Checkstyle to be run  -->
+        <!-- from any directory within a project. See property      -->
+        <!-- expansion,                                             -->
+        <!-- http://checkstyle.sf.net/config.html#properties        -->
+        <!-- <property                                              -->
+        <!--     name="headerFile"                                  -->
+        <!--     value="${basedir}/java.header"/>                   -->
+        <!-- </module> -->
+
+        <!-- Following interprets the header file as regular expressions. -->
+        <!-- <module name="RegexpHeader"/>                                -->
+
+
+        <!-- Checks for imports                              -->
+        <!-- See http://checkstyle.sf.net/config_import.html -->
+        <!--module name="AvoidStarImport"/-->
+        <!--module name="IllegalImport"/--> <!-- defaults to sun.* packages -->
+        <module name="RedundantImport"/>
+        <module name="UnusedImports"/>
+
+
+        <!-- Checks for Size Violations.                    -->
+        <!-- See http://checkstyle.sf.net/config_sizes.html -->
+        <!--module name="LineLength"/-->
+        <!--module name="MethodLength"/-->
+        <module name="ParameterNumber"/>
+
+
+        <!-- Checks for whitespace                               -->
+        <!-- See http://checkstyle.sf.net/config_whitespace.html -->
+        <!--module name="EmptyForIteratorPad"/>
+        <module name="GenericWhitespace"/>
+        <module name="MethodParamPad"/>
+        <module name="NoWhitespaceAfter"/>
+        <module name="NoWhitespaceBefore"/>
+        <module name="OperatorWrap"/>
+        <module name="ParenPad"/>
+        <module name="TypecastParenPad"/>
+        <module name="WhitespaceAfter"/>
+        <module name="WhitespaceAround"/-->
+
+
+        <!-- Modifier Checks                                    -->
+        <!-- See http://checkstyle.sf.net/config_modifiers.html -->
+        <module name="ModifierOrder"/>
+        <module name="RedundantModifier"/>
+
+
+        <!-- Checks for blocks. You know, those {}'s         -->
+        <!-- See http://checkstyle.sf.net/config_blocks.html -->
+        <module name="AvoidNestedBlocks"/>
+        <!--module name="EmptyBlock"/-->
+        <module name="LeftCurly"/>
+        <module name="NeedBraces"/>
+        <module name="RightCurly"/>
+
+        <!-- Checks for common coding problems               -->
+        <!-- See http://checkstyle.sf.net/config_coding.html -->
+        <!--module name="AvoidInlineConditionals"/-->
+        <module name="DoubleCheckedLocking"/>
+        <!-- MY FAVOURITE -->
+        <module name="EmptyStatement"/>
+        <module name="EqualsHashCode"/>
+        <!--module name="HiddenField"/-->
+        <module name="IllegalInstantiation"/>
+        <!--module name="InnerAssignment"/-->
+        <!--module name="MagicNumber"/-->
+        <module name="MissingSwitchDefault"/>
+        <module name="RedundantThrows"/>
+        <module name="SimplifyBooleanExpression"/>
+        <module name="SimplifyBooleanReturn"/>
+
+        <!-- Checks for class design                         -->
+        <!-- See http://checkstyle.sf.net/config_design.html -->
+        <!--module name="DesignForExtension"/-->
+        <module name="FinalClass"/>
+        <module name="HideUtilityClassConstructor"/>
+        <module name="InterfaceIsType"/>
+        <!--module name="VisibilityModifier">
+            <property name="packageAllowed" value="true"/>
+            <property name="protectedAllowed" value="true"/>
+        </module-->
+
+
+        <!-- Miscellaneous other checks.                   -->
+        <!-- See http://checkstyle.sf.net/config_misc.html -->
+        <module name="ArrayTypeStyle"/>
+        <!--module name="FinalParameters"/-->
+        <module name="TodoComment"/>
+        <module name="UpperEll"/>
+
+    </module>
+
+</module>
diff --git a/src/contribs/davmailpd/davmailpd b/src/contribs/davmailpd/davmailpd
new file mode 100644
index 0000000..98cf0b0
--- /dev/null
+++ b/src/contribs/davmailpd/davmailpd
@@ -0,0 +1,86 @@
+#!/bin/bash
+#
+# davmailpd: Davmail private daemon
+# 
+# copyright Geert Stappers <stappers at stappers.nl> 2010
+# distributed under the terms of the GNU General Public License
+#
+usage() {
+  cat << USAGE
+
+   davmailpd {status|start|stop|restart}
+
+     restart: stops and starts davmail
+     status: tells if davmail is running for the executing user
+     start: starts davmail
+     stop: sends SIGHUP to davmail proces of the executing user
+
+USAGE
+}
+
+#### Example output of commands used in find_process_id()
+## # Executing User
+## $ who am i
+## stappers pts/4        2010-09-06 07:22 (:0.0)
+## # Possible Processes
+## $ ps -ef | grep -e  "^stappers.*gv.*vol.*tor$"
+## stappers 23201  1  0 Sep06 ?  00:00:00 /usr/lib/gvfs-gdu-volume-monitor
+## stappers 23203  1  0 Sep06 ?  00:00:00 /usr/lib/gvfs-gphoto2-volume-monitor
+
+find_process_id() {
+  EU=$( who ami | awk '{ print $1 }' )
+  PP=$(ps -ef | grep -e  "^${EU}.*java.*\.jar.*\.jar.*davmail\.DavGateway" )
+  if [ -z "${PP}" ] ; then
+    echo -n 'None'
+  else
+    echo ${PP} | awk '{ printf(" %s",$2) }'
+  fi
+}
+
+status_cmd() {
+  if [ $(find_process_id) == 'None' ] ; then
+    echo 'NO Davmail running for you'
+  else
+    echo 'OKay, you have running Davmail process(es)'
+  fi
+}
+
+send_signal_hangup() {
+  if [ $(find_process_id) != 'None' ] ; then
+    kill -n SIGHUP $(find_process_id)
+  fi
+}
+
+case "$1" in
+  status)
+    status_cmd
+    exit 0
+  ;;
+  stop)
+    send_signal_hangup
+    exit 0
+  ;;
+  start)
+    shift # remove first parameter ( and leave others untouched )
+    # continue, no exit here
+  ;;
+  restart)
+    shift # remove first parameter ( and leave others untouched )
+    send_signal_hangup
+    # continue, no exit here
+  ;;
+  processes|proc*|debug|d*)
+    EU=$( who ami | awk '{ print $1 }' )
+    ps -ef | grep -e  "^${EU}.*java.*\.jar.*\.jar.*davmail\.DavGateway"
+    find_process_id ; echo
+    exit 0
+  ;;
+  *)
+    usage
+    exit 0
+  ;;
+esac
+# the actual start
+export LD_LIBRARY_PATH=/usr/lib/jni
+for i in /usr/share/davmail/lib/*; do export CLASSPATH=$CLASSPATH:$i; done
+java -Xmx512M -cp /usr/share/davmail/davmail.jar:$CLASSPATH davmail.DavGateway "$@" > /dev/null 2>&1 &
diff --git a/src/contribs/init/davmail-init b/src/contribs/init/davmail-init
new file mode 100644
index 0000000..9f43b6c
--- /dev/null
+++ b/src/contribs/init/davmail-init
@@ -0,0 +1,162 @@
+#!/bin/bash
+#
+# chkconfig: 345 99 05 
+# # description: Java deamon script for davmail (http://davmail.sourceforge.net/) write by qk4l at tem4uk.ru 2011
+#
+# Derived from -
+# Home page: http://www.source-code.biz
+# License:   GNU/LGPL (http://www.gnu.org/licenses/lgpl.html)
+# Copyright 2006 Christian d'Heureuse, Inventec Informatik AG, Switzerland.
+#
+# History:
+# 2010-09-21 Josh Davis: Changed 'sudo' to 'su', fix some typos, removed unused variables
+# 2009-03-04 Josh Davis: Ubuntu/Redhat version.
+# 2006-06-27 Christian d'Heureuse: Script created.
+# 2006-07-02 chdh: Minor improvements.
+# 2006-07-10 chdh: Changes for SUSE 10.0.
+
+
+# Set this to your Java installation
+
+serviceNameLo="davmail"                                  # service name with the first letter in lowercase
+serviceName="Davmail"                                    # service name
+serviceUser="davmail"                                      # OS user name for the service
+serviceGroup="root"                                    # OS group name for the service
+applDir="/usr/share/$serviceNameLo/lib"                          # home directory of the service application
+serviceUserHome="/etc/$serviceNameLo"                       # home directory of the service user
+serviceLogFile="/var/log/$serviceNameLo.log"               # log file for StdOut/StdErr
+maxShutdownTime=15                                         # maximum number of seconds to wait for the daemon to terminate normally
+pidFile="/var/run/$serviceNameLo.pid"                      # name of PID file (PID = process ID number)
+javaCommand="java"                                         # name of the Java launcher without the path
+javaExe="/usr/bin/$javaCommand -Xmx512M"                      # file name of the Java application launcher executable
+javaCommandLineKeyword="davmail.jar"                     # a keyword that occurs on the commandline, used to detect an already running service process and to distinguish it from others
+DAEMON_ARGS="/etc/davmail/davmail.properties"
+
+# Makes the file $1 writable by the group $serviceGroup.
+function makeFileWritable {
+   local filename="$1"
+   touch $filename || return 1
+   chown $serviceUser:$serviceGroup $filename || return 1
+   #chgrp $serviceGroup $filename || return 1
+   chmod g+w $filename || return 1
+   return 0; }
+
+# Returns 0 if the process with PID $1 is running.
+function checkProcessIsRunning {
+   local pid="$1"
+   if [ -z "$pid" -o "$pid" == " " ]; then return 1; fi
+   if [ ! -e /proc/$pid ]; then return 1; fi
+   return 0; }
+
+# Returns 0 if the process with PID $1 is our Java service process.
+function checkProcessIsOurService {
+   local pid="$1"
+   if [ "$(ps -p $pid --no-headers -o comm)" != "$javaCommand" ]; then return 1; fi
+   grep -q --binary -F "$javaCommandLineKeyword" /proc/$pid/cmdline
+   if [ $? -ne 0 ]; then return 1; fi
+   return 0; }
+
+# Returns 0 when the service is running and sets the variable $pid to the PID.
+function getServicePID {
+   if [ ! -f $pidFile ]; then return 1; fi
+   pid="$(<$pidFile)"
+   checkProcessIsRunning $pid || return 1
+   checkProcessIsOurService $pid || return 1
+   return 0; }
+
+function startServiceProcess {
+   cd $applDir || return 1
+   rm -f $pidFile
+   makeFileWritable $pidFile || return 1
+   makeFileWritable $serviceLogFile || return 1
+   for i in $applDir/*; do export CLASSPATH=$CLASSPATH:$i; done
+   cmd="nohup $javaExe -cp /usr/share/davmail/davmail.jar:$CLASSPATH davmail.DavGateway $DAEMON_ARGS >>$serviceLogFile 2>&1 & echo \$! >$pidFile"
+   #echo $cmd
+   su -m $serviceUser -s $SHELL -c "$cmd" || return 1
+   sleep 0.1
+   pid="$(<$pidFile)"
+   if checkProcessIsRunning $pid; then :; else
+      echo -ne "\n$serviceName start failed, see logfile."
+      return 1
+   fi
+   return 0; }
+
+function stopServiceProcess {
+   kill $pid || return 1
+   for ((i=0; i<maxShutdownTime*10; i++)); do
+      checkProcessIsRunning $pid
+      if [ $? -ne 0 ]; then
+         rm -f $pidFile
+         return 0
+         fi
+      sleep 0.1
+      done
+   echo -e "\n$serviceName did not terminate within $maxShutdownTime seconds, sending SIGKILL..."
+   kill -s KILL $pid || return 1
+   local killWaitTime=15
+   for ((i=0; i<killWaitTime*10; i++)); do
+      checkProcessIsRunning $pid
+      if [ $? -ne 0 ]; then
+         rm -f $pidFile
+         return 0
+         fi
+      sleep 0.1
+      done
+   echo "Error: $serviceName could not be stopped within $maxShutdownTime+$killWaitTime seconds!"
+   return 1; }
+
+function startService {
+   getServicePID
+   if [ $? -eq 0 ]; then echo -n "$serviceName is already running"; RETVAL=0; return 0; fi
+   echo -n "Starting $serviceName   "
+   startServiceProcess
+   if [ $? -ne 0 ]; then RETVAL=1; echo "failed"; return 1; fi
+   echo "started PID=$pid"
+   RETVAL=0
+   return 0; }
+
+function stopService {
+   getServicePID
+   if [ $? -ne 0 ]; then echo -n "$serviceName is not running"; RETVAL=0; echo ""; return 0; fi
+   echo -n "Stopping $serviceName   "
+   stopServiceProcess
+   if [ $? -ne 0 ]; then RETVAL=1; echo "failed"; return 1; fi
+   echo "stopped PID=$pid"
+   RETVAL=0
+   return 0; }
+
+function checkServiceStatus {
+   echo -n "Checking for $serviceName:   "
+   if getServicePID; then
+	echo "running PID=$pid"
+	RETVAL=0
+   else
+	echo "stopped"
+	RETVAL=3
+   fi
+   return 0; }
+
+function main {
+   RETVAL=0
+   case "$1" in
+      start)                                               # starts the Java program as a Linux service
+         startService
+         ;;
+      stop)                                                # stops the Java program service
+         stopService
+         ;;
+      restart)                                             # stops and restarts the service
+         stopService && startService
+         ;;
+      status)                                              # displays the service status
+         checkServiceStatus
+         ;;
+      *)
+         echo "Usage: $0 {start|stop|restart|status}"
+         exit 1
+         ;;
+      esac
+   exit $RETVAL
+}
+
+main $1
\ No newline at end of file
diff --git a/src/contribs/rpm/SOURCES/davmail-init b/src/contribs/rpm/SOURCES/davmail-init
new file mode 100644
index 0000000..6bf9914
--- /dev/null
+++ b/src/contribs/rpm/SOURCES/davmail-init
@@ -0,0 +1,134 @@
+#!/bin/sh
+#
+# davmail:	davmail exchange gateway daemon
+#
+# chkconfig:	345 98 02
+# description:	DavMail gateway for Microsoft Exchange
+# processname:	davmail
+# config:	/etc/davmail.properties
+
+# LSB init-info
+### BEGIN INIT INFO
+# Provides:          davmail
+# Required-Start:    $network
+# Required-Stop:     $network
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: DavMail gateway for Microsoft Exchange
+### END INIT INFO
+
+# Source function library.
+if [ -e /etc/init.d/functions ]; then
+	. /etc/init.d/functions
+fi
+
+# LSB functions
+. /lib/lsb/init-functions
+
+# Check that networking is configured.
+[ "${NETWORKING}" = "no" ] && exit 0
+
+DAVMAIL_CONF=/etc/davmail.properties
+test -f $DAVMAIL_CONF || exit 4
+DAVMAIL_HOME=/var/lib/davmail
+test -d $DAVMAIL_HOME || exit 5
+
+LOGFILE=/var/log/davmail.log
+PIDFILE=/var/run/davmail.pid
+LOCKFILE=/var/lock/subsys/davmail
+
+start() {
+	echo -n $"Starting DavMail gateway: "
+	dostatus > /dev/null 2>&1
+	if [ $RETVAL -eq 0 ]
+	then
+		echo -n $"DavMail gateway already running"
+		log_failure_msg
+		RETVAL=1
+		return
+	fi
+	runuser - davmail -s /bin/sh -c "exec nohup $DAVMAIL_HOME/davmail $DAVMAIL_CONF >> $LOGFILE 2>&1 &"
+	RETVAL=$?
+	if [ $RETVAL -eq 0 ]
+	then
+		sleep 1
+		cat $DAVMAIL_HOME/pid > $PIDFILE
+		touch $LOCKFILE
+		log_success_msg
+	else
+		log_failure_msg
+	fi
+	return $RETVAL
+}
+
+stop() {
+	echo -n $"Shutting down DavMail gateway: "
+	kill $(cat $PIDFILE 2>/dev/null) > /dev/null 2>&1
+	RETVAL=$?
+	sleep 1
+	if [ $RETVAL -eq 0 ]
+	then
+		rm -f $PIDFILE $LOCKFILE
+		log_success_msg
+	else
+		log_failure_msg
+	fi
+	return $RETVAL
+}
+
+restart() {
+	stop
+	start
+}
+
+condrestart() {
+	[ -f $LOCKFILE ] && restart || :
+}
+
+dostatus() {
+	kill -0 $(cat $PIDFILE 2>/dev/null) > /dev/null 2>&1
+	RETVAL=$?
+	if [ $RETVAL -eq 0 ]
+	then
+		echo "DavMail gateway (pid $(cat $PIDFILE 2>/dev/null)) is running..."
+	else
+		if [ -f $PIDFILE ]
+		then
+			echo "DavMail gateway dead but pid file exists"
+			RETVAL=1
+			return
+		fi
+		if [ -f $LOCKFILE ]
+		then
+			echo "DavMail gateway dead but subsys locked"
+			RETVAL=2
+			return
+		fi
+		echo "DavMail gateway is stopped"
+		RETVAL=3
+	fi
+}
+
+# See how we were called.
+case "$1" in
+  start)
+	start
+	;;
+  stop)
+	stop
+	;;
+  status)
+	dostatus
+	;;
+  restart|reload)
+	restart
+	;;
+  condrestart)
+	condrestart
+	;;
+  *)
+	echo $"Usage: $0 {start|stop|status|restart|reload|condrestart}"
+	exit 1
+esac
+
+exit $RETVAL
diff --git a/src/contribs/rpm/SOURCES/davmail-logrotate b/src/contribs/rpm/SOURCES/davmail-logrotate
new file mode 100644
index 0000000..a88f0ae
--- /dev/null
+++ b/src/contribs/rpm/SOURCES/davmail-logrotate
@@ -0,0 +1,7 @@
+/var/log/davmail.log {
+    missingok
+    notifempty
+    size 30k
+    monthly
+    create 0640 davmail davmail
+}
diff --git a/src/contribs/rpm/SOURCES/davmail-wrapper b/src/contribs/rpm/SOURCES/davmail-wrapper
new file mode 100644
index 0000000..96a726e
--- /dev/null
+++ b/src/contribs/rpm/SOURCES/davmail-wrapper
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+LOGFILE=/var/log/davmail.log
+PIDFILE=/var/lib/davmail/pid
+
+for class in /usr/share/davmail/lib/*
+do
+	export CLASSPATH=${CLASSPATH}:${class}
+done
+
+echo $$ > $PIDFILE
+exec java -cp /usr/share/davmail/davmail.jar:${CLASSPATH} \
+	davmail.DavGateway "$@" >> $LOGFILE 2>&1
diff --git a/src/contribs/rpm/SOURCES/davmail.desktop b/src/contribs/rpm/SOURCES/davmail.desktop
new file mode 100644
index 0000000..ac4916b
--- /dev/null
+++ b/src/contribs/rpm/SOURCES/davmail.desktop
@@ -0,0 +1,10 @@
+[Desktop Entry]
+Version=1.0
+Type=Application
+Terminal=false
+Encoding=UTF-8
+Name=DavMail
+Icon=davmail
+Exec=/usr/bin/davmail
+Comment=DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway
+Categories=Application;Office;Email;Network;Calendar;ContactManagement;
diff --git a/src/contribs/rpm/SOURCES/davmail.properties b/src/contribs/rpm/SOURCES/davmail.properties
new file mode 100644
index 0000000..e703b91
--- /dev/null
+++ b/src/contribs/rpm/SOURCES/davmail.properties
@@ -0,0 +1,26 @@
+# DavMail settings
+davmail.url=https\://owa.example.com/owa/
+davmail.enableEws=true
+davmail.ldapPort=1389
+davmail.caldavPort=1080
+davmail.smtpPort=1025
+davmail.popPort=1110
+davmail.imapPort=1143
+davmail.proxyPort=
+davmail.disableUpdateCheck=true
+davmail.bindAddress=127.0.0.1
+davmail.logFilePath=/var/log/davmail.log
+davmail.server=true
+davmail.server.certificate.hash=
+davmail.caldavPastDelay=90
+davmail.sentKeepDelay=90
+davmail.keepDelay=30
+davmail.allowRemote=false
+davmail.enableProxy=false
+davmail.proxyHost=
+davmail.proxyPassword=
+davmail.proxyUser=
+log4j.logger.davmail=WARN
+log4j.logger.httpclient.wire=WARN
+log4j.logger.org.apache.commons.httpclient=WARN
+log4j.rootLogger=WARN
diff --git a/src/contribs/rpm/SOURCES/davmail.sh b/src/contribs/rpm/SOURCES/davmail.sh
new file mode 100644
index 0000000..44b45db
--- /dev/null
+++ b/src/contribs/rpm/SOURCES/davmail.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+#
+# Usage: davmail [</path/to/davmail.properties>]
+#
+
+# Create the $HOME/.davmail.properties if necessary
+if [ -r /etc/davmail.properties ]; then
+  if [ ! -f ${HOME}/.davmail.properties ]; then
+	grep -v ^davmail.logFilePath /etc/davmail.properties | \
+	  sed -e 's/^davmail.server=true/davmail.server=false/' > \
+	    ${HOME}/.davmail.properties
+  fi
+fi
+
+# Add our libs into CLASSPATH
+for i in /usr/share/davmail/lib/*; do export CLASSPATH=${CLASSPATH}:${i}; done
+
+# Start davmail
+java -cp /usr/share/davmail/davmail.jar:${CLASSPATH} davmail.DavGateway $*
diff --git a/src/contribs/rpm/SPECS/davmail.spec b/src/contribs/rpm/SPECS/davmail.spec
new file mode 100644
index 0000000..2eb570e
--- /dev/null
+++ b/src/contribs/rpm/SPECS/davmail.spec
@@ -0,0 +1,116 @@
+%define davrel 3.8.5
+%define davsvn 1480
+%define davver %{davrel}-%{davsvn}
+%ifarch i686
+%define davarch x86
+%endif
+%ifarch x86_64
+%define davarch x86_64
+%endif
+
+Summary: DavMail is a POP/IMAP/SMTP/Caldav/Carddav/LDAP gateway for Microsoft Exchange
+Name: davmail
+Version: %{davrel}
+Release: 1%{?dist}
+License: GPL
+Group: Applications/Internet
+BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
+BuildRequires: ant >= 1.7.1, ant-antlr, desktop-file-utils
+Requires(pre): chkconfig, coreutils, initscripts, shadow-utils
+Requires: logrotate, jre
+
+Source0: %{name}-src-%{davver}.tgz
+Source1: davmail.sh
+Source2: davmail-logrotate
+Source3: davmail-init
+Source4: davmail.properties
+Source5: davmail.desktop
+Source6: davmail-wrapper
+
+%description
+DavMail is a POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange gateway allowing 
+users to use any mail/calendar client with an Exchange server, even from 
+the internet or behind a firewall through Outlook Web Access. DavMail 
+now includes an LDAP gateway to Exchange global address book and user 
+personal contacts to allow recipient address completion in mail compose 
+window and full calendar support with attendees free/busy display.
+
+%prep
+%setup -q -n %{name}-src-%{davver}
+
+%build
+export JAVA_HOME=/etc/alternatives/java_sdk
+ant
+
+%install
+rm -rf $RPM_BUILD_ROOT
+mkdir -p $RPM_BUILD_ROOT/%{_bindir}
+mkdir -p $RPM_BUILD_ROOT/%{_sysconfdir}/logrotate.d
+mkdir -p $RPM_BUILD_ROOT/%{_sysconfdir}/rc.d/init.d
+mkdir -p $RPM_BUILD_ROOT/%{_datadir}/applications
+mkdir -p $RPM_BUILD_ROOT/%{_datadir}/pixmaps
+mkdir -p $RPM_BUILD_ROOT/%{_datadir}/davmail/lib
+mkdir -p $RPM_BUILD_ROOT/%{_localstatedir}/lib/davmail
+mkdir -p $RPM_BUILD_ROOT/%{_localstatedir}/log
+
+# Init scripts, icons, configurations
+install -m 0775 %{SOURCE1} $RPM_BUILD_ROOT/%{_bindir}/davmail
+install -m 0644 %{SOURCE2} $RPM_BUILD_ROOT/%{_sysconfdir}/logrotate.d/davmail
+install -m 0775 %{SOURCE3} $RPM_BUILD_ROOT/%{_sysconfdir}/rc.d/init.d/davmail
+install -m 0644 %{SOURCE4} $RPM_BUILD_ROOT/%{_sysconfdir}
+desktop-file-install --dir $RPM_BUILD_ROOT/%{_datadir}/applications/ %{SOURCE5}
+install -m 0775 %{SOURCE6} $RPM_BUILD_ROOT/%{_localstatedir}/lib/davmail/davmail
+
+# Actual DavMail files
+install -m 0644 src/java/tray32.png $RPM_BUILD_ROOT/%{_datadir}/pixmaps/davmail.png
+rm -f dist/lib/*win32*.jar
+install -m 0664 dist/lib/*-%{davarch}.jar $RPM_BUILD_ROOT/%{_datadir}/davmail/lib/
+rm -f dist/lib/*x86*.jar
+install -m 0664 dist/lib/* $RPM_BUILD_ROOT/%{_datadir}/davmail/lib/
+install -m 0664 dist/*.jar $RPM_BUILD_ROOT/%{_datadir}/davmail/
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%pre
+/usr/sbin/groupadd -r davmail > /dev/null 2>&1 || :
+/usr/sbin/useradd  -r -s /sbin/nologin -d /var/lib/davmail -M \
+	-g davmail davmail > /dev/null 2>&1 || :
+
+%post
+if [ ! -f /var/log/davmail.log ]
+then
+	/bin/touch /var/log/davmail.log
+fi
+/bin/chown davmail:davmail /var/log/davmail.log
+/bin/chmod 0640 /var/log/davmail.log
+/sbin/chkconfig --add davmail
+#/sbin/chkconfig davmail on
+
+%preun
+if [ "$1" = "0" ]; then
+	/sbin/service davmail stop > /dev/null 2>&1 || :
+	/bin/rm -f /var/lib/davmail/pid > /dev/null 2>&1 || :
+	/sbin/chkconfig davmail off
+	/sbin/chkconfig --del davmail
+fi
+
+%postun
+if [ $1 -ge 1 ]; then
+	/sbin/service davmail condrestart > /dev/null 2>&1 || :
+fi
+
+%files
+%defattr (-,root,root,-)
+%{_bindir}/*
+%{_sysconfdir}/rc.d/init.d/davmail
+%{_sysconfdir}/logrotate.d/davmail
+%{_sysconfdir}/davmail.properties
+%{_datadir}/applications/*
+%{_datadir}/pixmaps/*
+%{_datadir}/davmail/
+%attr(0775,davmail,davmail) %{_localstatedir}/lib/davmail
+
+%changelog
+* Mon Oct 18 2010 Marko Myllynen <myllynen at redhat.com>
+- Initial version
diff --git a/src/contribs/wrapper/bin/davmail b/src/contribs/wrapper/bin/davmail
new file mode 100644
index 0000000..59ad632
--- /dev/null
+++ b/src/contribs/wrapper/bin/davmail
@@ -0,0 +1,732 @@
+#! /bin/sh
+
+#
+# Copyright (c) 1999, 2009 Tanuki Software, Ltd.
+# http://www.tanukisoftware.com
+# All rights reserved.
+#
+# This software is the proprietary information of Tanuki Software.
+# You shall use it only in accordance with the terms of the
+# license agreement you entered into with Tanuki Software.
+# http://wrapper.tanukisoftware.org/doc/english/licenseOverview.html
+#
+# Java Service Wrapper sh script.  Suitable for starting and stopping
+#  wrapped Java applications on UNIX platforms.
+#
+
+#-----------------------------------------------------------------------------
+# These settings can be modified to fit the needs of your application
+# Optimized for use with version 3.3.6 of the Wrapper.
+
+# Application
+BASEDIR="/opt/davmail/bin"
+APP_NAME="davmail"
+APP_LONG_NAME="DavMail Exchange Gateway"
+
+# Wrapper
+WRAPPER_CMD="$BASEDIR/wrapper"
+WRAPPER_CONF="$BASEDIR/../conf/wrapper.conf"
+
+# Priority at which to run the wrapper.  See "man nice" for valid priorities.
+#  nice is only used if a priority is specified.
+PRIORITY=
+
+# Location of the pid file.
+PIDDIR="$BASEDIR"
+
+# If uncommented, causes the Wrapper to be shutdown using an anchor file.
+#  When launched with the 'start' command, it will also ignore all INT and
+#  TERM signals.
+#IGNORE_SIGNALS=true
+
+# Wrapper will start the JVM asynchronously. Your application may have some
+#  initialization tasks and it may be desirable to wait a few seconds
+#  before returning.  For example, to delay the invocation of following
+#  startup scripts.  Setting WAIT_AFTER_STARTUP to a positive number will
+#  cause the start command to delay for the indicated period of time 
+#  (in seconds).
+# 
+WAIT_AFTER_STARTUP=0
+
+# If set, the status, start_msg and stop_msg commands will print out detailed
+#   state information on the Wrapper and Java processes.
+#DETAIL_STATUS=true
+
+# If specified, the Wrapper will be run as the specified user.
+# IMPORTANT - Make sure that the user has the required privileges to write
+#  the PID file and wrapper.log files.  Failure to be able to write the log
+#  file will cause the Wrapper to exit without any way to write out an error
+#  message.
+# NOTE - This will set the user which is used to run the Wrapper as well as
+#  the JVM and is not useful in situations where a privileged resource or
+#  port needs to be allocated prior to the user being changed.
+#RUN_AS_USER=
+
+# The following two lines are used by the chkconfig command. Change as is
+#  appropriate for your application.  They should remain commented.
+# chkconfig: 2345 20 80
+# description: @app.long.name@
+ 
+# Initialization block for the install_initd and remove_initd scripts used by
+#  SUSE linux distributions.
+### BEGIN INIT INFO
+# Provides: @app.name@
+# Required-Start: $local_fs $network $syslog
+# Should-Start: 
+# Required-Stop:
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: @app.long.name@
+# Description: @app.description@
+### END INIT INFO
+
+# Do not modify anything beyond this point
+#-----------------------------------------------------------------------------
+
+# Get the fully qualified path to the script
+case $0 in
+    /*)
+        SCRIPT="$0"
+        ;;
+    *)
+        PWD=`pwd`
+        SCRIPT="$PWD/$0"
+        ;;
+esac
+
+# Resolve the true real path without any sym links.
+CHANGED=true
+while [ "X$CHANGED" != "X" ]
+do
+    # Change spaces to ":" so the tokens can be parsed.
+    SAFESCRIPT=`echo $SCRIPT | sed -e 's; ;:;g'`
+    # Get the real path to this script, resolving any symbolic links
+    TOKENS=`echo $SAFESCRIPT | sed -e 's;/; ;g'`
+    REALPATH=
+    for C in $TOKENS; do
+        # Change any ":" in the token back to a space.
+        C=`echo $C | sed -e 's;:; ;g'`
+        REALPATH="$REALPATH/$C"
+        # If REALPATH is a sym link, resolve it.  Loop for nested links.
+        while [ -h "$REALPATH" ] ; do
+            LS="`ls -ld "$REALPATH"`"
+            LINK="`expr "$LS" : '.*-> \(.*\)$'`"
+            if expr "$LINK" : '/.*' > /dev/null; then
+                # LINK is absolute.
+                REALPATH="$LINK"
+            else
+                # LINK is relative.
+                REALPATH="`dirname "$REALPATH"`""/$LINK"
+            fi
+        done
+    done
+
+    if [ "$REALPATH" = "$SCRIPT" ]
+    then
+        CHANGED=""
+    else
+        SCRIPT="$REALPATH"
+    fi
+done
+
+# Change the current directory to the location of the script
+cd "`dirname "$REALPATH"`"
+REALDIR=`pwd`
+
+# If the PIDDIR is relative, set its value relative to the full REALPATH to avoid problems if
+#  the working directory is later changed.
+FIRST_CHAR=`echo $PIDDIR | cut -c1,1`
+if [ "$FIRST_CHAR" != "/" ]
+then
+    PIDDIR=$REALDIR/$PIDDIR
+fi
+# Same test for WRAPPER_CMD
+FIRST_CHAR=`echo $WRAPPER_CMD | cut -c1,1`
+if [ "$FIRST_CHAR" != "/" ]
+then
+    WRAPPER_CMD=$REALDIR/$WRAPPER_CMD
+fi
+# Same test for WRAPPER_CONF
+FIRST_CHAR=`echo $WRAPPER_CONF | cut -c1,1`
+if [ "$FIRST_CHAR" != "/" ]
+then
+    WRAPPER_CONF=$REALDIR/$WRAPPER_CONF
+fi
+
+# Process ID
+ANCHORFILE="$PIDDIR/$APP_NAME.anchor"
+STATUSFILE="$PIDDIR/$APP_NAME.status"
+JAVASTATUSFILE="$PIDDIR/$APP_NAME.java.status"
+PIDFILE="$PIDDIR/$APP_NAME.pid"
+LOCKDIR="/var/lock/subsys"
+LOCKFILE="$LOCKDIR/$APP_NAME"
+pid=""
+
+# Resolve the location of the 'ps' command
+PSEXE="/usr/ucb/ps"
+    if [ ! -x "$PSEXE" ]
+    then
+        PSEXE="/usr/bin/ps"
+        if [ ! -x "$PSEXE" ]
+        then
+            PSEXE="/bin/ps"
+            if [ ! -x "$PSEXE" ]
+            then
+                echo "Unable to locate 'ps'."
+                echo "Please report this message along with the location of the command on your system."
+                exit 1
+            fi
+        fi
+    fi
+
+# Resolve the os
+DIST_OS=`uname -s | tr [:upper:] [:lower:] | tr -d [:blank:]`
+case "$DIST_OS" in
+    'sunos')
+        DIST_OS="solaris"
+        ;;
+    'hp-ux' | 'hp-ux64')
+        # HP-UX needs the XPG4 version of ps (for -o args)
+        DIST_OS="hpux"
+        UNIX95=""
+        export UNIX95   
+        ;;
+    'darwin')
+        DIST_OS="macosx"
+        ;;
+    'unix_sv')
+        DIST_OS="unixware"
+        ;;
+esac
+
+# Resolve the architecture
+if [ "$DIST_OS" = "macosx" ]
+then
+    DIST_ARCH="universal"
+else
+    DIST_ARCH=
+    DIST_ARCH=`uname -p 2>/dev/null | tr [:upper:] [:lower:] | tr -d [:blank:]`
+    if [ "X$DIST_ARCH" = "X" ]
+    then
+        DIST_ARCH="unknown"
+    fi
+    if [ "$DIST_ARCH" = "unknown" ]
+    then
+        DIST_ARCH=`uname -m 2>/dev/null | tr [:upper:] [:lower:] | tr -d [:blank:]`
+    fi
+    case "$DIST_ARCH" in
+        'amd64' | 'athlon' | 'i386' | 'i486' | 'i586' | 'i686' | 'x86_64')
+            DIST_ARCH="x86"
+            ;;
+        'ia32' | 'ia64' | 'ia64n' | 'ia64w')
+            DIST_ARCH="ia"
+            ;;
+        'ip27')
+            DIST_ARCH="mips"
+            ;;
+        'power' | 'powerpc' | 'power_pc' | 'ppc64')
+            DIST_ARCH="ppc"
+            ;;
+        'pa_risc' | 'pa-risc')
+            DIST_ARCH="parisc"
+            ;;
+        'sun4u' | 'sparcv9')
+            DIST_ARCH="sparc"
+            ;;
+        '9000/800')
+            DIST_ARCH="parisc"
+            ;;
+    esac
+fi
+
+# OSX always places Java in the same location so we can reliably set JAVA_HOME
+if [ "$DIST_OS" = "macosx" ]
+then
+    if [ -z "$JAVA_HOME" ]; then
+        JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
+    fi
+fi
+
+outputFile() {
+    if [ -f "$1" ]
+    then
+        echo "  $1 (Found but not executable.)";
+    else
+        echo "  $1"
+    fi
+}
+
+# Decide on the wrapper binary to use.
+# If a 32-bit wrapper binary exists then it will work on 32 or 64 bit
+#  platforms, if the 64-bit binary exists then the distribution most
+#  likely wants to use long names.  Otherwise, look for the default.
+WRAPPER_TEST_CMD="$WRAPPER_CMD-$DIST_OS-$DIST_ARCH-32"
+if [ -x "$WRAPPER_TEST_CMD" ]
+then
+    WRAPPER_CMD="$WRAPPER_TEST_CMD"
+else
+    WRAPPER_TEST_CMD="$WRAPPER_CMD-$DIST_OS-$DIST_ARCH-64"
+    if [ -x "$WRAPPER_TEST_CMD" ]
+    then
+        WRAPPER_CMD="$WRAPPER_TEST_CMD"
+    else
+        if [ ! -x "$WRAPPER_CMD" ]
+        then
+            echo "Unable to locate any of the following binaries:"
+            outputFile "$WRAPPER_CMD-$DIST_OS-$DIST_ARCH-32"
+            outputFile "$WRAPPER_CMD-$DIST_OS-$DIST_ARCH-64"
+            outputFile "$WRAPPER_CMD"
+            exit 1
+        fi
+    fi
+fi
+
+# Build the nice clause
+if [ "X$PRIORITY" = "X" ]
+then
+    CMDNICE=""
+else
+    CMDNICE="nice -$PRIORITY"
+fi
+
+# Build the anchor file clause.
+if [ "X$IGNORE_SIGNALS" = "X" ]
+then
+   ANCHORPROP=
+   IGNOREPROP=
+else
+   ANCHORPROP=wrapper.anchorfile=\"$ANCHORFILE\"
+   IGNOREPROP=wrapper.ignore_signals=TRUE
+fi
+
+# Build the status file clause.
+if [ "X$DETAIL_STATUS" = "X" ]
+then
+   STATUSPROP=
+else
+   STATUSPROP="wrapper.statusfile=\"$STATUSFILE\" wrapper.java.statusfile=\"$JAVASTATUSFILE\""
+fi
+
+# Build the lock file clause.  Only create a lock file if the lock directory exists on this platform.
+LOCKPROP=
+if [ -d $LOCKDIR ]
+then
+    if [ -w $LOCKDIR ]
+    then
+        LOCKPROP=wrapper.lockfile=\"$LOCKFILE\"
+    fi
+fi
+
+checkUser() {
+    # $1 touchLock flag
+    # $2 command
+
+    # Check the configured user.  If necessary rerun this script as the desired user.
+    if [ "X$RUN_AS_USER" != "X" ]
+    then
+        # Resolve the location of the 'id' command
+        IDEXE="/usr/xpg4/bin/id"
+        if [ ! -x "$IDEXE" ]
+        then
+            IDEXE="/usr/bin/id"
+            if [ ! -x "$IDEXE" ]
+            then
+                echo "Unable to locate 'id'."
+                echo "Please report this message along with the location of the command on your system."
+                exit 1
+            fi
+        fi
+    
+        if [ "`$IDEXE -u -n`" = "$RUN_AS_USER" ]
+        then
+            # Already running as the configured user.  Avoid password prompts by not calling su.
+            RUN_AS_USER=""
+        fi
+    fi
+    if [ "X$RUN_AS_USER" != "X" ]
+    then
+        # If LOCKPROP and $RUN_AS_USER are defined then the new user will most likely not be
+        # able to create the lock file.  The Wrapper will be able to update this file once it
+        # is created but will not be able to delete it on shutdown.  If $2 is defined then
+        # the lock file should be created for the current command
+        if [ "X$LOCKPROP" != "X" ]
+        then
+            if [ "X$1" != "X" ]
+            then
+                # Resolve the primary group 
+                RUN_AS_GROUP=`groups $RUN_AS_USER | awk '{print $3}' | tail -1`
+                if [ "X$RUN_AS_GROUP" = "X" ]
+                then
+                    RUN_AS_GROUP=$RUN_AS_USER
+                fi
+                touch $LOCKFILE
+                chown $RUN_AS_USER:$RUN_AS_GROUP $LOCKFILE
+            fi
+        fi
+
+        # Still want to change users, recurse.  This means that the user will only be
+        #  prompted for a password once. Variables shifted by 1
+        # 
+        # Use "runuser" if this exists.  runuser should be used on RedHat in preference to su.
+        #
+        if test -f "/sbin/runuser"
+        then
+            /sbin/runuser - $RUN_AS_USER -c "\"$REALPATH\" $2"
+        else
+            su - $RUN_AS_USER -c "\"$REALPATH\" $2"
+        fi
+
+        # Now that we are the original user again, we may need to clean up the lock file.
+        if [ "X$LOCKPROP" != "X" ]
+        then
+            getpid
+            if [ "X$pid" = "X" ]
+            then
+                # Wrapper is not running so make sure the lock file is deleted.
+                if [ -f "$LOCKFILE" ]
+                then
+                    rm "$LOCKFILE"
+                fi
+            fi
+        fi
+
+        exit 0
+    fi
+}
+
+getpid() {
+    pid=""
+    if [ -f "$PIDFILE" ]
+    then
+        if [ -r "$PIDFILE" ]
+        then
+            pid=`cat "$PIDFILE"`
+            if [ "X$pid" != "X" ]
+            then
+                # It is possible that 'a' process with the pid exists but that it is not the
+                #  correct process.  This can happen in a number of cases, but the most
+                #  common is during system startup after an unclean shutdown.
+                # The ps statement below looks for the specific wrapper command running as
+                #  the pid.  If it is not found then the pid file is considered to be stale.
+                case "$DIST_OS" in
+                    'macosx')
+                        pidtest=`$PSEXE -ww -p $pid -o command | grep "$WRAPPER_CMD" | tail -1`
+                        ;;
+                    'solaris')
+                        pidtest=`$PSEXE -auxww  $pid | grep "$WRAPPER_CMD" | tail -1`
+                        ;;                    
+                    'hpux')
+                        pidtest=`$PSEXE -p $pid -x -o args | grep "$WRAPPER_CMD" | tail -1`
+                        ;;
+                    *)
+                        pidtest=`$PSEXE -p $pid -o args | grep "$WRAPPER_CMD" | tail -1`
+                        ;;
+                esac
+
+                if [ "X$pidtest" = "X" ]
+                then
+                    # This is a stale pid file.
+                    rm -f "$PIDFILE"
+                    echo "Removed stale pid file: $PIDFILE"
+                    pid=""
+                fi
+            fi
+        else
+            echo "Cannot read $PIDFILE."
+            exit 1
+        fi
+    fi
+}
+
+getstatus() {
+    STATUS=
+    if [ -f "$STATUSFILE" ]
+    then
+        if [ -r "$STATUSFILE" ]
+        then
+            STATUS=`cat "$STATUSFILE"`
+        fi
+    fi
+    if [ "X$STATUS" = "X" ]
+    then
+        STATUS="Unknown"
+    fi
+    
+    JAVASTATUS=
+    if [ -f "$JAVASTATUSFILE" ]
+    then
+        if [ -r "$JAVASTATUSFILE" ]
+        then
+            JAVASTATUS=`cat "$JAVASTATUSFILE"`
+        fi
+    fi
+    if [ "X$JAVASTATUS" = "X" ]
+    then
+        JAVASTATUS="Unknown"
+    fi
+}
+
+testpid() {
+    case "$DIST_OS" in
+        'solaris')
+            pid=`$PSEXE  $pid | grep $pid | grep -v grep | awk '{print $1}' | tail -1`
+            ;;
+        *)
+            pid=`$PSEXE -p $pid | grep $pid | grep -v grep | awk '{print $1}' | tail -1`
+            ;;
+    esac
+    if [ "X$pid" = "X" ]
+    then
+        # Process is gone so remove the pid file.
+        rm -f "$PIDFILE"
+        pid=""
+    fi
+}
+
+console() {
+    echo "Running $APP_LONG_NAME..."
+    getpid
+    if [ "X$pid" = "X" ]
+    then
+        # The string passed to eval must handles spaces in paths correctly.
+        COMMAND_LINE="$CMDNICE \"$WRAPPER_CMD\" \"$WRAPPER_CONF\" wrapper.syslog.ident=\"$APP_NAME\" wrapper.pidfile=\"$PIDFILE\" wrapper.name=\"$APP_NAME\" wrapper.displayname=\"$APP_LONG_NAME\" $ANCHORPROP $STATUSPROP $LOCKPROP"
+        eval $COMMAND_LINE
+    else
+        echo "$APP_LONG_NAME is already running."
+        exit 1
+    fi
+}
+ 
+start() {
+    echo -n "Starting $APP_LONG_NAME..."
+    getpid
+    if [ "X$pid" = "X" ]
+    then
+        # The string passed to eval must handles spaces in paths correctly.
+        COMMAND_LINE="$CMDNICE \"$WRAPPER_CMD\" \"$WRAPPER_CONF\" wrapper.syslog.ident=\"$APP_NAME\" wrapper.pidfile=\"$PIDFILE\" wrapper.name=\"$APP_NAME\" wrapper.displayname=\"$APP_LONG_NAME\" wrapper.daemonize=TRUE $ANCHORPROP $IGNOREPROP $STATUSPROP $LOCKPROP"
+        eval $COMMAND_LINE
+    else
+        echo "$APP_LONG_NAME is already running."
+        exit 1
+    fi
+
+    # Sleep for a few seconds to allow for intialization if required 
+    #  then test to make sure we're still running.
+    #
+    i=0
+    while [ $i -lt $WAIT_AFTER_STARTUP ]
+    do
+        sleep 1
+        echo -n "."
+        i=`expr $i + 1`
+    done
+    if [ $WAIT_AFTER_STARTUP -gt 0 ]
+    then
+        getpid
+        if [ "X$pid" = "X" ]
+        then
+            echo " WARNING: $APP_LONG_NAME may have failed to start."
+            exit 1
+        else
+            echo " running ($pid)."
+        fi
+    else 
+        echo ""
+    fi
+}
+ 
+stopit() {
+    # $1 exit if down flag
+    
+    echo "Stopping $APP_LONG_NAME..."
+    getpid
+    if [ "X$pid" = "X" ]
+    then
+        echo "$APP_LONG_NAME was not running."
+        if [ "X$1" = "X1" ]
+        then
+            exit 1
+        fi
+    else
+        if [ "X$IGNORE_SIGNALS" = "X" ]
+        then
+            # Running so try to stop it.
+            kill $pid
+            if [ $? -ne 0 ]
+            then
+                # An explanation for the failure should have been given
+                echo "Unable to stop $APP_LONG_NAME."
+                exit 1
+            fi
+        else
+            rm -f "$ANCHORFILE"
+            if [ -f "$ANCHORFILE" ]
+            then
+                # An explanation for the failure should have been given
+                echo "Unable to stop $APP_LONG_NAME."
+                exit 1
+            fi
+        fi
+
+        # We can not predict how long it will take for the wrapper to
+        #  actually stop as it depends on settings in wrapper.conf.
+        #  Loop until it does.
+        savepid=$pid
+        CNT=0
+        TOTCNT=0
+        while [ "X$pid" != "X" ]
+        do
+            # Show a waiting message every 5 seconds.
+            if [ "$CNT" -lt "5" ]
+            then
+                CNT=`expr $CNT + 1`
+            else
+                echo "Waiting for $APP_LONG_NAME to exit..."
+                CNT=0
+            fi
+            TOTCNT=`expr $TOTCNT + 1`
+
+            sleep 1
+
+            testpid
+        done
+
+        pid=$savepid
+        testpid
+        if [ "X$pid" != "X" ]
+        then
+            echo "Failed to stop $APP_LONG_NAME."
+            exit 1
+        else
+            echo "Stopped $APP_LONG_NAME."
+        fi
+    fi
+}
+
+status() {
+    getpid
+    if [ "X$pid" = "X" ]
+    then
+        echo "$APP_LONG_NAME is not running."
+        exit 1
+    else
+        if [ "X$DETAIL_STATUS" = "X" ]
+        then
+            echo "$APP_LONG_NAME is running (PID:$pid)."
+        else
+            getstatus
+            echo "$APP_LONG_NAME is running (PID:$pid, Wrapper:$STATUS, Java:$JAVASTATUS)"
+        fi
+        exit 0
+    fi
+}
+
+dump() {
+    echo "Dumping $APP_LONG_NAME..."
+    getpid
+    if [ "X$pid" = "X" ]
+    then
+        echo "$APP_LONG_NAME was not running."
+    else
+        kill -3 $pid
+
+        if [ $? -ne 0 ]
+        then
+            echo "Failed to dump $APP_LONG_NAME."
+            exit 1
+        else
+            echo "Dumped $APP_LONG_NAME."
+        fi
+    fi
+}
+
+# Used by HP-UX init scripts.
+startmsg() {
+    getpid
+    if [ "X$pid" = "X" ]
+    then
+        echo "Starting $APP_LONG_NAME... (Wrapper:Stopped)"
+    else
+        if [ "X$DETAIL_STATUS" = "X" ]
+        then
+            echo "Starting $APP_LONG_NAME... (Wrapper:Running)"
+        else
+            getstatus
+            echo "Starting $APP_LONG_NAME... (Wrapper:$STATUS, Java:$JAVASTATUS)"
+        fi
+    fi
+}
+
+# Used by HP-UX init scripts.
+stopmsg() {
+    getpid
+    if [ "X$pid" = "X" ]
+    then
+        echo "Stopping $APP_LONG_NAME... (Wrapper:Stopped)"
+    else
+        if [ "X$DETAIL_STATUS" = "X" ]
+        then
+            echo "Stopping $APP_LONG_NAME... (Wrapper:Running)"
+        else
+            getstatus
+            echo "Stopping $APP_LONG_NAME... (Wrapper:$STATUS, Java:$JAVASTATUS)"
+        fi
+    fi
+}
+
+case "$1" in
+
+    'console')
+        checkUser touchlock $1
+        console
+        ;;
+
+    'start')
+        checkUser touchlock $1
+        start
+        ;;
+
+    'stop')
+        checkUser "" $1
+        stopit "0"
+        ;;
+
+    'restart')
+        checkUser touchlock $1
+        stopit "0"
+        start
+        ;;
+
+    'condrestart')
+        checkUser touchlock $1
+        stopit "1"
+        start
+        ;;
+
+    'status')
+        checkUser "" $1
+        status
+        ;;
+
+    'dump')
+        checkUser "" $1
+        dump
+        ;;
+
+    'start_msg')
+        checkUser "" $1
+        startmsg
+        ;;
+
+    'stop_msg')
+        checkUser "" $1
+        stopmsg
+        ;;
+
+    *)
+        echo "Usage: $0 { console | start | stop | restart | condrestart | status | dump }"
+        exit 1
+        ;;
+esac
+
+exit 0
diff --git a/src/contribs/wrapper/bin/davmail_2 b/src/contribs/wrapper/bin/davmail_2
new file mode 100644
index 0000000..b6603b2
--- /dev/null
+++ b/src/contribs/wrapper/bin/davmail_2
@@ -0,0 +1,732 @@
+#! /bin/sh
+
+#
+# Copyright (c) 1999, 2009 Tanuki Software, Ltd.
+# http://www.tanukisoftware.com
+# All rights reserved.
+#
+# This software is the proprietary information of Tanuki Software.
+# You shall use it only in accordance with the terms of the
+# license agreement you entered into with Tanuki Software.
+# http://wrapper.tanukisoftware.org/doc/english/licenseOverview.html
+#
+# Java Service Wrapper sh script.  Suitable for starting and stopping
+#  wrapped Java applications on UNIX platforms.
+#
+
+#-----------------------------------------------------------------------------
+# These settings can be modified to fit the needs of your application
+# Optimized for use with version 3.3.6 of the Wrapper.
+
+# Application
+BASEDIR="/opt/davmail/bin"
+APP_NAME="davmail_2"
+APP_LONG_NAME="DavMail Exchange Gateway #2"
+
+# Wrapper
+WRAPPER_CMD="$BASEDIR/wrapper"
+WRAPPER_CONF="$BASEDIR/../conf/wrapper.conf_2"
+
+# Priority at which to run the wrapper.  See "man nice" for valid priorities.
+#  nice is only used if a priority is specified.
+PRIORITY=
+
+# Location of the pid file.
+PIDDIR="$BASEDIR"
+
+# If uncommented, causes the Wrapper to be shutdown using an anchor file.
+#  When launched with the 'start' command, it will also ignore all INT and
+#  TERM signals.
+#IGNORE_SIGNALS=true
+
+# Wrapper will start the JVM asynchronously. Your application may have some
+#  initialization tasks and it may be desirable to wait a few seconds
+#  before returning.  For example, to delay the invocation of following
+#  startup scripts.  Setting WAIT_AFTER_STARTUP to a positive number will
+#  cause the start command to delay for the indicated period of time 
+#  (in seconds).
+# 
+WAIT_AFTER_STARTUP=0
+
+# If set, the status, start_msg and stop_msg commands will print out detailed
+#   state information on the Wrapper and Java processes.
+#DETAIL_STATUS=true
+
+# If specified, the Wrapper will be run as the specified user.
+# IMPORTANT - Make sure that the user has the required privileges to write
+#  the PID file and wrapper.log files.  Failure to be able to write the log
+#  file will cause the Wrapper to exit without any way to write out an error
+#  message.
+# NOTE - This will set the user which is used to run the Wrapper as well as
+#  the JVM and is not useful in situations where a privileged resource or
+#  port needs to be allocated prior to the user being changed.
+#RUN_AS_USER=
+
+# The following two lines are used by the chkconfig command. Change as is
+#  appropriate for your application.  They should remain commented.
+# chkconfig: 2345 20 80
+# description: @app.long.name@
+ 
+# Initialization block for the install_initd and remove_initd scripts used by
+#  SUSE linux distributions.
+### BEGIN INIT INFO
+# Provides: @app.name@
+# Required-Start: $local_fs $network $syslog
+# Should-Start: 
+# Required-Stop:
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: @app.long.name@
+# Description: @app.description@
+### END INIT INFO
+
+# Do not modify anything beyond this point
+#-----------------------------------------------------------------------------
+
+# Get the fully qualified path to the script
+case $0 in
+    /*)
+        SCRIPT="$0"
+        ;;
+    *)
+        PWD=`pwd`
+        SCRIPT="$PWD/$0"
+        ;;
+esac
+
+# Resolve the true real path without any sym links.
+CHANGED=true
+while [ "X$CHANGED" != "X" ]
+do
+    # Change spaces to ":" so the tokens can be parsed.
+    SAFESCRIPT=`echo $SCRIPT | sed -e 's; ;:;g'`
+    # Get the real path to this script, resolving any symbolic links
+    TOKENS=`echo $SAFESCRIPT | sed -e 's;/; ;g'`
+    REALPATH=
+    for C in $TOKENS; do
+        # Change any ":" in the token back to a space.
+        C=`echo $C | sed -e 's;:; ;g'`
+        REALPATH="$REALPATH/$C"
+        # If REALPATH is a sym link, resolve it.  Loop for nested links.
+        while [ -h "$REALPATH" ] ; do
+            LS="`ls -ld "$REALPATH"`"
+            LINK="`expr "$LS" : '.*-> \(.*\)$'`"
+            if expr "$LINK" : '/.*' > /dev/null; then
+                # LINK is absolute.
+                REALPATH="$LINK"
+            else
+                # LINK is relative.
+                REALPATH="`dirname "$REALPATH"`""/$LINK"
+            fi
+        done
+    done
+
+    if [ "$REALPATH" = "$SCRIPT" ]
+    then
+        CHANGED=""
+    else
+        SCRIPT="$REALPATH"
+    fi
+done
+
+# Change the current directory to the location of the script
+cd "`dirname "$REALPATH"`"
+REALDIR=`pwd`
+
+# If the PIDDIR is relative, set its value relative to the full REALPATH to avoid problems if
+#  the working directory is later changed.
+FIRST_CHAR=`echo $PIDDIR | cut -c1,1`
+if [ "$FIRST_CHAR" != "/" ]
+then
+    PIDDIR=$REALDIR/$PIDDIR
+fi
+# Same test for WRAPPER_CMD
+FIRST_CHAR=`echo $WRAPPER_CMD | cut -c1,1`
+if [ "$FIRST_CHAR" != "/" ]
+then
+    WRAPPER_CMD=$REALDIR/$WRAPPER_CMD
+fi
+# Same test for WRAPPER_CONF
+FIRST_CHAR=`echo $WRAPPER_CONF | cut -c1,1`
+if [ "$FIRST_CHAR" != "/" ]
+then
+    WRAPPER_CONF=$REALDIR/$WRAPPER_CONF
+fi
+
+# Process ID
+ANCHORFILE="$PIDDIR/$APP_NAME.anchor"
+STATUSFILE="$PIDDIR/$APP_NAME.status"
+JAVASTATUSFILE="$PIDDIR/$APP_NAME.java.status"
+PIDFILE="$PIDDIR/$APP_NAME.pid"
+LOCKDIR="/var/lock/subsys"
+LOCKFILE="$LOCKDIR/$APP_NAME"
+pid=""
+
+# Resolve the location of the 'ps' command
+PSEXE="/usr/ucb/ps"
+    if [ ! -x "$PSEXE" ]
+    then
+        PSEXE="/usr/bin/ps"
+        if [ ! -x "$PSEXE" ]
+        then
+            PSEXE="/bin/ps"
+            if [ ! -x "$PSEXE" ]
+            then
+                echo "Unable to locate 'ps'."
+                echo "Please report this message along with the location of the command on your system."
+                exit 1
+            fi
+        fi
+    fi
+
+# Resolve the os
+DIST_OS=`uname -s | tr [:upper:] [:lower:] | tr -d [:blank:]`
+case "$DIST_OS" in
+    'sunos')
+        DIST_OS="solaris"
+        ;;
+    'hp-ux' | 'hp-ux64')
+        # HP-UX needs the XPG4 version of ps (for -o args)
+        DIST_OS="hpux"
+        UNIX95=""
+        export UNIX95   
+        ;;
+    'darwin')
+        DIST_OS="macosx"
+        ;;
+    'unix_sv')
+        DIST_OS="unixware"
+        ;;
+esac
+
+# Resolve the architecture
+if [ "$DIST_OS" = "macosx" ]
+then
+    DIST_ARCH="universal"
+else
+    DIST_ARCH=
+    DIST_ARCH=`uname -p 2>/dev/null | tr [:upper:] [:lower:] | tr -d [:blank:]`
+    if [ "X$DIST_ARCH" = "X" ]
+    then
+        DIST_ARCH="unknown"
+    fi
+    if [ "$DIST_ARCH" = "unknown" ]
+    then
+        DIST_ARCH=`uname -m 2>/dev/null | tr [:upper:] [:lower:] | tr -d [:blank:]`
+    fi
+    case "$DIST_ARCH" in
+        'amd64' | 'athlon' | 'i386' | 'i486' | 'i586' | 'i686' | 'x86_64')
+            DIST_ARCH="x86"
+            ;;
+        'ia32' | 'ia64' | 'ia64n' | 'ia64w')
+            DIST_ARCH="ia"
+            ;;
+        'ip27')
+            DIST_ARCH="mips"
+            ;;
+        'power' | 'powerpc' | 'power_pc' | 'ppc64')
+            DIST_ARCH="ppc"
+            ;;
+        'pa_risc' | 'pa-risc')
+            DIST_ARCH="parisc"
+            ;;
+        'sun4u' | 'sparcv9')
+            DIST_ARCH="sparc"
+            ;;
+        '9000/800')
+            DIST_ARCH="parisc"
+            ;;
+    esac
+fi
+
+# OSX always places Java in the same location so we can reliably set JAVA_HOME
+if [ "$DIST_OS" = "macosx" ]
+then
+    if [ -z "$JAVA_HOME" ]; then
+        JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
+    fi
+fi
+
+outputFile() {
+    if [ -f "$1" ]
+    then
+        echo "  $1 (Found but not executable.)";
+    else
+        echo "  $1"
+    fi
+}
+
+# Decide on the wrapper binary to use.
+# If a 32-bit wrapper binary exists then it will work on 32 or 64 bit
+#  platforms, if the 64-bit binary exists then the distribution most
+#  likely wants to use long names.  Otherwise, look for the default.
+WRAPPER_TEST_CMD="$WRAPPER_CMD-$DIST_OS-$DIST_ARCH-32"
+if [ -x "$WRAPPER_TEST_CMD" ]
+then
+    WRAPPER_CMD="$WRAPPER_TEST_CMD"
+else
+    WRAPPER_TEST_CMD="$WRAPPER_CMD-$DIST_OS-$DIST_ARCH-64"
+    if [ -x "$WRAPPER_TEST_CMD" ]
+    then
+        WRAPPER_CMD="$WRAPPER_TEST_CMD"
+    else
+        if [ ! -x "$WRAPPER_CMD" ]
+        then
+            echo "Unable to locate any of the following binaries:"
+            outputFile "$WRAPPER_CMD-$DIST_OS-$DIST_ARCH-32"
+            outputFile "$WRAPPER_CMD-$DIST_OS-$DIST_ARCH-64"
+            outputFile "$WRAPPER_CMD"
+            exit 1
+        fi
+    fi
+fi
+
+# Build the nice clause
+if [ "X$PRIORITY" = "X" ]
+then
+    CMDNICE=""
+else
+    CMDNICE="nice -$PRIORITY"
+fi
+
+# Build the anchor file clause.
+if [ "X$IGNORE_SIGNALS" = "X" ]
+then
+   ANCHORPROP=
+   IGNOREPROP=
+else
+   ANCHORPROP=wrapper.anchorfile=\"$ANCHORFILE\"
+   IGNOREPROP=wrapper.ignore_signals=TRUE
+fi
+
+# Build the status file clause.
+if [ "X$DETAIL_STATUS" = "X" ]
+then
+   STATUSPROP=
+else
+   STATUSPROP="wrapper.statusfile=\"$STATUSFILE\" wrapper.java.statusfile=\"$JAVASTATUSFILE\""
+fi
+
+# Build the lock file clause.  Only create a lock file if the lock directory exists on this platform.
+LOCKPROP=
+if [ -d $LOCKDIR ]
+then
+    if [ -w $LOCKDIR ]
+    then
+        LOCKPROP=wrapper.lockfile=\"$LOCKFILE\"
+    fi
+fi
+
+checkUser() {
+    # $1 touchLock flag
+    # $2 command
+
+    # Check the configured user.  If necessary rerun this script as the desired user.
+    if [ "X$RUN_AS_USER" != "X" ]
+    then
+        # Resolve the location of the 'id' command
+        IDEXE="/usr/xpg4/bin/id"
+        if [ ! -x "$IDEXE" ]
+        then
+            IDEXE="/usr/bin/id"
+            if [ ! -x "$IDEXE" ]
+            then
+                echo "Unable to locate 'id'."
+                echo "Please report this message along with the location of the command on your system."
+                exit 1
+            fi
+        fi
+    
+        if [ "`$IDEXE -u -n`" = "$RUN_AS_USER" ]
+        then
+            # Already running as the configured user.  Avoid password prompts by not calling su.
+            RUN_AS_USER=""
+        fi
+    fi
+    if [ "X$RUN_AS_USER" != "X" ]
+    then
+        # If LOCKPROP and $RUN_AS_USER are defined then the new user will most likely not be
+        # able to create the lock file.  The Wrapper will be able to update this file once it
+        # is created but will not be able to delete it on shutdown.  If $2 is defined then
+        # the lock file should be created for the current command
+        if [ "X$LOCKPROP" != "X" ]
+        then
+            if [ "X$1" != "X" ]
+            then
+                # Resolve the primary group 
+                RUN_AS_GROUP=`groups $RUN_AS_USER | awk '{print $3}' | tail -1`
+                if [ "X$RUN_AS_GROUP" = "X" ]
+                then
+                    RUN_AS_GROUP=$RUN_AS_USER
+                fi
+                touch $LOCKFILE
+                chown $RUN_AS_USER:$RUN_AS_GROUP $LOCKFILE
+            fi
+        fi
+
+        # Still want to change users, recurse.  This means that the user will only be
+        #  prompted for a password once. Variables shifted by 1
+        # 
+        # Use "runuser" if this exists.  runuser should be used on RedHat in preference to su.
+        #
+        if test -f "/sbin/runuser"
+        then
+            /sbin/runuser - $RUN_AS_USER -c "\"$REALPATH\" $2"
+        else
+            su - $RUN_AS_USER -c "\"$REALPATH\" $2"
+        fi
+
+        # Now that we are the original user again, we may need to clean up the lock file.
+        if [ "X$LOCKPROP" != "X" ]
+        then
+            getpid
+            if [ "X$pid" = "X" ]
+            then
+                # Wrapper is not running so make sure the lock file is deleted.
+                if [ -f "$LOCKFILE" ]
+                then
+                    rm "$LOCKFILE"
+                fi
+            fi
+        fi
+
+        exit 0
+    fi
+}
+
+getpid() {
+    pid=""
+    if [ -f "$PIDFILE" ]
+    then
+        if [ -r "$PIDFILE" ]
+        then
+            pid=`cat "$PIDFILE"`
+            if [ "X$pid" != "X" ]
+            then
+                # It is possible that 'a' process with the pid exists but that it is not the
+                #  correct process.  This can happen in a number of cases, but the most
+                #  common is during system startup after an unclean shutdown.
+                # The ps statement below looks for the specific wrapper command running as
+                #  the pid.  If it is not found then the pid file is considered to be stale.
+                case "$DIST_OS" in
+                    'macosx')
+                        pidtest=`$PSEXE -ww -p $pid -o command | grep "$WRAPPER_CMD" | tail -1`
+                        ;;
+                    'solaris')
+                        pidtest=`$PSEXE -auxww  $pid | grep "$WRAPPER_CMD" | tail -1`
+                        ;;                    
+                    'hpux')
+                        pidtest=`$PSEXE -p $pid -x -o args | grep "$WRAPPER_CMD" | tail -1`
+                        ;;
+                    *)
+                        pidtest=`$PSEXE -p $pid -o args | grep "$WRAPPER_CMD" | tail -1`
+                        ;;
+                esac
+
+                if [ "X$pidtest" = "X" ]
+                then
+                    # This is a stale pid file.
+                    rm -f "$PIDFILE"
+                    echo "Removed stale pid file: $PIDFILE"
+                    pid=""
+                fi
+            fi
+        else
+            echo "Cannot read $PIDFILE."
+            exit 1
+        fi
+    fi
+}
+
+getstatus() {
+    STATUS=
+    if [ -f "$STATUSFILE" ]
+    then
+        if [ -r "$STATUSFILE" ]
+        then
+            STATUS=`cat "$STATUSFILE"`
+        fi
+    fi
+    if [ "X$STATUS" = "X" ]
+    then
+        STATUS="Unknown"
+    fi
+    
+    JAVASTATUS=
+    if [ -f "$JAVASTATUSFILE" ]
+    then
+        if [ -r "$JAVASTATUSFILE" ]
+        then
+            JAVASTATUS=`cat "$JAVASTATUSFILE"`
+        fi
+    fi
+    if [ "X$JAVASTATUS" = "X" ]
+    then
+        JAVASTATUS="Unknown"
+    fi
+}
+
+testpid() {
+    case "$DIST_OS" in
+        'solaris')
+            pid=`$PSEXE  $pid | grep $pid | grep -v grep | awk '{print $1}' | tail -1`
+            ;;
+        *)
+            pid=`$PSEXE -p $pid | grep $pid | grep -v grep | awk '{print $1}' | tail -1`
+            ;;
+    esac
+    if [ "X$pid" = "X" ]
+    then
+        # Process is gone so remove the pid file.
+        rm -f "$PIDFILE"
+        pid=""
+    fi
+}
+
+console() {
+    echo "Running $APP_LONG_NAME..."
+    getpid
+    if [ "X$pid" = "X" ]
+    then
+        # The string passed to eval must handles spaces in paths correctly.
+        COMMAND_LINE="$CMDNICE \"$WRAPPER_CMD\" \"$WRAPPER_CONF\" wrapper.syslog.ident=\"$APP_NAME\" wrapper.pidfile=\"$PIDFILE\" wrapper.name=\"$APP_NAME\" wrapper.displayname=\"$APP_LONG_NAME\" $ANCHORPROP $STATUSPROP $LOCKPROP"
+        eval $COMMAND_LINE
+    else
+        echo "$APP_LONG_NAME is already running."
+        exit 1
+    fi
+}
+ 
+start() {
+    echo -n "Starting $APP_LONG_NAME..."
+    getpid
+    if [ "X$pid" = "X" ]
+    then
+        # The string passed to eval must handles spaces in paths correctly.
+        COMMAND_LINE="$CMDNICE \"$WRAPPER_CMD\" \"$WRAPPER_CONF\" wrapper.syslog.ident=\"$APP_NAME\" wrapper.pidfile=\"$PIDFILE\" wrapper.name=\"$APP_NAME\" wrapper.displayname=\"$APP_LONG_NAME\" wrapper.daemonize=TRUE $ANCHORPROP $IGNOREPROP $STATUSPROP $LOCKPROP"
+        eval $COMMAND_LINE
+    else
+        echo "$APP_LONG_NAME is already running."
+        exit 1
+    fi
+
+    # Sleep for a few seconds to allow for intialization if required 
+    #  then test to make sure we're still running.
+    #
+    i=0
+    while [ $i -lt $WAIT_AFTER_STARTUP ]
+    do
+        sleep 1
+        echo -n "."
+        i=`expr $i + 1`
+    done
+    if [ $WAIT_AFTER_STARTUP -gt 0 ]
+    then
+        getpid
+        if [ "X$pid" = "X" ]
+        then
+            echo " WARNING: $APP_LONG_NAME may have failed to start."
+            exit 1
+        else
+            echo " running ($pid)."
+        fi
+    else 
+        echo ""
+    fi
+}
+ 
+stopit() {
+    # $1 exit if down flag
+    
+    echo "Stopping $APP_LONG_NAME..."
+    getpid
+    if [ "X$pid" = "X" ]
+    then
+        echo "$APP_LONG_NAME was not running."
+        if [ "X$1" = "X1" ]
+        then
+            exit 1
+        fi
+    else
+        if [ "X$IGNORE_SIGNALS" = "X" ]
+        then
+            # Running so try to stop it.
+            kill $pid
+            if [ $? -ne 0 ]
+            then
+                # An explanation for the failure should have been given
+                echo "Unable to stop $APP_LONG_NAME."
+                exit 1
+            fi
+        else
+            rm -f "$ANCHORFILE"
+            if [ -f "$ANCHORFILE" ]
+            then
+                # An explanation for the failure should have been given
+                echo "Unable to stop $APP_LONG_NAME."
+                exit 1
+            fi
+        fi
+
+        # We can not predict how long it will take for the wrapper to
+        #  actually stop as it depends on settings in wrapper.conf.
+        #  Loop until it does.
+        savepid=$pid
+        CNT=0
+        TOTCNT=0
+        while [ "X$pid" != "X" ]
+        do
+            # Show a waiting message every 5 seconds.
+            if [ "$CNT" -lt "5" ]
+            then
+                CNT=`expr $CNT + 1`
+            else
+                echo "Waiting for $APP_LONG_NAME to exit..."
+                CNT=0
+            fi
+            TOTCNT=`expr $TOTCNT + 1`
+
+            sleep 1
+
+            testpid
+        done
+
+        pid=$savepid
+        testpid
+        if [ "X$pid" != "X" ]
+        then
+            echo "Failed to stop $APP_LONG_NAME."
+            exit 1
+        else
+            echo "Stopped $APP_LONG_NAME."
+        fi
+    fi
+}
+
+status() {
+    getpid
+    if [ "X$pid" = "X" ]
+    then
+        echo "$APP_LONG_NAME is not running."
+        exit 1
+    else
+        if [ "X$DETAIL_STATUS" = "X" ]
+        then
+            echo "$APP_LONG_NAME is running (PID:$pid)."
+        else
+            getstatus
+            echo "$APP_LONG_NAME is running (PID:$pid, Wrapper:$STATUS, Java:$JAVASTATUS)"
+        fi
+        exit 0
+    fi
+}
+
+dump() {
+    echo "Dumping $APP_LONG_NAME..."
+    getpid
+    if [ "X$pid" = "X" ]
+    then
+        echo "$APP_LONG_NAME was not running."
+    else
+        kill -3 $pid
+
+        if [ $? -ne 0 ]
+        then
+            echo "Failed to dump $APP_LONG_NAME."
+            exit 1
+        else
+            echo "Dumped $APP_LONG_NAME."
+        fi
+    fi
+}
+
+# Used by HP-UX init scripts.
+startmsg() {
+    getpid
+    if [ "X$pid" = "X" ]
+    then
+        echo "Starting $APP_LONG_NAME... (Wrapper:Stopped)"
+    else
+        if [ "X$DETAIL_STATUS" = "X" ]
+        then
+            echo "Starting $APP_LONG_NAME... (Wrapper:Running)"
+        else
+            getstatus
+            echo "Starting $APP_LONG_NAME... (Wrapper:$STATUS, Java:$JAVASTATUS)"
+        fi
+    fi
+}
+
+# Used by HP-UX init scripts.
+stopmsg() {
+    getpid
+    if [ "X$pid" = "X" ]
+    then
+        echo "Stopping $APP_LONG_NAME... (Wrapper:Stopped)"
+    else
+        if [ "X$DETAIL_STATUS" = "X" ]
+        then
+            echo "Stopping $APP_LONG_NAME... (Wrapper:Running)"
+        else
+            getstatus
+            echo "Stopping $APP_LONG_NAME... (Wrapper:$STATUS, Java:$JAVASTATUS)"
+        fi
+    fi
+}
+
+case "$1" in
+
+    'console')
+        checkUser touchlock $1
+        console
+        ;;
+
+    'start')
+        checkUser touchlock $1
+        start
+        ;;
+
+    'stop')
+        checkUser "" $1
+        stopit "0"
+        ;;
+
+    'restart')
+        checkUser touchlock $1
+        stopit "0"
+        start
+        ;;
+
+    'condrestart')
+        checkUser touchlock $1
+        stopit "1"
+        start
+        ;;
+
+    'status')
+        checkUser "" $1
+        status
+        ;;
+
+    'dump')
+        checkUser "" $1
+        dump
+        ;;
+
+    'start_msg')
+        checkUser "" $1
+        startmsg
+        ;;
+
+    'stop_msg')
+        checkUser "" $1
+        stopmsg
+        ;;
+
+    *)
+        echo "Usage: $0 { console | start | stop | restart | condrestart | status | dump }"
+        exit 1
+        ;;
+esac
+
+exit 0
diff --git a/src/contribs/wrapper/conf/wrapper.conf b/src/contribs/wrapper/conf/wrapper.conf
new file mode 100644
index 0000000..2442d7b
--- /dev/null
+++ b/src/contribs/wrapper/conf/wrapper.conf
@@ -0,0 +1,119 @@
+#********************************************************************
+# Wrapper License Properties (Ignored by Community Edition)
+#********************************************************************
+# Include file problems can be debugged by removing the first '#'
+#  from the following line:
+##include.debug
+#include ../conf/wrapper-license.conf
+#include ../conf/wrapper-license-%WRAPPER_HOST_NAME%.conf
+
+#********************************************************************
+# Wrapper Java Properties
+#********************************************************************
+# Java Application
+wrapper.java.command=java
+wrapper.debug=false
+# Tell the Wrapper to log the full generated Java command line.
+#wrapper.java.command.loglevel=INFO
+
+# Java Main class.  This class must implement the WrapperListener interface
+#  or guarantee that the WrapperManager class is initialized.  Helper
+#  classes are provided to do this for you.  See the Integration section
+#  of the documentation for details.
+wrapper.java.mainclass=org.tanukisoftware.wrapper.WrapperSimpleApp
+
+# Java Classpath (include wrapper.jar)  Add class path elements as
+#  needed starting from 1
+wrapper.java.classpath.1=../lib/wrapper.jar
+wrapper.java.classpath.2=../lib/*.jar
+
+
+# Java Library Path (location of Wrapper.DLL or libwrapper.so)
+wrapper.java.library.path.1=../lib
+
+# Java Bits.  On applicable platforms, tells the JVM to run in 32 or 64-bit mode.
+wrapper.java.additional.auto_bits=TRUE
+
+# Java Additional Parameters
+#wrapper.java.additional.1=
+
+# Initial Java Heap Size (in MB)
+wrapper.java.initmemory=3
+
+# Maximum Java Heap Size (in MB)
+wrapper.java.maxmemory=64
+
+# Application parameters.  Add parameters as needed starting from 1
+wrapper.app.parameter.1=davmail.DavGateway
+wrapper.app.parameter.2=../conf/davmail.properties
+
+
+#********************************************************************
+# Wrapper Logging Properties
+#********************************************************************
+# Enables Debug output from the Wrapper.
+# wrapper.debug=TRUE
+
+# Format of output for the console.  (See docs for formats)
+wrapper.console.format=PM
+
+# Log Level for console output.  (See docs for log levels)
+wrapper.console.loglevel=INFO
+
+# Log file to use for wrapper output logging.
+wrapper.logfile=../logs/davmail.log
+
+# Format of output for the log file.  (See docs for formats)
+wrapper.logfile.format=LPTM
+
+# Log Level for log file output.  (See docs for log levels)
+wrapper.logfile.loglevel=INFO
+
+# Maximum size that the log file will be allowed to grow to before
+#  the log is rolled. Size is specified in bytes.  The default value
+#  of 0, disables log rolling.  May abbreviate with the 'k' (kb) or
+#  'm' (mb) suffix.  For example: 10m = 10 megabytes.
+wrapper.logfile.maxsize=10m
+
+# Maximum number of rolled log files which will be allowed before old
+#  files are deleted.  The default value of 0 implies no limit.
+wrapper.logfile.maxfiles=5
+
+# Log Level for sys/event log output.  (See docs for log levels)
+wrapper.syslog.loglevel=NONE
+
+#********************************************************************
+# Wrapper General Properties
+#********************************************************************
+# Allow for the use of non-contiguous numbered properties
+wrapper.ignore_sequence_gaps=TRUE
+
+# Title to use when running as a console
+wrapper.console.title=DavMail
+
+#********************************************************************
+# Wrapper Windows NT/2000/XP Service Properties
+#********************************************************************
+# WARNING - Do not modify any of these properties when an application
+#  using this configuration file has been installed as a service.
+#  Please uninstall the service before modifying this section.  The
+#  service can then be reinstalled.
+
+# Name of the service
+wrapper.name=davmail
+
+# Display name of the service
+wrapper.displayname=DavMail
+
+# Description of the service
+wrapper.description=DavMail 
+
+# Service dependencies.  Add dependencies as needed starting from 1
+wrapper.ntservice.dependency.1=
+
+# Mode in which the service is installed.  AUTO_START or DEMAND_START
+wrapper.ntservice.starttype=AUTO_START
+
+# Allow the service to interact with the desktop.
+wrapper.ntservice.interactive=false
+
diff --git a/src/contribs/wrapper/conf/wrapper.conf_2 b/src/contribs/wrapper/conf/wrapper.conf_2
new file mode 100644
index 0000000..1a531bf
--- /dev/null
+++ b/src/contribs/wrapper/conf/wrapper.conf_2
@@ -0,0 +1,119 @@
+#********************************************************************
+# Wrapper License Properties (Ignored by Community Edition)
+#********************************************************************
+# Include file problems can be debugged by removing the first '#'
+#  from the following line:
+##include.debug
+#include ../conf/wrapper-license.conf
+#include ../conf/wrapper-license-%WRAPPER_HOST_NAME%.conf
+
+#********************************************************************
+# Wrapper Java Properties
+#********************************************************************
+# Java Application
+wrapper.java.command=java
+wrapper.debug=false
+# Tell the Wrapper to log the full generated Java command line.
+#wrapper.java.command.loglevel=INFO
+
+# Java Main class.  This class must implement the WrapperListener interface
+#  or guarantee that the WrapperManager class is initialized.  Helper
+#  classes are provided to do this for you.  See the Integration section
+#  of the documentation for details.
+wrapper.java.mainclass=org.tanukisoftware.wrapper.WrapperSimpleApp
+
+# Java Classpath (include wrapper.jar)  Add class path elements as
+#  needed starting from 1
+wrapper.java.classpath.1=../lib/wrapper.jar
+wrapper.java.classpath.2=../lib/*.jar
+
+
+# Java Library Path (location of Wrapper.DLL or libwrapper.so)
+wrapper.java.library.path.1=../lib
+
+# Java Bits.  On applicable platforms, tells the JVM to run in 32 or 64-bit mode.
+wrapper.java.additional.auto_bits=TRUE
+
+# Java Additional Parameters
+#wrapper.java.additional.1=
+
+# Initial Java Heap Size (in MB)
+wrapper.java.initmemory=3
+
+# Maximum Java Heap Size (in MB)
+wrapper.java.maxmemory=64
+
+# Application parameters.  Add parameters as needed starting from 1
+wrapper.app.parameter.1=davmail.DavGateway
+wrapper.app.parameter.2=../conf/davmail.properties_2
+
+
+#********************************************************************
+# Wrapper Logging Properties
+#********************************************************************
+# Enables Debug output from the Wrapper.
+# wrapper.debug=TRUE
+
+# Format of output for the console.  (See docs for formats)
+wrapper.console.format=PM
+
+# Log Level for console output.  (See docs for log levels)
+wrapper.console.loglevel=INFO
+
+# Log file to use for wrapper output logging.
+wrapper.logfile=../logs/davmail.log_2
+
+# Format of output for the log file.  (See docs for formats)
+wrapper.logfile.format=LPTM
+
+# Log Level for log file output.  (See docs for log levels)
+wrapper.logfile.loglevel=INFO
+
+# Maximum size that the log file will be allowed to grow to before
+#  the log is rolled. Size is specified in bytes.  The default value
+#  of 0, disables log rolling.  May abbreviate with the 'k' (kb) or
+#  'm' (mb) suffix.  For example: 10m = 10 megabytes.
+wrapper.logfile.maxsize=10m
+
+# Maximum number of rolled log files which will be allowed before old
+#  files are deleted.  The default value of 0 implies no limit.
+wrapper.logfile.maxfiles=5
+
+# Log Level for sys/event log output.  (See docs for log levels)
+wrapper.syslog.loglevel=NONE
+
+#********************************************************************
+# Wrapper General Properties
+#********************************************************************
+# Allow for the use of non-contiguous numbered properties
+wrapper.ignore_sequence_gaps=TRUE
+
+# Title to use when running as a console
+wrapper.console.title=DavMail
+
+#********************************************************************
+# Wrapper Windows NT/2000/XP Service Properties
+#********************************************************************
+# WARNING - Do not modify any of these properties when an application
+#  using this configuration file has been installed as a service.
+#  Please uninstall the service before modifying this section.  The
+#  service can then be reinstalled.
+
+# Name of the service
+wrapper.name=davmail
+
+# Display name of the service
+wrapper.displayname=DavMail
+
+# Description of the service
+wrapper.description=DavMail 
+
+# Service dependencies.  Add dependencies as needed starting from 1
+wrapper.ntservice.dependency.1=
+
+# Mode in which the service is installed.  AUTO_START or DEMAND_START
+wrapper.ntservice.starttype=AUTO_START
+
+# Allow the service to interact with the desktop.
+wrapper.ntservice.interactive=false
+
diff --git a/src/contribs/wrapper/readme.txt b/src/contribs/wrapper/readme.txt
new file mode 100644
index 0000000..8d6a65d
--- /dev/null
+++ b/src/contribs/wrapper/readme.txt
@@ -0,0 +1,39 @@
+From Dustin Hawkins:
+
+I run two instances of DavMail on my linux desktop to connect to work and university exchange servers. 
+In order to accomplish this, I used the Java Service Wrapper.
+
+Thought I would share my configs in case any one else wanted to run multi-instance under the java service wrapper.
+
+http://wrapper.tanukisoftware.org/doc/english/download.jsp
+
+
+I created  the following directory structure
+
+/opt/davmail
+    bin/
+        wrapper   <-- this is provided by java service wrapper. platform specific native executable.
+        davmail
+        davmail_2
+    lib/
+        <davmail jars>
+        <wrapper static objects/dll's for appropriate platform>
+    conf/
+        wrapper.conf
+        wrapper.conf_2
+        davmail.properties
+        davmail.properties_2
+    logs/
+
+
+The bin/davmail* scripts are the linux start/stop scripts for each instance
+the conf/ directory has two files for each instance, a wrapper.conf, and a davmail.properties.
+
+by linking the bin/davmail* scripts into /etc/init.d and /etc/rc.d, I start davmail as a linux service, 
+and its easily start/stop/restart-able via the standard linux commands.
+
+If you download the Java Service Wrapper, you can find the appropriate executables and start scripts for 
+your platform under <wrapper install dir>/bin
+You can find the correct static object or DLL files under the <wrapper install dir>/lib
+
+I have included a ZIP of my wrapper.conf and davmail startup scripts
diff --git a/src/java/com/ctc/wstx/sr/StreamScanner.java b/src/java/com/ctc/wstx/sr/StreamScanner.java
new file mode 100644
index 0000000..215cc53
--- /dev/null
+++ b/src/java/com/ctc/wstx/sr/StreamScanner.java
@@ -0,0 +1,2444 @@
+/* Woodstox XML processor
+ *
+ * Copyright (c) 2004- Tatu Saloranta, tatu.saloranta at iki.fi
+ *
+ * Licensed under the License specified in file LICENSE, included with
+ * the source code.
+ * You may not use this file except in compliance with the License.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ctc.wstx.sr;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.URL;
+import java.text.MessageFormat;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.xml.stream.Location;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLReporter;
+import javax.xml.stream.XMLResolver;
+import javax.xml.stream.XMLStreamException;
+
+import org.codehaus.stax2.XMLReporter2;
+import org.codehaus.stax2.XMLStreamLocation2;
+import org.codehaus.stax2.validation.XMLValidationProblem;
+
+import com.ctc.wstx.api.ReaderConfig;
+import com.ctc.wstx.cfg.ErrorConsts;
+import com.ctc.wstx.cfg.InputConfigFlags;
+import com.ctc.wstx.cfg.ParsingErrorMsgs;
+import com.ctc.wstx.cfg.XmlConsts;
+import com.ctc.wstx.dtd.MinimalDTDReader;
+import com.ctc.wstx.ent.EntityDecl;
+import com.ctc.wstx.ent.IntEntity;
+import com.ctc.wstx.exc.*;
+import com.ctc.wstx.io.DefaultInputResolver;
+import com.ctc.wstx.io.WstxInputData;
+import com.ctc.wstx.io.WstxInputLocation;
+import com.ctc.wstx.io.WstxInputSource;
+import com.ctc.wstx.util.ExceptionUtil;
+import com.ctc.wstx.util.SymbolTable;
+import com.ctc.wstx.util.TextBuffer;
+
+/**
+ * Abstract base class that defines some basic functionality that all
+ * Woodstox reader classes (main XML reader, DTD reader) extend from.
+ */
+
+public abstract class StreamScanner
+    extends WstxInputData
+    implements InputProblemReporter,
+        InputConfigFlags, ParsingErrorMsgs
+{
+
+    // // // Some well-known chars:
+
+    /**
+     * Last (highest) char code of the three, LF, CR and NULL
+     */
+    public final static char CHAR_CR_LF_OR_NULL = (char) 13;
+
+    public final static int INT_CR_LF_OR_NULL = 13;
+
+    /**
+     * Character that allows quick check of whether a char can potentially
+     * be some kind of markup, WRT input stream processing;
+     * has to contain linefeeds, &, < and > (">" only matters when
+     * quoting text, as part of "]]>")
+     */
+    protected final static char CHAR_FIRST_PURE_TEXT = (char) ('>' + 1);
+
+
+    /**
+     * First character in Unicode (ie one with lowest id) that is legal
+     * as part of a local name (all valid name chars minus ':'). Used
+     * for doing quick check for local name end; usually name ends in
+     * a whitespace or equals sign.
+     */
+    protected final static char CHAR_LOWEST_LEGAL_LOCALNAME_CHAR = '-';
+
+    /*
+    ///////////////////////////////////////////////////////////
+    // Character validity constants, structs
+    ///////////////////////////////////////////////////////////
+     */
+
+    /**
+     * We will only use validity array for first 256 characters, mostly
+     * because after those characters it's easier to do fairly simple
+     * block checks.
+     */
+    private final static int VALID_CHAR_COUNT = 0x100;
+
+    private final static byte NAME_CHAR_INVALID_B = (byte) 0;
+    private final static byte NAME_CHAR_ALL_VALID_B = (byte) 1;
+    private final static byte NAME_CHAR_VALID_NONFIRST_B = (byte) -1;
+
+    private final static byte[] sCharValidity = new byte[VALID_CHAR_COUNT];
+
+    static {
+        /* First, since all valid-as-first chars are also valid-as-other chars,
+         * we'll initialize common chars:
+         */
+        sCharValidity['_'] = NAME_CHAR_ALL_VALID_B;
+        for (int i = 0, last = ('z' - 'a'); i <= last; ++i) {
+            sCharValidity['A' + i] = NAME_CHAR_ALL_VALID_B;
+            sCharValidity['a' + i] = NAME_CHAR_ALL_VALID_B;
+        }
+        for (int i = 0xC0; i < 0xF6; ++i) { // not all are fully valid, but
+            sCharValidity[i] = NAME_CHAR_ALL_VALID_B;
+        }
+        // ... now we can 'revert' ones not fully valid:
+        sCharValidity[0xD7] = NAME_CHAR_INVALID_B;
+        sCharValidity[0xF7] = NAME_CHAR_INVALID_B;
+
+        /* And then we can proceed with ones only valid-as-other.
+         */
+        sCharValidity['-'] = NAME_CHAR_VALID_NONFIRST_B;
+        sCharValidity['.'] = NAME_CHAR_VALID_NONFIRST_B;
+        sCharValidity[0xB7] = NAME_CHAR_VALID_NONFIRST_B;
+        for (int i = '0'; i <= '9'; ++i) {
+            sCharValidity[i] = NAME_CHAR_VALID_NONFIRST_B;
+        }
+    }
+
+    /**
+     * Public identifiers only use 7-bit ascii range.
+     */
+    private final static int VALID_PUBID_CHAR_COUNT = 0x80;
+    private final static byte[] sPubidValidity = new byte[VALID_PUBID_CHAR_COUNT];
+//    private final static byte PUBID_CHAR_INVALID_B = (byte) 0;
+    private final static byte PUBID_CHAR_VALID_B = (byte) 1;
+    static {
+        for (int i = 0, last = ('z' - 'a'); i <= last; ++i) {
+            sPubidValidity['A' + i] = PUBID_CHAR_VALID_B;
+            sPubidValidity['a' + i] = PUBID_CHAR_VALID_B;
+        }
+        for (int i = '0'; i <= '9'; ++i) {
+            sPubidValidity[i] = PUBID_CHAR_VALID_B;
+        }
+
+        // 3 main white space types are valid
+        sPubidValidity[0x0A] = PUBID_CHAR_VALID_B;
+        sPubidValidity[0x0D] = PUBID_CHAR_VALID_B;
+        sPubidValidity[0x20] = PUBID_CHAR_VALID_B;
+
+        // And many of punctuation/separator ascii chars too:
+        sPubidValidity['-'] = PUBID_CHAR_VALID_B;
+        sPubidValidity['\''] = PUBID_CHAR_VALID_B;
+        sPubidValidity['('] = PUBID_CHAR_VALID_B;
+        sPubidValidity[')'] = PUBID_CHAR_VALID_B;
+        sPubidValidity['+'] = PUBID_CHAR_VALID_B;
+        sPubidValidity[','] = PUBID_CHAR_VALID_B;
+        sPubidValidity['.'] = PUBID_CHAR_VALID_B;
+        sPubidValidity['/'] = PUBID_CHAR_VALID_B;
+        sPubidValidity[':'] = PUBID_CHAR_VALID_B;
+        sPubidValidity['='] = PUBID_CHAR_VALID_B;
+        sPubidValidity['?'] = PUBID_CHAR_VALID_B;
+        sPubidValidity[';'] = PUBID_CHAR_VALID_B;
+        sPubidValidity['!'] = PUBID_CHAR_VALID_B;
+        sPubidValidity['*'] = PUBID_CHAR_VALID_B;
+        sPubidValidity['#'] = PUBID_CHAR_VALID_B;
+        sPubidValidity['@'] = PUBID_CHAR_VALID_B;
+        sPubidValidity['$'] = PUBID_CHAR_VALID_B;
+        sPubidValidity['_'] = PUBID_CHAR_VALID_B;
+        sPubidValidity['%'] = PUBID_CHAR_VALID_B;
+    }
+
+    /*
+    ///////////////////////////////////////////////////////////
+    // Basic configuration
+    ///////////////////////////////////////////////////////////
+     */
+
+    /**
+     * Copy of the configuration object passed by the factory.
+     * Contains immutable settings for this reader (or in case
+     * of DTD parsers, reader that uses it)
+     */
+    protected final ReaderConfig mConfig;
+
+    // // // Various extracted settings:
+
+    /**
+     * If true, Reader is namespace aware, and should do basic checks
+     * (usually enforcing limitations on having colons in names)
+     */
+    protected final boolean mCfgNsEnabled;
+
+    // Extracted standard on/off settings:
+
+    /**
+     * note: left non-final on purpose: sub-class may need to modify
+     * the default value after construction.
+     */
+    protected boolean mCfgReplaceEntities;
+
+    /*
+    ///////////////////////////////////////////////////////////
+    // Symbol handling, if applicable
+    ///////////////////////////////////////////////////////////
+     */
+
+    final SymbolTable mSymbols;
+
+    /**
+     * Local full name for the event, if it has one (note: element events
+     * do NOT use this variable; those names are stored in element stack):
+     * target for processing instructions.
+     *<p>
+     * Currently used for proc. instr. target, and entity name (at least
+     * when current entity reference is null).
+     *<p>
+     * Note: this variable is generally not cleared, since it comes from
+     * a symbol table, ie. this won't be the only reference.
+     */
+    protected String mCurrName;
+
+    /*
+    ///////////////////////////////////////////////////////////
+    // Input handling
+    ///////////////////////////////////////////////////////////
+     */
+
+    /**
+     * Currently active input source; contains link to parent (nesting) input
+     * sources, if any.
+     */
+    protected WstxInputSource mInput;
+
+    /**
+     * Top-most input source this reader can use; due to input source
+     * chaining, this is not necessarily the root of all input; for example,
+     * external DTD subset reader's root input still has original document
+     * input as its parent.
+     */
+    protected final WstxInputSource mRootInput;
+
+    /**
+     * Custom resolver used to handle external entities that are to be expanded
+     * by this reader (external param/general entity expander)
+     */
+    XMLResolver mEntityResolver = null;
+
+    /**
+     * This is the current depth of the input stack (same as what input
+     * element stack would return as its depth).
+     * It is used to enforce input scope constraints for nesting of
+     * elements (for xml reader) and dtd declaration (for dtd reader)
+     * with regards to input block (entity expansion) boundaries.
+     *<p>
+     * Basically this value is compared to {@link #mInputTopDepth}, which
+     * indicates what was the depth at the point where the currently active
+     * input scope/block was started.
+     */
+    protected int mCurrDepth = 0;
+
+    protected int mInputTopDepth = 0;
+
+    /**
+     * Flag that indicates whether linefeeds in the input data are to
+     * be normalized or not.
+     * Xml specs mandate that the line feeds are only normalized
+     * when they are from the external entities (main doc, external
+     * general/parsed entities), so normalization has to be
+     * suppressed when expanding internal general/parsed entities.
+     */
+    protected boolean mNormalizeLFs;
+
+    /*
+    ///////////////////////////////////////////////////////////
+    // Buffer(s) for local name(s) and text content
+    ///////////////////////////////////////////////////////////
+     */
+
+    /**
+     * Temporary buffer used if local name can not be just directly
+     * constructed from input buffer (name is on a boundary or such).
+     */
+    protected char[] mNameBuffer = null;
+
+    /*
+    ///////////////////////////////////////////////////////////
+    // Information about starting location of event
+    // Reader is pointing to; updated on-demand
+    ///////////////////////////////////////////////////////////
+     */
+
+    // // // Location info at point when current token was started
+
+    /**
+     * Total number of characters read before start of current token.
+     * For big (gigabyte-sized) sizes are possible, needs to be long,
+     * unlike pointers and sizes related to in-memory buffers.
+     */
+    protected long mTokenInputTotal = 0; 
+
+    /**
+     * Input row on which current token starts, 1-based
+     */
+    protected int mTokenInputRow = 1;
+
+    /**
+     * Column on input row that current token starts; 0-based (although
+     * in the end it'll be converted to 1-based)
+     */
+    protected int mTokenInputCol = 0;
+
+    /*
+    ///////////////////////////////////////////////////////////
+    // XML document information (from doc decl if one
+    // was found) common to all entities (main xml
+    // document, external DTD subset)
+    ///////////////////////////////////////////////////////////
+     */
+
+    /**
+     * Input stream encoding, if known (passed in, or determined by
+     * auto-detection); null if not.
+     */
+    String mDocInputEncoding = null;
+
+    /**
+     * Character encoding from xml declaration, if any; null if no
+     * declaration, or it didn't specify encoding.
+     */
+    String mDocXmlEncoding = null;
+
+    /**
+     * XML version as declared by the document; one of constants
+     * from {@link XmlConsts} (like {@link XmlConsts#XML_V_10}).
+     */
+    protected int mDocXmlVersion = XmlConsts.XML_V_UNKNOWN;
+    
+    /**
+     * Cache of internal character entities;
+     */
+    protected Map mCachedEntities;
+    
+    /**
+     * Flag for whether or not character references should be treated as entities
+     */
+    protected boolean mCfgTreatCharRefsAsEntities;
+    
+    /**
+     * Entity reference stream currently points to.
+     */
+    protected EntityDecl mCurrEntity;
+
+    /*
+    ///////////////////////////////////////////////////////////
+    // Life-cycle
+    ///////////////////////////////////////////////////////////
+     */
+
+    /**
+     * Constructor used when creating a complete new (main-level) reader that
+     * does not share its input buffers or state with another reader.
+     */
+    protected StreamScanner(WstxInputSource input, ReaderConfig cfg,
+                            XMLResolver res)
+    {
+        super();
+        mInput = input;
+        // 17-Jun-2004, TSa: Need to know root-level input source
+        mRootInput = input;
+
+        mConfig = cfg;
+        mSymbols = cfg.getSymbols();
+        int cf = cfg.getConfigFlags();
+        mCfgNsEnabled = (cf & CFG_NAMESPACE_AWARE) != 0;
+        mCfgReplaceEntities = (cf & CFG_REPLACE_ENTITY_REFS) != 0;
+
+        mNormalizeLFs = mConfig.willNormalizeLFs();
+        mInputBuffer = null;
+        mInputPtr = mInputEnd = 0;
+        mEntityResolver = res;
+        
+        mCfgTreatCharRefsAsEntities = mConfig.willTreatCharRefsAsEnts();
+        mCachedEntities = mCfgTreatCharRefsAsEntities ? new HashMap() : Collections.EMPTY_MAP;
+    }
+
+    /*
+    ///////////////////////////////////////////////////////////
+    // Package API
+    ///////////////////////////////////////////////////////////
+     */
+
+    /**
+     * Method that returns location of the last character returned by this
+     * reader; that is, location "one less" than the currently pointed to
+     * location.
+     */
+    protected WstxInputLocation getLastCharLocation()
+    {
+        return mInput.getLocation(mCurrInputProcessed + mInputPtr - 1,
+                                  mCurrInputRow,
+                                  mInputPtr - mCurrInputRowStart);
+    }
+
+    protected URL getSource() {
+        return mInput.getSource();
+    }
+
+    protected String getSystemId() {
+        return mInput.getSystemId();
+    }
+
+    /*
+    ///////////////////////////////////////////////////////////
+    // Partial LocationInfo implementation (not implemented
+    // by this base class, but is by some sub-classes)
+    ///////////////////////////////////////////////////////////
+     */
+
+    /**
+     * Returns location of last properly parsed token; as per StAX specs,
+     * apparently needs to be the end of current event, which is the same
+     * as the start of the following event (or EOF if that's next).
+     */
+    public abstract Location getLocation();
+
+    public XMLStreamLocation2 getStartLocation()
+    {
+        // note: +1 is used as columns are 1-based...
+        return mInput.getLocation(mTokenInputTotal, mTokenInputRow,
+                                  mTokenInputCol + 1);
+    }
+
+    public XMLStreamLocation2 getCurrentLocation()
+    {
+        return mInput.getLocation(mCurrInputProcessed + mInputPtr,
+                                  mCurrInputRow,
+                                  mInputPtr - mCurrInputRowStart + 1);
+    }
+
+    /*
+    ///////////////////////////////////////////////////////////
+    // InputProblemReporter implementation
+    ///////////////////////////////////////////////////////////
+     */
+
+    public WstxException throwWfcException(String msg, boolean deferErrors)
+        throws WstxException
+    {
+        WstxException ex = constructWfcException(msg);
+        if (!deferErrors) {
+            throw ex;
+        }
+        return ex;
+    }
+
+    public void throwParseError(String msg) throws XMLStreamException
+    {
+        throwParseError(msg, null, null);
+    }
+
+    /**
+     * Throws generic parse error with specified message and current parsing
+     * location.
+     *<p>
+     * Note: public access only because core code in other packages needs
+     * to access it.
+     */
+    public void throwParseError(String format, Object arg, Object arg2)
+        throws XMLStreamException
+    {
+        String msg = (arg != null || arg2 != null) ?
+            MessageFormat.format(format, new Object[] { arg, arg2 }) : format;
+        throw constructWfcException(msg);
+    }
+
+    public void reportProblem(String probType, String format, Object arg, Object arg2)
+        throws XMLStreamException
+    {
+        XMLReporter rep = mConfig.getXMLReporter();
+        if (rep != null) {
+            _reportProblem(rep, probType,
+                            MessageFormat.format(format, new Object[] { arg, arg2 }), null);
+        }
+    }
+
+    public void reportProblem(Location loc, String probType,
+                              String format, Object arg, Object arg2)
+        throws XMLStreamException
+    {
+        XMLReporter rep = mConfig.getXMLReporter();
+        if (rep != null) {
+            String msg = (arg != null || arg2 != null) ?
+                MessageFormat.format(format, new Object[] { arg, arg2 }) : format;
+            _reportProblem(rep, probType, msg, loc);
+        }
+    }
+
+    protected void _reportProblem(XMLReporter rep, String probType, String msg, Location loc)
+        throws XMLStreamException
+    {
+        if (loc == null) {
+            loc = getLastCharLocation();
+        }
+        _reportProblem(rep, new XMLValidationProblem(loc, msg, XMLValidationProblem.SEVERITY_ERROR, probType));
+    }
+
+    protected void _reportProblem(XMLReporter rep, XMLValidationProblem prob)
+        throws XMLStreamException
+    {
+        if (rep != null) {
+            Location loc = prob.getLocation();
+            if (loc == null) {
+                loc = getLastCharLocation();
+                prob.setLocation(loc);
+            }
+            // Backwards-compatibility fix: add non-null type, if missing:
+            if (prob.getType() == null) {
+                prob.setType(ErrorConsts.WT_VALIDATION);
+            }
+            // [WSTX-154]: was catching and dropping thrown exception: shouldn't.
+            // [WTSX-157]: need to support XMLReporter2
+            if (rep instanceof XMLReporter2) {
+                ((XMLReporter2) rep).report(prob);
+            } else {
+                rep.report(prob.getMessage(), prob.getType(), prob, loc);
+            }
+        }
+    }
+
+    /**
+     *<p>
+     * Note: this is the base implementation used for implementing
+     * <code>ValidationContext</code>
+     */
+    public void reportValidationProblem(XMLValidationProblem prob)
+        throws XMLStreamException
+    {
+        // !!! TBI: Fail-fast vs. deferred modes?
+        /* For now let's implement basic functionality: warnings get
+         * reported via XMLReporter, errors and fatal errors result in
+         * immediate exceptions.
+         */
+        /* 27-May-2008, TSa: [WSTX-153] Above is incorrect: as per Stax
+         *   javadocs for XMLReporter, both warnings and non-fatal errors
+         *   (which includes all validation errors) should be reported via
+         *   XMLReporter interface, and only fatals should cause an
+         *   immediate stream exception (by-passing reporter)
+         */
+        if (prob.getSeverity() > XMLValidationProblem.SEVERITY_ERROR) {
+            throw WstxValidationException.create(prob);
+        }
+        XMLReporter rep = mConfig.getXMLReporter();
+        if (rep != null) {
+            _reportProblem(rep, prob);
+        } else {
+            /* If no reporter, regular non-fatal errors are to be reported
+             * as exceptions as well, for backwards compatibility
+             */
+            if (prob.getSeverity() >= XMLValidationProblem.SEVERITY_ERROR) {
+                throw WstxValidationException.create(prob);
+            }
+        }
+    }
+
+    public void reportValidationProblem(String msg, int severity)
+        throws XMLStreamException
+    {
+        reportValidationProblem(new XMLValidationProblem(getLastCharLocation(),
+                                                         msg, severity));
+    }
+
+    public void reportValidationProblem(String msg)
+        throws XMLStreamException
+    {
+        reportValidationProblem(new XMLValidationProblem(getLastCharLocation(),
+                                                         msg,
+                                                         XMLValidationProblem.SEVERITY_ERROR));
+    }
+
+    public void reportValidationProblem(Location loc, String msg)
+        throws XMLStreamException
+    {
+        reportValidationProblem(new XMLValidationProblem(loc, msg));
+    }
+
+    public void reportValidationProblem(String format, Object arg, Object arg2)
+        throws XMLStreamException
+    {
+        reportValidationProblem(MessageFormat.format(format, new Object[] { arg, arg2 }));
+    }
+
+    /*
+    ///////////////////////////////////////////////////////////
+    // Other error reporting methods
+    ///////////////////////////////////////////////////////////
+     */
+
+    protected WstxException constructWfcException(String msg)
+    {
+        return new WstxParsingException(msg, getLastCharLocation());
+    }
+
+    /**
+     * Construct and return a {@link XMLStreamException} to throw
+     * as a result of a failed Typed Access operation (but one not
+     * caused by a Well-Formedness Constraint or Validation Constraint
+     * problem)
+     */
+    /*
+    protected WstxException _constructTypeException(String msg)
+    {
+        // Hmmh. Should there be a distinct sub-type?
+        return new WstxParsingException(msg, getLastCharLocation());
+    }
+    */
+
+    protected WstxException constructFromIOE(IOException ioe)
+    {
+        return new WstxIOException(ioe);
+    }
+
+    protected WstxException constructNullCharException()
+    {
+        return new WstxUnexpectedCharException("Illegal character (NULL, unicode 0) encountered: not valid in any content",
+                getLastCharLocation(), CHAR_NULL);
+    }
+
+    protected void throwUnexpectedChar(int i, String msg)
+        throws WstxException
+    {
+        char c = (char) i;
+        String excMsg = "Unexpected character "+getCharDesc(c)+msg;
+        throw new WstxUnexpectedCharException(excMsg, getLastCharLocation(), c);
+    }
+
+    protected void throwNullChar()
+        throws WstxException
+    {
+        throw constructNullCharException();
+    }
+
+    protected void throwInvalidSpace(int i)
+        throws WstxException
+    {
+        throwInvalidSpace(i, false);
+    }
+
+    protected WstxException throwInvalidSpace(int i, boolean deferErrors)
+        throws WstxException
+    {
+        char c = (char) i;
+        WstxException ex;
+        if (c == CHAR_NULL) {
+            ex = constructNullCharException();
+        } else {
+            String msg = "Illegal character ("+getCharDesc(c)+")";
+            if (mXml11) {
+                msg += " [note: in XML 1.1, it could be included via entity expansion]";
+            }
+            ex = new WstxUnexpectedCharException(msg, getLastCharLocation(), c);
+        }
+        if (!deferErrors) {
+            throw ex;
+        }
+        return ex;
+    }
+
+    protected void throwUnexpectedEOF(String msg)
+        throws WstxException
+    {
+        throw new WstxEOFException("Unexpected EOF"
+                                   +(msg == null ? "" : msg),
+                                   getLastCharLocation());
+    }
+
+    /**
+     * Similar to {@link #throwUnexpectedEOF}, but only indicates ending
+     * of an input block. Used when reading a token that can not span
+     * input block boundaries (ie. can not continue past end of an
+     * entity expansion).
+     */
+    protected void throwUnexpectedEOB(String msg)
+        throws WstxException
+    {
+        throw new WstxEOFException("Unexpected end of input block"
+                                   +(msg == null ? "" : msg),
+                                   getLastCharLocation());
+    }
+
+    protected void throwFromIOE(IOException ioe)
+        throws WstxException
+    {
+        throw new WstxIOException(ioe);
+    }
+
+    protected void throwFromStrE(XMLStreamException strex)
+        throws WstxException
+    {
+        if (strex instanceof WstxException) {
+            throw (WstxException) strex;
+        }
+        WstxException newEx = new WstxException(strex);
+        ExceptionUtil.setInitCause(newEx, strex);
+        throw newEx;
+    }
+
+    /**
+     * Method called to report an error, when caller's signature only
+     * allows runtime exceptions to be thrown.
+     */
+    protected void throwLazyError(Exception e)
+    {
+        if (e instanceof XMLStreamException) {
+            WstxLazyException.throwLazily((XMLStreamException) e);
+        }
+        ExceptionUtil.throwRuntimeException(e);
+    }
+
+    protected String tokenTypeDesc(int type)
+    {
+        return ErrorConsts.tokenTypeDesc(type);
+    }
+
+    /*
+    ///////////////////////////////////////////////////////////
+    // Input buffer handling
+    ///////////////////////////////////////////////////////////
+     */
+
+    /**
+     * Returns current input source this source uses.
+     *<p>
+     * Note: public only because some implementations are on different
+     * package.
+     */
+    public final WstxInputSource getCurrentInput() {
+        return mInput;
+    }
+
+    protected final int inputInBuffer() {
+        return mInputEnd - mInputPtr;
+    }
+
+    protected final int getNext()
+        throws XMLStreamException
+    {
+        if (mInputPtr >= mInputEnd) {
+            if (!loadMore()) {
+                return -1;
+            }
+        }
+        return (int) mInputBuffer[mInputPtr++];
+    }
+
+    /**
+     * Similar to {@link #getNext}, but does not advance pointer
+     * in input buffer.
+     *<p>
+     * Note: this method only peeks within current input source;
+     * it does not close it and check nested input source (if any).
+     * This is necessary when checking keywords, since they can never
+     * cross input block boundary.
+     */
+    protected final int peekNext()
+        throws XMLStreamException
+    {
+        if (mInputPtr >= mInputEnd) {
+            if (!loadMoreFromCurrent()) {
+                return -1;
+            }
+        }
+        return (int) mInputBuffer[mInputPtr];
+    }
+
+    protected final char getNextChar(String errorMsg)
+        throws XMLStreamException
+    {
+        if (mInputPtr >= mInputEnd) {
+            loadMore(errorMsg);
+        }
+        return mInputBuffer[mInputPtr++];
+    }
+
+    /**
+     * Similar to {@link #getNextChar}, but will not read more characters
+     * from parent input source(s) if the current input source doesn't
+     * have more content. This is often needed to prevent "runaway" content,
+     * such as comments that start in an entity but do not have matching
+     * close marker inside entity; XML specification specifically states
+     * such markup is not legal.
+     */
+    protected final char getNextCharFromCurrent(String errorMsg)
+        throws XMLStreamException
+    {
+        if (mInputPtr >= mInputEnd) {
+            loadMoreFromCurrent(errorMsg);
+        }
+        return mInputBuffer[mInputPtr++];
+    }
+
+    /**
+     * Method that will skip through zero or more white space characters,
+     * and return either the character following white space, or -1 to
+     * indicate EOF (end of the outermost input source)/
+     */
+    protected final int getNextAfterWS()
+        throws XMLStreamException
+    {
+        if (mInputPtr >= mInputEnd) {
+            if (!loadMore()) {
+                return -1;
+            }
+        }
+        char c = mInputBuffer[mInputPtr++];
+        while (c <= CHAR_SPACE) {
+            // Linefeed?
+            if (c == '\n' || c == '\r') {
+                skipCRLF(c);
+            } else if (c != CHAR_SPACE && c != '\t') {
+                throwInvalidSpace(c);
+            }
+            // Still a white space?
+            if (mInputPtr >= mInputEnd) {
+                if (!loadMore()) {
+                    return -1;
+                }
+            }
+            c = mInputBuffer[mInputPtr++];
+        }
+        return (int) c;
+    }
+
+    protected final char getNextCharAfterWS(String errorMsg)
+        throws XMLStreamException
+    {
+        if (mInputPtr >= mInputEnd) {
+            loadMore(errorMsg);
+        }
+
+        char c = mInputBuffer[mInputPtr++];
+        while (c <= CHAR_SPACE) {
+            // Linefeed?
+            if (c == '\n' || c == '\r') {
+                skipCRLF(c);
+            } else if (c != CHAR_SPACE && c != '\t') {
+                throwInvalidSpace(c);
+            }
+
+            // Still a white space?
+            if (mInputPtr >= mInputEnd) {
+                loadMore(errorMsg);
+            }
+            c = mInputBuffer[mInputPtr++];
+        }
+        return c;
+    }
+
+    protected final char getNextInCurrAfterWS(String errorMsg)
+        throws XMLStreamException
+    {
+        return getNextInCurrAfterWS(errorMsg, getNextCharFromCurrent(errorMsg));
+    }
+
+    protected final char getNextInCurrAfterWS(String errorMsg, char c)
+        throws XMLStreamException
+    {
+        while (c <= CHAR_SPACE) {
+            // Linefeed?
+            if (c == '\n' || c == '\r') {
+                skipCRLF(c);
+            } else if (c != CHAR_SPACE && c != '\t') {
+                throwInvalidSpace(c);
+            }
+
+            // Still a white space?
+            if (mInputPtr >= mInputEnd) {
+                loadMoreFromCurrent(errorMsg);
+            }
+            c = mInputBuffer[mInputPtr++];
+        }
+        return c;
+    }
+
+    /**
+     * Method called when a CR has been spotted in input; checks if next
+     * char is LF, and if so, skips it. Note that next character has to
+     * come from the current input source, to qualify; it can never come
+     * from another (nested) input source.
+     *
+     * @return True, if passed in char is '\r' and next one is '\n'.
+     */
+    protected final boolean skipCRLF(char c) 
+        throws XMLStreamException
+    {
+        boolean result;
+
+        if (c == '\r' && peekNext() == '\n') {
+            ++mInputPtr;
+            result = true;
+        } else {
+            result = false;
+        }
+        ++mCurrInputRow;
+        mCurrInputRowStart = mInputPtr;
+        return result;
+    }
+
+    protected final void markLF() {
+        ++mCurrInputRow;
+        mCurrInputRowStart = mInputPtr;
+    }
+
+    protected final void markLF(int inputPtr) {
+        ++mCurrInputRow;
+        mCurrInputRowStart = inputPtr;
+    }
+
+    /**
+     * Method to push back last character read; can only be called once,
+     * that is, no more than one char can be guaranteed to be succesfully
+     * returned.
+     */
+    protected final void pushback() { --mInputPtr; }
+
+    /*
+    ///////////////////////////////////////////////////////////
+    // Sub-class overridable input handling methods
+    ///////////////////////////////////////////////////////////
+     */
+
+    /**
+     * Method called when an entity has been expanded (new input source
+     * has been created). Needs to initialize location information and change
+     * active input source.
+     *
+     * @param entityId Name of the entity being expanded
+     */
+    protected void initInputSource(WstxInputSource newInput, boolean isExt,
+                                   String entityId)
+        throws XMLStreamException
+    {
+        mInput = newInput;
+        // Let's make sure new input will be read next time input is needed:
+        mInputPtr = 0;
+        mInputEnd = 0;
+        /* Plus, reset the input location so that'll be accurate for
+         * error reporting etc.
+         */
+        mInputTopDepth = mCurrDepth;
+        mInput.initInputLocation(this, mCurrDepth);
+
+        /* 21-Feb-2006, TSa: Linefeeds are NOT normalized when expanding
+         *   internal entities (XML, 2.11)
+         */
+        if (isExt) {
+            mNormalizeLFs = true;
+        } else {
+            mNormalizeLFs = false;
+        }
+    }
+
+    /**
+     * Method that will try to read one or more characters from currently
+     * open input sources; closing input sources if necessary.
+     *
+     * @return true if reading succeeded (or may succeed), false if
+     *   we reached EOF.
+     */
+    protected boolean loadMore()
+        throws XMLStreamException
+    {
+        WstxInputSource input = mInput;
+        do {
+            /* Need to make sure offsets are properly updated for error
+             * reporting purposes, and do this now while previous amounts
+             * are still known.
+             */
+            mCurrInputProcessed += mInputEnd;
+            mCurrInputRowStart -= mInputEnd;
+            int count;
+            try {
+                count = input.readInto(this);
+                if (count > 0) {
+                    return true;
+                }
+                input.close();
+            } catch (IOException ioe) {
+                throw constructFromIOE(ioe);
+            }
+            if (input == mRootInput) {
+                /* Note: no need to check entity/input nesting in this
+                 * particular case, since it will be handled by higher level
+                 * parsing code (results in an unexpected EOF)
+                 */
+                return false;
+            }
+            WstxInputSource parent = input.getParent();
+            if (parent == null) { // sanity check!
+                throwNullParent(input);
+            }
+            /* 13-Feb-2006, TSa: Ok, do we violate a proper nesting constraints
+             *   with this input block closure?
+             */
+            if (mCurrDepth != input.getScopeId()) {
+                handleIncompleteEntityProblem(input);
+            }
+
+            mInput = input = parent;
+            input.restoreContext(this);
+            mInputTopDepth = input.getScopeId();
+            /* 21-Feb-2006, TSa: Since linefeed normalization needs to be
+             *   suppressed for internal entity expansion, we may need to
+             *   change the state...
+             */
+            if (!mNormalizeLFs) {
+                mNormalizeLFs = !input.fromInternalEntity();
+            }
+            // Maybe there are leftovers from that input in buffer now?
+        } while (mInputPtr >= mInputEnd);
+
+        return true;
+    }
+
+    protected final boolean loadMore(String errorMsg)
+        throws XMLStreamException
+    {
+        if (!loadMore()) {
+            throwUnexpectedEOF(errorMsg);
+        }
+        return true;
+    }
+
+    protected boolean loadMoreFromCurrent()
+        throws XMLStreamException
+    {
+        // Need to update offsets properly
+        mCurrInputProcessed += mInputEnd;
+        mCurrInputRowStart -= mInputEnd;
+        try {
+            int count = mInput.readInto(this);
+            return (count > 0);
+        } catch (IOException ie) {
+            throw constructFromIOE(ie);
+        }
+    }
+
+    protected final boolean loadMoreFromCurrent(String errorMsg)
+        throws XMLStreamException
+    {
+        if (!loadMoreFromCurrent()) {
+            throwUnexpectedEOB(errorMsg);
+        }
+        return true;
+    }
+
+    /**
+     * Method called to make sure current main-level input buffer has at
+     * least specified number of characters available consequtively,
+     * without having to call {@link #loadMore}. It can only be called
+     * when input comes from main-level buffer; further, call can shift
+     * content in input buffer, so caller has to flush any data still
+     * pending. In short, caller has to know exactly what it's doing. :-)
+     *<p>
+     * Note: method does not check for any other input sources than the
+     * current one -- if current source can not fulfill the request, a
+     * failure is indicated.
+     *
+     * @return true if there's now enough data; false if not (EOF)
+     */
+    protected boolean ensureInput(int minAmount)
+        throws XMLStreamException
+    {
+        int currAmount = mInputEnd - mInputPtr;
+        if (currAmount >= minAmount) {
+            return true;
+        }
+        try {
+            return mInput.readMore(this, minAmount);
+        } catch (IOException ie) {
+            throw constructFromIOE(ie);
+        }
+    }
+
+    protected void closeAllInput(boolean force)
+        throws XMLStreamException
+    {
+        WstxInputSource input = mInput;
+        while (true) {
+            try {
+                if (force) {
+                    input.closeCompletely();
+                } else {
+                    input.close();
+                }
+            } catch (IOException ie) {
+                throw constructFromIOE(ie);
+            }
+            if (input == mRootInput) {
+                break;
+            }
+            WstxInputSource parent = input.getParent();
+            if (parent == null) { // sanity check!
+                throwNullParent(input);
+            }
+            mInput = input = parent;
+        }
+    }
+
+    protected void throwNullParent(WstxInputSource curr)
+    {
+        throw new IllegalStateException(ErrorConsts.ERR_INTERNAL);
+        //throw new IllegalStateException("Internal error: null parent for input source '"+curr+"'; should never occur (should have stopped at root input '"+mRootInput+"').");
+    }
+
+    /*
+    ///////////////////////////////////////////////////////////
+    // Entity resolution
+    ///////////////////////////////////////////////////////////
+     */
+
+    /**
+     * Method that tries to resolve a character entity, or (if caller so
+     * specifies), a pre-defined internal entity (lt, gt, amp, apos, quot).
+     * It will succeed iff:
+     * <ol>
+     *  <li>Entity in question is a simple character entity (either one of
+     *    5 pre-defined ones, or using decimal/hex notation), AND
+     *   <li>
+     *  <li>Entity fits completely inside current input buffer.
+     *   <li>
+     * </ol>
+     * If so, character value of entity is returned. Character 0 is returned
+     * otherwise; if so, caller needs to do full resolution.
+     *<p>
+     * Note: On entry we are guaranteed there are at least 3 more characters
+     * in this buffer; otherwise we shouldn't be called.
+     *
+     * @param checkStd If true, will check pre-defined internal entities
+     *   (gt, lt, amp, apos, quot); if false, will only check actual
+     *   character entities.
+     *
+     * @return (Valid) character value, if entity is a character reference,
+     *   and could be resolved from current input buffer (does not span
+     *   buffer boundary); null char (code 0) if not (either non-char
+     *   entity, or spans input buffer boundary).
+     */
+    protected int resolveSimpleEntity(boolean checkStd)
+        throws XMLStreamException
+    {
+        char[] buf = mInputBuffer;
+        int ptr = mInputPtr;
+        char c = buf[ptr++];
+
+        // Numeric reference?
+        if (c == '#') {
+            c = buf[ptr++];
+            int value = 0;
+            int inputLen = mInputEnd;
+            if (c == 'x') { // hex
+                while (ptr < inputLen) {
+                    c = buf[ptr++];
+                    if (c == ';') {
+                        break;
+                    }
+                    value = value << 4;
+                    if (c <= '9' && c >= '0') {
+                        value += (c - '0');
+                    } else if (c >= 'a' && c <= 'f') {
+                        value += (10 + (c - 'a'));
+                    } else if (c >= 'A' && c <= 'F') {
+                        value += (10 + (c - 'A'));
+                    } else {
+                        mInputPtr = ptr; // so error points to correct char
+                        throwUnexpectedChar(c, "; expected a hex digit (0-9a-fA-F).");
+                    }
+                    /* Need to check for overflow; easiest to do right as
+                     * it happens...
+                     */
+                    if (value > MAX_UNICODE_CHAR) {
+                        reportUnicodeOverflow();
+                    }
+                }
+            } else { // numeric (decimal)
+                while (c != ';') {
+                    if (c <= '9' && c >= '0') {
+                        value = (value * 10) + (c - '0');
+                        // Overflow?
+                        if (value > MAX_UNICODE_CHAR) {
+                            reportUnicodeOverflow();
+                        }
+                    } else {
+                        mInputPtr = ptr; // so error points to correct char
+                        throwUnexpectedChar(c, "; expected a decimal number.");
+                    }
+                    if (ptr >= inputLen) {
+                        break;
+                    }
+                    c = buf[ptr++];
+                }
+            }
+            /* We get here either if we got it all, OR if we ran out of
+             * input in current buffer.
+             */
+            if (c == ';') { // got the full thing
+                mInputPtr = ptr;
+                validateChar(value);
+                return value;
+            }
+
+            /* If we ran out of input, need to just fall back, gets
+             * resolved via 'full' resolution mechanism.
+             */
+        } else if (checkStd) {
+            /* Caller may not want to resolve these quite yet...
+             * (when it wants separate events for non-char entities)
+             */
+            if (c == 'a') { // amp or apos?
+                c = buf[ptr++];
+                
+                if (c == 'm') { // amp?
+                    if (buf[ptr++] == 'p') {
+                        if (ptr < mInputEnd && buf[ptr++] == ';') {
+                            mInputPtr = ptr;
+                            return '&';
+                        }
+                    }
+                } else if (c == 'p') { // apos?
+                    if (buf[ptr++] == 'o') {
+                        int len = mInputEnd;
+                        if (ptr < len && buf[ptr++] == 's') {
+                            if (ptr < len && buf[ptr++] == ';') {
+                                mInputPtr = ptr;
+                                return '\'';
+                            }
+                        }
+                    }
+                }
+            } else if (c == 'g') { // gt?
+                if (buf[ptr++] == 't' && buf[ptr++] == ';') {
+                    mInputPtr = ptr;
+                    return '>';
+                }
+            } else if (c == 'l') { // lt?
+                if (buf[ptr++] == 't' && buf[ptr++] == ';') {
+                    mInputPtr = ptr;
+                    return '<';
+                }
+            } else if (c == 'q') { // quot?
+                if (buf[ptr++] == 'u' && buf[ptr++] == 'o') {
+                    int len = mInputEnd;
+                    if (ptr < len && buf[ptr++] == 't') {
+                        if (ptr < len && buf[ptr++] == ';') {
+                            mInputPtr = ptr;
+                            return '"';
+                        }
+                    }
+                }
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * Method called to resolve character entities, and only character
+     * entities (except that pre-defined char entities -- amp, apos, lt,
+     * gt, quote -- MAY be "char entities" in this sense, depending on
+     * arguments).
+     * Otherwise it is to return the null char; if so,
+     * the input pointer will point to the same point as when method
+     * entered (char after ampersand), plus the ampersand itself is
+     * guaranteed to be in the input buffer (so caller can just push it
+     * back if necessary).
+     *<p>
+     * Most often this method is called when reader is not to expand
+     * non-char entities automatically, but to return them as separate
+     * events.
+     *<p>
+     * Main complication here is that we need to do 5-char lookahead. This
+     * is problematic if chars are on input buffer boundary. This is ok
+     * for the root level input buffer, but not for some nested buffers.
+     * However, according to XML specs, such split entities are actually
+     * illegal... so we can throw an exception in those cases.
+     *
+     * @param checkStd If true, will check pre-defined internal entities
+     *   (gt, lt, amp, apos, quot) as character entities; if false, will only
+     *   check actual 'real' character entities.
+     *
+     * @return (Valid) character value, if entity is a character reference,
+     *   and could be resolved from current input buffer (does not span
+     *   buffer boundary); null char (code 0) if not (either non-char
+     *   entity, or spans input buffer boundary).
+     */
+    protected int resolveCharOnlyEntity(boolean checkStd)
+        throws XMLStreamException
+    {
+        //int avail = inputInBuffer();
+        int avail = mInputEnd - mInputPtr;
+        if (avail < 6) {
+            // split entity, or buffer boundary
+            /* Don't want to lose leading '&' (in case we can not expand
+             * the entity), so let's push it back first
+             */
+            --mInputPtr;
+            /* Shortest valid reference would be 3 chars ('&a;'); which
+             * would only be legal from an expanded entity...
+             */
+            if (!ensureInput(6)) {
+                avail = inputInBuffer();
+                if (avail < 3) {
+                    throwUnexpectedEOF(SUFFIX_IN_ENTITY_REF);
+                }
+            } else {
+                avail = 6;
+            }
+            // ... and now we can move pointer back as well:
+            ++mInputPtr;
+        }
+
+        /* Ok, now we have one more character to check, and that's enough
+         * to determine type decisively.
+         */
+        char c = mInputBuffer[mInputPtr];
+
+        // A char reference?
+        if (c == '#') { // yup
+            ++mInputPtr;
+            return resolveCharEnt(null);
+        }
+
+        // nope... except may be a pre-def?
+        if (checkStd) {
+            if (c == 'a') {
+                char d = mInputBuffer[mInputPtr+1];
+                if (d == 'm') {
+                    if (avail >= 4
+                        && mInputBuffer[mInputPtr+2] == 'p'
+                        && mInputBuffer[mInputPtr+3] == ';') {
+                        mInputPtr += 4;
+                        return '&';
+                    }
+                } else if (d == 'p') {
+                    if (avail >= 5
+                        && mInputBuffer[mInputPtr+2] == 'o'
+                        && mInputBuffer[mInputPtr+3] == 's'
+                        && mInputBuffer[mInputPtr+4] == ';') {
+                        mInputPtr += 5;
+                        return '\'';
+                    }
+                }
+            } else if (c == 'l') {
+                if (avail >= 3
+                    && mInputBuffer[mInputPtr+1] == 't'
+                    && mInputBuffer[mInputPtr+2] == ';') {
+                    mInputPtr += 3;
+                    return '<';
+                }
+            } else if (c == 'g') {
+                if (avail >= 3
+                    && mInputBuffer[mInputPtr+1] == 't'
+                    && mInputBuffer[mInputPtr+2] == ';') {
+                    mInputPtr += 3;
+                    return '>';
+                }
+            } else if (c == 'q') {
+                if (avail >= 5
+                    && mInputBuffer[mInputPtr+1] == 'u'
+                    && mInputBuffer[mInputPtr+2] == 'o'
+                    && mInputBuffer[mInputPtr+3] == 't'
+                    && mInputBuffer[mInputPtr+4] == ';') {
+                    mInputPtr += 5;
+                    return '"';
+                }
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * Reverse of {@link #resolveCharOnlyEntity}; will only resolve entity
+     * if it is NOT a character entity (or pre-defined 'generic' entity;
+     * amp, apos, lt, gt or quot). Only used in cases where entities
+     * are to be separately returned unexpanded (in non-entity-replacing
+     * mode); which means it's never called from dtd handler.
+     */
+    protected EntityDecl resolveNonCharEntity()
+        throws XMLStreamException
+    {
+        //int avail = inputInBuffer();
+        int avail = mInputEnd - mInputPtr;
+        if (avail < 6) {
+            // split entity, or buffer boundary
+            /* Don't want to lose leading '&' (in case we can not expand
+             * the entity), so let's push it back first
+             */
+            --mInputPtr;
+
+            /* Shortest valid reference would be 3 chars ('&a;'); which
+             * would only be legal from an expanded entity...
+             */
+            if (!ensureInput(6)) {
+                avail = inputInBuffer();
+                if (avail < 3) {
+                    throwUnexpectedEOF(SUFFIX_IN_ENTITY_REF);
+                }
+            } else {
+                avail = 6;
+            }
+            // ... and now we can move pointer back as well:
+            ++mInputPtr;
+        }
+
+        // We don't care about char entities:
+        char c = mInputBuffer[mInputPtr];
+        if (c == '#') {
+            return null;
+        }
+
+        /* 19-Aug-2004, TSa: Need special handling for pre-defined
+         *   entities; they are not counted as 'real' general parsed
+         *   entities, but more as character entities...
+         */
+
+        // have chars at least up to mInputPtr+4 by now
+        if (c == 'a') {
+            char d = mInputBuffer[mInputPtr+1];
+            if (d == 'm') {
+                if (avail >= 4
+                    && mInputBuffer[mInputPtr+2] == 'p'
+                    && mInputBuffer[mInputPtr+3] == ';') {
+                    // If not automatically expanding:
+                    //return sEntityAmp;
+                    // mInputPtr += 4;
+                    return null;
+                }
+            } else if (d == 'p') {
+                if (avail >= 5
+                    && mInputBuffer[mInputPtr+2] == 'o'
+                    && mInputBuffer[mInputPtr+3] == 's'
+                    && mInputBuffer[mInputPtr+4] == ';') {
+                    return null;
+                }
+            }
+        } else if (c == 'l') {
+            if (avail >= 3
+                && mInputBuffer[mInputPtr+1] == 't'
+                && mInputBuffer[mInputPtr+2] == ';') {
+                return null;
+            }
+        } else if (c == 'g') {
+            if (avail >= 3
+                && mInputBuffer[mInputPtr+1] == 't'
+                && mInputBuffer[mInputPtr+2] == ';') {
+                return null;
+            }
+        } else if (c == 'q') {
+            if (avail >= 5
+                && mInputBuffer[mInputPtr+1] == 'u'
+                && mInputBuffer[mInputPtr+2] == 'o'
+                && mInputBuffer[mInputPtr+3] == 't'
+                && mInputBuffer[mInputPtr+4] == ';') {
+                return null;
+            }
+        }
+
+        // Otherwise, let's just parse in generic way:
+        ++mInputPtr; // since we already read the first letter
+        String id = parseEntityName(c);
+        mCurrName = id;
+
+        return findEntity(id, null);
+    }
+
+    /**
+     * Method that does full resolution of an entity reference, be it
+     * character entity, internal entity or external entity, including
+     * updating of input buffers, and depending on whether result is
+     * a character entity (or one of 5 pre-defined entities), returns
+     * char in question, or null character (code 0) to indicate it had
+     * to change input source.
+     *
+     * @param allowExt If true, is allowed to expand external entities
+     *   (expanding text); if false, is not (expanding attribute value).
+     *
+     * @return Either single-character replacement (which is NOT to be
+     *    reparsed), or null char (0) to indicate expansion is done via
+     *    input source.
+     */
+    protected int fullyResolveEntity(boolean allowExt)
+        throws XMLStreamException
+    {
+        char c = getNextCharFromCurrent(SUFFIX_IN_ENTITY_REF);
+        // Do we have a (numeric) character entity reference?
+        if (c == '#') { // numeric
+            final StringBuffer originalSurface = new StringBuffer("#");
+            int ch = resolveCharEnt(originalSurface);
+            if (mCfgTreatCharRefsAsEntities) {
+                final char[] originalChars = new char[originalSurface.length()];
+                originalSurface.getChars(0, originalSurface.length(), originalChars, 0);
+                mCurrEntity = getIntEntity(ch, originalChars);
+                return 0;
+            }
+            return ch;
+        }
+
+        String id = parseEntityName(c);
+ 
+        // Perhaps we have a pre-defined char reference?
+        c = id.charAt(0);
+        /*
+         * 16-May-2004, TSa: Should custom entities (or ones defined in int/ext subset) override
+         * pre-defined settings for these?
+         */
+        char d = CHAR_NULL;
+        if (c == 'a') { // amp or apos?
+            if (id.equals("amp")) {
+                d = '&';
+            } else if (id.equals("apos")) {
+                d = '\'';
+            }
+        } else if (c == 'g') { // gt?
+            if (id.length() == 2 && id.charAt(1) == 't') {
+                d = '>';
+            }
+        } else if (c == 'l') { // lt?
+            if (id.length() == 2 && id.charAt(1) == 't') {
+                d = '<';
+            }
+        } else if (c == 'q') { // quot?
+            if (id.equals("quot")) {
+                d = '"';
+            }
+        }
+
+        if (d != CHAR_NULL) {
+            if (mCfgTreatCharRefsAsEntities) {
+                final char[] originalChars = new char[id.length()];
+                id.getChars(0, id.length(), originalChars, 0);
+                mCurrEntity = getIntEntity(d, originalChars);
+                return 0;
+            }
+            return d;
+        }
+
+        final EntityDecl e = expandEntity(id, allowExt, null);
+        if (mCfgTreatCharRefsAsEntities) {
+            mCurrEntity = e;
+        }
+        return 0;
+    }
+
+    /**
+     * Returns an entity (possibly from cache) for the argument character using the encoded
+     * representation in mInputBuffer[entityStartPos ... mInputPtr-1].
+     */
+    protected EntityDecl getIntEntity(int ch, final char[] originalChars)
+    {
+        String cacheKey = new String(originalChars);
+
+        IntEntity entity = (IntEntity) mCachedEntities.get(cacheKey);
+        if (entity == null) {
+            String repl;
+            if (ch <= 0xFFFF) {
+                repl = Character.toString((char) ch);
+            } else {
+                StringBuffer sb = new StringBuffer(2);
+                ch -= 0x10000;
+                sb.append((char) ((ch >> 10)  + 0xD800));
+                sb.append((char) ((ch & 0x3FF)  + 0xDC00));
+                repl = sb.toString();
+            }
+            entity = IntEntity.create(new String(originalChars), repl);
+            mCachedEntities.put(cacheKey, entity);
+        }
+        return entity;
+    }
+
+
+    /**
+     * Helper method that will try to expand a parsed entity (parameter or
+     * generic entity).
+     *<p>
+     * note: called by sub-classes (dtd parser), needs to be protected.
+     *
+     * @param id Name of the entity being expanded 
+     * @param allowExt Whether external entities can be expanded or not; if
+     *   not, and the entity to expand would be external one, an exception
+     *   will be thrown
+     */
+    protected EntityDecl expandEntity(String id, boolean allowExt,
+                                      Object extraArg)
+        throws XMLStreamException
+    {
+        mCurrName = id;
+
+        EntityDecl ed = findEntity(id, extraArg);
+
+        if (ed == null) {
+            /* 30-Sep-2005, TSa: As per [WSTX-5], let's only throw exception
+             *   if we have to resolve it (otherwise it's just best-effort, 
+             *   and null is ok)
+             */
+            /* 02-Oct-2005, TSa: Plus, [WSTX-4] adds "undeclared entity
+             *    resolver"
+             */
+            if (mCfgReplaceEntities) {
+                mCurrEntity = expandUnresolvedEntity(id);
+            }
+            return null;
+        }
+        
+        if (!mCfgTreatCharRefsAsEntities || this instanceof MinimalDTDReader) {
+            expandEntity(ed, allowExt);
+        }
+        
+        return ed;
+    }
+
+    /**
+     *
+     *<p>
+     * note: defined as private for documentation, ie. it's just called
+     * from within this class (not sub-classes), from one specific method
+     * (see above)
+     *
+     * @param ed Entity to be expanded
+     * @param allowExt Whether external entities are allowed or not.
+     */
+    private void expandEntity(EntityDecl ed, boolean allowExt)
+        throws XMLStreamException
+    {
+        String id = ed.getName();
+
+        /* Very first thing; we can immediately check if expanding
+         * this entity would result in infinite recursion:
+         */
+        if (mInput.isOrIsExpandedFrom(id)) {
+            throwRecursionError(id);
+        }
+
+        /* Should not refer unparsed entities from attribute values
+         * or text content (except via notation mechanism, but that's
+         * not parsed here)
+         */
+        if (!ed.isParsed()) {
+            throwParseError("Illegal reference to unparsed external entity \"{0}\"", id, null);
+        }
+
+        // 28-Jun-2004, TSa: Do we support external entity expansion?
+        boolean isExt = ed.isExternal();
+        if (isExt) {
+            if (!allowExt) { // never ok in attribute value...
+                throwParseError("Encountered a reference to external parsed entity \"{0}\" when expanding attribute value: not legal as per XML 1.0/1.1 #3.1", id, null);
+            }
+            if (!mConfig.willSupportExternalEntities()) {
+                throwParseError("Encountered a reference to external entity \"{0}\", but stream reader has feature \"{1}\" disabled",
+                                id, XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES);
+            }
+        }
+
+        // First, let's give current context chance to save its stuff
+        WstxInputSource oldInput = mInput;
+        oldInput.saveContext(this);
+        WstxInputSource newInput = null;
+        try {
+            newInput = ed.expand(oldInput, mEntityResolver, mConfig, mDocXmlVersion);
+        } catch (FileNotFoundException fex) {
+            /* Let's catch and rethrow this just so we get more meaningful
+             * description (with input source position etc)
+             */
+            throwParseError("(was {0}) {1}", fex.getClass().getName(), fex.getMessage());
+        } catch (IOException ioe) {
+            throw constructFromIOE(ioe);
+        }
+        /* And then we'll need to make sure new input comes from the new
+         * input source
+         */
+        initInputSource(newInput, isExt, id);
+    }
+
+    /**
+     *<p>
+     * note: only called from the local expandEntity() method
+     */
+    private EntityDecl expandUnresolvedEntity(String id)
+        throws XMLStreamException
+    {
+        XMLResolver resolver = mConfig.getUndeclaredEntityResolver();
+        if (resolver != null) {
+            /* Ok, we can check for recursion here; but let's only do that
+             * if there is any chance that it might get resolved by
+             * the special resolver (it must have been resolved this way
+             * earlier, too...)
+             */
+            if (mInput.isOrIsExpandedFrom(id)) {
+                throwRecursionError(id);
+            }
+
+            WstxInputSource oldInput = mInput;
+            oldInput.saveContext(this);
+            // null, null -> no public or system ids
+            int xmlVersion = mDocXmlVersion;
+            // 05-Feb-2006, TSa: If xmlVersion not explicitly known, defaults to 1.0
+            if (xmlVersion == XmlConsts.XML_V_UNKNOWN) {
+                xmlVersion = XmlConsts.XML_V_10;
+            }
+            WstxInputSource newInput;
+            try {
+                newInput = DefaultInputResolver.resolveEntityUsing
+                    (oldInput, id, null, null, resolver, mConfig, xmlVersion);
+                if (mCfgTreatCharRefsAsEntities) {
+                    return new IntEntity(WstxInputLocation.getEmptyLocation(), newInput.getEntityId(),
+                            newInput.getSource(), new char[]{}, WstxInputLocation.getEmptyLocation());
+                }
+            } catch (IOException ioe) {
+                throw constructFromIOE(ioe);
+            }
+            if (newInput != null) {
+                // true -> is external
+                initInputSource(newInput, true, id);
+                return null;
+            }
+        }
+        handleUndeclaredEntity(id);
+        return null;
+    }
+
+    /*
+    ///////////////////////////////////////////////////////////
+    // Abstract methods for sub-classes to implement
+    ///////////////////////////////////////////////////////////
+     */
+
+    /**
+     * Abstract method for sub-classes to implement, for finding
+     * a declared general or parsed entity.
+     *
+     * @param id Identifier of the entity to find
+     * @param arg Optional argument passed from caller; needed by DTD
+     *    reader.
+     */
+    protected abstract EntityDecl findEntity(String id, Object arg)
+        throws XMLStreamException;
+
+    /**
+     * This method gets called if a declaration for an entity was not
+     * found in entity expanding mode (enabled by default for xml reader,
+     * always enabled for dtd reader).
+     */
+    protected abstract void handleUndeclaredEntity(String id)
+        throws XMLStreamException;
+
+    protected abstract void handleIncompleteEntityProblem(WstxInputSource closing)
+        throws XMLStreamException;
+
+    /*
+    ///////////////////////////////////////////////////////////
+    // Basic tokenization
+    ///////////////////////////////////////////////////////////
+     */
+
+    /**
+     * Method that will parse name token (roughly equivalent to XML specs;
+     * although bit lenier for more efficient handling); either uri prefix,
+     * or local name.
+     *<p>
+     * Much of complexity in this method has to do with the intention to 
+     * try to avoid any character copies. In this optimal case algorithm
+     * would be fairly simple. However, this only works if all data is
+     * already in input buffer... if not, copy has to be made halfway
+     * through parsing, and that complicates things.
+     *<p>
+     * One thing to note is that String returned has been canonicalized
+     * and (if necessary) added to symbol table. It can thus be compared
+     * against other such (usually id) Strings, with simple equality operator.
+     *
+     * @param c First character of the name; not yet checked for validity
+     *
+     * @return Canonicalized name String (which may have length 0, if
+     *    EOF or non-name-start char encountered)
+     */
+    protected String parseLocalName(char c)
+        throws XMLStreamException
+    {
+        /* Has to start with letter, or '_' (etc); we won't allow ':' as that
+         * is taken as namespace separator; no use trying to optimize
+         * heavily as it's 98% likely it is a valid char...
+         */
+        if (!isNameStartChar(c)) {
+            if (c == ':') {
+                throwUnexpectedChar(c, " (missing namespace prefix?)");
+            }
+            throwUnexpectedChar(c, " (expected a name start character)");
+        }
+
+        int ptr = mInputPtr;
+        int hash = (int) c;
+        final int inputLen = mInputEnd;
+        int startPtr = ptr-1; // already read previous char
+        final char[] inputBuf = mInputBuffer;
+
+        /* After which there may be zero or more name chars
+         * we have to consider
+         */
+        while (true) {
+            if (ptr >= inputLen) {
+                /* Ok, identifier may continue past buffer end, need
+                 * to continue with part 2 (separate method, as this is
+                 * not as common as having it all in buffer)
+                 */
+                mInputPtr = ptr;
+                return parseLocalName2(startPtr, hash);
+            }
+            // Ok, we have the char... is it a name char?
+            c = inputBuf[ptr];
+            if (c < CHAR_LOWEST_LEGAL_LOCALNAME_CHAR) {
+                break;
+            }
+            if (!isNameChar(c)) {
+                break;
+            }
+            hash = (hash * 31) + (int) c;
+            ++ptr;
+        }
+        mInputPtr = ptr;
+        return mSymbols.findSymbol(mInputBuffer, startPtr, ptr - startPtr, hash);
+    }
+
+    /**
+     * Second part of name token parsing; called when name can continue
+     * past input buffer end (so only part was read before calling this
+     * method to read the rest).
+     *<p>
+     * Note that this isn't heavily optimized, on assumption it's not
+     * called very often.
+     */
+    protected String parseLocalName2(int start, int hash)
+        throws XMLStreamException
+    {
+        int ptr = mInputEnd - start;
+        // Let's assume fairly short names
+        char[] outBuf = getNameBuffer(ptr+8);
+
+        if (ptr > 0) {
+            System.arraycopy(mInputBuffer, start, outBuf, 0, ptr);
+        }
+
+        int outLen = outBuf.length;
+        while (true) {
+            // note: names can not cross input block (entity) boundaries...
+            if (mInputPtr >= mInputEnd) {
+                if (!loadMoreFromCurrent()) {
+                    break;
+                }
+            }
+            char c = mInputBuffer[mInputPtr];
+            if (c < CHAR_LOWEST_LEGAL_LOCALNAME_CHAR) {
+                break;
+            }
+            if (!isNameChar(c)) {
+                break;
+            }
+            ++mInputPtr;
+            if (ptr >= outLen) {
+                mNameBuffer = outBuf = expandBy50Pct(outBuf);
+                outLen = outBuf.length;
+            }
+            outBuf[ptr++] = c;
+            hash = (hash * 31) + (int) c;
+        }
+        // Still need to canonicalize the name:
+        return mSymbols.findSymbol(outBuf, 0, ptr, hash);
+    }
+
+    /**
+     * Method that will parse 'full' name token; what full means depends on
+     * whether reader is namespace aware or not. If it is, full name means
+     * local name with no namespace prefix (PI target, entity/notation name);
+     * if not, name can contain arbitrary number of colons. Note that
+     * element and attribute names are NOT parsed here, so actual namespace
+     * prefix separation can be handled properly there.
+     *<p>
+     * Similar to {@link #parseLocalName}, much of complexity stems from
+     * trying to avoid copying name characters from input buffer.
+     *<p>
+     * Note that returned String will be canonicalized, similar to
+     * {@link #parseLocalName}, but without separating prefix/local name.
+      *
+     * @return Canonicalized name String (which may have length 0, if
+     *    EOF or non-name-start char encountered)
+     */
+    protected String parseFullName()
+        throws XMLStreamException
+    {
+        if (mInputPtr >= mInputEnd) {
+            loadMoreFromCurrent();
+        }
+        return parseFullName(mInputBuffer[mInputPtr++]);
+    }
+
+    protected String parseFullName(char c)
+        throws XMLStreamException
+    {
+        // First char has special handling:
+        if (!isNameStartChar(c)) {
+            if (c == ':') { // no name.... generally an error:
+                if (mCfgNsEnabled) {
+                    throwNsColonException(parseFNameForError());
+                }
+                // Ok, that's fine actually
+            } else {
+                if (c <= CHAR_SPACE) {
+                    throwUnexpectedChar(c, " (missing name?)");
+                }
+                throwUnexpectedChar(c, " (expected a name start character)");
+            }
+        }
+
+        int ptr = mInputPtr;
+        int hash = (int) c;
+        int inputLen = mInputEnd;
+        int startPtr = ptr-1; // to account for the first char
+
+        /* After which there may be zero or more name chars
+         * we have to consider
+         */
+        while (true) {
+            if (ptr >= inputLen) {
+                /* Ok, identifier may continue past buffer end, need
+                 * to continue with part 2 (separate method, as this is
+                 * not as common as having it all in buffer)
+                 */
+                mInputPtr = ptr;
+                return parseFullName2(startPtr, hash);
+            }
+            c = mInputBuffer[ptr];
+            if (c == ':') { // colon only allowed in non-NS mode
+                if (mCfgNsEnabled) {
+                    mInputPtr = ptr;
+                    throwNsColonException(new String(mInputBuffer, startPtr, ptr - startPtr) + parseFNameForError());
+                }
+            } else {
+                if (c < CHAR_LOWEST_LEGAL_LOCALNAME_CHAR) {
+                    break;
+                }
+                if (!isNameChar(c)) {
+                    break;
+                }
+            }
+            hash = (hash * 31) + (int) c;
+            ++ptr;
+        }
+        mInputPtr = ptr;
+        return mSymbols.findSymbol(mInputBuffer, startPtr, ptr - startPtr, hash);
+    }
+
+    protected String parseFullName2(int start, int hash)
+        throws XMLStreamException
+    {
+        int ptr = mInputEnd - start;
+        // Let's assume fairly short names
+        char[] outBuf = getNameBuffer(ptr+8);
+
+        if (ptr > 0) {
+            System.arraycopy(mInputBuffer, start, outBuf, 0, ptr);
+        }
+
+        int outLen = outBuf.length;
+        while (true) {
+            /* 06-Sep-2004, TSa: Name tokens are not allowed to continue
+             *   past entity expansion ranges... that is, all characters
+             *   have to come from the same input source. Thus, let's only
+             *   load things from same input level
+             */
+            if (mInputPtr >= mInputEnd) {
+                if (!loadMoreFromCurrent()) {
+                    break;
+                }
+            }
+            char c = mInputBuffer[mInputPtr];
+            if (c == ':') { // colon only allowed in non-NS mode
+                if (mCfgNsEnabled) {
+                    throwNsColonException(new String(outBuf, 0, ptr) + c + parseFNameForError());
+                }
+            } else if (c < CHAR_LOWEST_LEGAL_LOCALNAME_CHAR) {
+                break;
+            } else if (!isNameChar(c)) {
+                break;
+            }
+            ++mInputPtr;
+
+            if (ptr >= outLen) {
+                mNameBuffer = outBuf = expandBy50Pct(outBuf);
+                outLen = outBuf.length;
+            }
+            outBuf[ptr++] = c;
+            hash = (hash * 31) + (int) c;
+        }
+
+        // Still need to canonicalize the name:
+        return mSymbols.findSymbol(outBuf, 0, ptr, hash);
+    }
+
+    /**
+     * Method called to read in full name, including unlimited number of
+     * namespace separators (':'), for the purpose of displaying name in
+     * an error message. Won't do any further validations, and parsing
+     * is not optimized: main need is just to get more meaningful error
+     * messages.
+     */
+    protected String parseFNameForError()
+        throws XMLStreamException
+    {
+        StringBuffer sb = new StringBuffer(100);
+        while (true) {
+            char c;
+
+            if (mInputPtr < mInputEnd) {
+                c = mInputBuffer[mInputPtr++];
+            } else { // can't error here, so let's accept EOF for now:
+                int i = getNext();
+                if (i < 0) {
+                    break;
+                }
+                c = (char) i;
+            }
+            if (c != ':' && !isNameChar(c)) {
+                --mInputPtr;
+                break;
+            }
+            sb.append(c);
+        }
+        return sb.toString();
+    }
+
+    protected final String parseEntityName(char c)
+        throws XMLStreamException
+    {
+        String id = parseFullName(c);
+        // Needs to be followed by a semi-colon, too.. from same input source:
+        if (mInputPtr >= mInputEnd) {
+            if (!loadMoreFromCurrent()) {
+                throwParseError("Missing semicolon after reference for entity \"{0}\"", id, null);
+            }
+        }
+        c = mInputBuffer[mInputPtr++];
+        if (c != ';') {
+            throwUnexpectedChar(c, "; expected a semi-colon after the reference for entity '"+id+"'");
+        }
+        return id;
+    }
+    
+    /**
+     * Note: does not check for number of colons, amongst other things.
+     * Main idea is to skip through what superficially seems like a valid
+     * id, nothing more. This is only done when really skipping through
+     * something we do not care about at all: not even whether names/ids
+     * would be valid (for example, when ignoring internal DTD subset).
+     *
+     * @return Length of skipped name.
+     */
+    protected int skipFullName(char c)
+        throws XMLStreamException
+    {
+        if (!isNameStartChar(c)) {
+            --mInputPtr;
+            return 0;
+        }
+
+        /* After which there may be zero or more name chars
+         * we have to consider
+         */
+        int count = 1;
+        while (true) {
+            c = (mInputPtr < mInputEnd) ?
+                mInputBuffer[mInputPtr++] : getNextChar(SUFFIX_EOF_EXP_NAME);
+            if (c != ':' && !isNameChar(c)) {
+                break;
+            }
+            ++count;
+        }
+        return count;
+    }
+
+    /**
+     * Simple parsing method that parses system ids, which are generally
+     * used in entities (from DOCTYPE declaration to internal/external
+     * subsets).
+     *<p>
+     * NOTE: returned String is not canonicalized, on assumption that
+     * external ids may be longish, and are not shared all that often, as
+     * they are generally just used for resolving paths, if anything.
+     *<br />
+     * Also note that this method is not heavily optimized, as it's not
+     * likely to be a bottleneck for parsing.
+     */
+    protected final String parseSystemId(char quoteChar, boolean convertLFs,
+                                         String errorMsg)
+        throws XMLStreamException
+    {
+        char[] buf = getNameBuffer(-1);
+        int ptr = 0;
+
+        while (true) {
+            char c = (mInputPtr < mInputEnd) ?
+                mInputBuffer[mInputPtr++] : getNextChar(errorMsg);
+            if (c == quoteChar) {
+                break;
+            }
+            /* ??? 14-Jun-2004, TSa: Should we normalize linefeeds or not?
+             *   It seems like we should, for all input... so that's the way it
+             *   works.
+             */
+            if (c == '\n') {
+                markLF();
+            } else if (c == '\r') {
+                if (peekNext() == '\n') {
+                    ++mInputPtr;
+                    if (!convertLFs) {
+                        /* The only tricky thing; need to preserve 2-char LF; need to
+                         * output one char from here, then can fall back to default:
+                         */
+                        if (ptr >= buf.length) {
+                            buf = expandBy50Pct(buf);
+                        }
+                        buf[ptr++] = '\r';
+                    }
+                    c = '\n';
+                } else if (convertLFs) {
+                    c = '\n';
+                }
+            }
+
+            // Other than that, let's just append it:
+            if (ptr >= buf.length) {
+                buf = expandBy50Pct(buf);
+            }
+            buf[ptr++] = c;
+        }
+
+        return (ptr == 0) ? "" : new String(buf, 0, ptr);
+    }
+
+    /**
+     * Simple parsing method that parses system ids, which are generally
+     * used in entities (from DOCTYPE declaration to internal/external
+     * subsets).
+     *<p>
+     * As per xml specs, the contents are actually normalized.
+     *<p>
+     * NOTE: returned String is not canonicalized, on assumption that
+     * external ids may be longish, and are not shared all that often, as
+     * they are generally just used for resolving paths, if anything.
+     *<br />
+     * Also note that this method is not heavily optimized, as it's not
+     * likely to be a bottleneck for parsing.
+     */
+    protected final String parsePublicId(char quoteChar, String errorMsg)
+        throws XMLStreamException
+    {
+        char[] buf = getNameBuffer(-1);
+        int ptr = 0;
+        boolean spaceToAdd = false;
+
+        while (true) {
+            char c = (mInputPtr < mInputEnd) ?
+                mInputBuffer[mInputPtr++] : getNextChar(errorMsg);
+            if (c == quoteChar) {
+                break;
+            }
+            if (c == '\n') {
+                markLF();
+                spaceToAdd = true;
+                continue;
+            } else if (c == '\r') {
+                if (peekNext() == '\n') {
+                    ++mInputPtr;
+                }
+                spaceToAdd = true;
+                continue;
+            } else if (c == CHAR_SPACE) {
+                spaceToAdd = true;
+                continue;
+            } else {
+                // Verify it's a legal pubid char (see XML spec, #13, from 2.3)
+                if ((c >= VALID_PUBID_CHAR_COUNT)
+                    || sPubidValidity[c] != PUBID_CHAR_VALID_B) {
+                    throwUnexpectedChar(c, " in public identifier");
+                }
+            }
+        
+            // Other than that, let's just append it:
+            if (ptr >= buf.length) {
+                buf = expandBy50Pct(buf);
+            }
+            /* Space-normalization means scrapping leading and trailing
+             * white space, and coalescing remaining ws into single spaces.
+             */
+            if (spaceToAdd) { // pending white space to add?
+                if (c == CHAR_SPACE) { // still a space; let's skip
+                    continue;
+                }
+                /* ok: if we have non-space, we'll either forget about
+                 * space(s) (if nothing has been output, ie. leading space),
+                 * or output a single space (in-between non-white space)
+                 */
+                spaceToAdd = false;
+                if (ptr > 0) {
+                    buf[ptr++] = CHAR_SPACE;
+                    if (ptr >= buf.length) {
+                        buf = expandBy50Pct(buf);
+                    }
+                }
+            }
+            buf[ptr++] = c;
+        }
+      
+        return (ptr == 0) ? "" : new String(buf, 0, ptr);
+    }
+
+    protected final void parseUntil(TextBuffer tb, char endChar, boolean convertLFs,
+                                    String errorMsg)
+        throws XMLStreamException
+    {
+        // Let's first ensure we have some data in there...
+        if (mInputPtr >= mInputEnd) {
+            loadMore(errorMsg);
+        }
+        while (true) {
+            // Let's loop consequtive 'easy' spans:
+            char[] inputBuf = mInputBuffer;
+            int inputLen = mInputEnd;
+            int ptr = mInputPtr;
+            int startPtr = ptr;
+            while (ptr < inputLen) {
+                char c = inputBuf[ptr++];
+                if (c == endChar) {
+                    int thisLen = ptr - startPtr - 1;
+                    if (thisLen > 0) {
+                        tb.append(inputBuf, startPtr, thisLen);
+                    }
+                    mInputPtr = ptr;
+                    return;
+                }
+                if (c == '\n') {
+                    mInputPtr = ptr; // markLF() requires this
+                    markLF();
+                } else if (c == '\r') {
+                    if (!convertLFs && ptr < inputLen) {
+                        if (inputBuf[ptr] == '\n') {
+                            ++ptr;
+                        }
+                        mInputPtr = ptr;
+                        markLF();
+                    } else {
+                        int thisLen = ptr - startPtr - 1;
+                        if (thisLen > 0) {
+                            tb.append(inputBuf, startPtr, thisLen);
+                        }
+                        mInputPtr = ptr;
+                        c = getNextChar(errorMsg);
+                        if (c != '\n') {
+                            --mInputPtr; // pusback
+                            tb.append(convertLFs ? '\n' : '\r');
+                        } else {
+                            if (convertLFs) {
+                                tb.append('\n');
+                            } else {
+                                tb.append('\r');
+                                tb.append('\n');
+                            }
+                        }
+                        startPtr = ptr = mInputPtr;
+                        markLF();
+                    }
+                }
+            }
+            int thisLen = ptr - startPtr;
+            if (thisLen > 0) {
+                tb.append(inputBuf, startPtr, thisLen);
+            }
+            loadMore(errorMsg);
+            startPtr = ptr = mInputPtr;
+            inputBuf = mInputBuffer;
+            inputLen = mInputEnd;
+        }
+    }
+
+    /*
+    ///////////////////////////////////////////////////////////
+    // Internal methods
+    ///////////////////////////////////////////////////////////
+     */
+
+    private int resolveCharEnt(StringBuffer originalCharacters)
+        throws XMLStreamException
+    {
+        int value = 0;
+        char c = getNextChar(SUFFIX_IN_ENTITY_REF);
+        
+        if (originalCharacters != null) {
+            originalCharacters.append(c);
+        }
+        
+        if (c == 'x') { // hex
+            while (true) {
+                c = (mInputPtr < mInputEnd) ? mInputBuffer[mInputPtr++]
+                    : getNextCharFromCurrent(SUFFIX_IN_ENTITY_REF);
+                if (c == ';') {
+                    break;
+                }
+                
+                if (originalCharacters != null) {
+                    originalCharacters.append(c);
+                }
+                value = value << 4;
+                if (c <= '9' && c >= '0') {
+                    value += (c - '0');
+                } else if (c >= 'a' && c <= 'f') {
+                    value += 10 + (c - 'a');
+                } else if (c >= 'A' && c <= 'F') {
+                    value += 10 + (c - 'A');
+                } else {
+                    throwUnexpectedChar(c, "; expected a hex digit (0-9a-fA-F).");
+                }
+                // Overflow?
+                if (value > MAX_UNICODE_CHAR) {
+                    reportUnicodeOverflow();
+                }
+            }
+        } else { // numeric (decimal)
+            while (c != ';') {
+                if (c <= '9' && c >= '0') {
+                    value = (value * 10) + (c - '0');
+                    // Overflow?
+                    if (value > MAX_UNICODE_CHAR) {
+                        reportUnicodeOverflow();
+                    }
+                } else {
+                    throwUnexpectedChar(c, "; expected a decimal number.");
+                }
+                c = (mInputPtr < mInputEnd) ? mInputBuffer[mInputPtr++]
+                    : getNextCharFromCurrent(SUFFIX_IN_ENTITY_REF);
+                
+                if (originalCharacters != null && c != ';') {
+                    originalCharacters.append(c);
+                }
+            }
+        }
+        validateChar(value);
+        return value;
+    }
+
+    /**
+     * Method that will verify that expanded Unicode codepoint is a valid
+     * XML content character.
+     */
+    private final void validateChar(int value)
+        throws XMLStreamException
+    {
+        /* 24-Jan-2006, TSa: Ok, "high" Unicode chars are problematic,
+         *   need to be reported by a surrogate pair..
+         */
+        if (value >= 0xD800) {
+            if (value < 0xE000) { // no surrogates via entity expansion
+                reportIllegalChar(value);
+            }
+            if (value > 0xFFFF) {
+                // Within valid range at all?
+                if (value > MAX_UNICODE_CHAR) {
+                    reportUnicodeOverflow();
+                }
+            } else if (value >= 0xFFFE) { // 0xFFFE and 0xFFFF are illegal too
+                reportIllegalChar(value);
+            }
+            // Ok, fine as is
+        } else if (value < 32) {
+            if (value == 0) {
+                throwParseError("Invalid character reference: null character not allowed in XML content.");
+            }
+            // XML 1.1 allows most other chars; 1.0 does not: However Exchange sends such chars with XML 1.0
+            //if (!mXml11 &&
+            //    (value != 0x9 && value != 0xA && value != 0xD)) {
+            //    reportIllegalChar(value);
+            //}
+        }
+    }
+
+    protected final char[] getNameBuffer(int minSize)
+    {
+        char[] buf = mNameBuffer;
+        
+        if (buf == null) {
+            mNameBuffer = buf = new char[(minSize > 48) ? (minSize+16) : 64];
+        } else if (minSize >= buf.length) { // let's allow one char extra...
+            int len = buf.length;
+            len += (len >> 1); // grow by 50%
+            mNameBuffer = buf = new char[(minSize >= len) ? (minSize+16) : len];
+        }
+        return buf;
+    }
+    
+    protected final char[] expandBy50Pct(char[] buf)
+    {
+        int len = buf.length;
+        char[] newBuf = new char[len + (len >> 1)];
+        System.arraycopy(buf, 0, newBuf, 0, len);
+        return newBuf;
+    }
+
+    /**
+     * Method called to throw an exception indicating that a name that
+     * should not be namespace-qualified (PI target, entity/notation name)
+     * is one, and reader is namespace aware.
+     */
+    private void throwNsColonException(String name)
+        throws XMLStreamException
+    {
+        throwParseError("Illegal name \"{0}\" (PI target, entity/notation name): can not contain a colon (XML Namespaces 1.0#6)", name, null);
+    }
+
+    private void throwRecursionError(String entityName)
+        throws XMLStreamException
+    {
+        throwParseError("Illegal entity expansion: entity \"{0}\" expands itself recursively.", entityName, null);
+    }
+
+    private void reportUnicodeOverflow()
+        throws XMLStreamException
+    {
+        throwParseError("Illegal character entity: value higher than max allowed (0x{0})", Integer.toHexString(MAX_UNICODE_CHAR), null);
+    }
+
+    private void reportIllegalChar(int value)
+        throws XMLStreamException
+    {
+        throwParseError("Illegal character entity: expansion character (code 0x{0}", Integer.toHexString(value), null);
+    }
+}
diff --git a/src/java/davmail/AbstractConnection.java b/src/java/davmail/AbstractConnection.java
new file mode 100644
index 0000000..2c49277
--- /dev/null
+++ b/src/java/davmail/AbstractConnection.java
@@ -0,0 +1,278 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail;
+
+import davmail.exception.DavMailException;
+import davmail.exchange.ExchangeSession;
+import davmail.ui.tray.DavGatewayTray;
+import org.apache.commons.codec.binary.Base64;
+
+import java.io.*;
+import java.net.Socket;
+
+
+/**
+ * Generic connection common to pop3 and smtp implementations
+ */
+public class AbstractConnection extends Thread {
+
+    protected enum State {
+        INITIAL, LOGIN, USER, PASSWORD, AUTHENTICATED, STARTMAIL, RECIPIENT, MAILDATA
+    }
+
+    protected static class LineReaderInputStream extends PushbackInputStream {
+        final String encoding;
+
+        /**
+         * @inheritDoc
+         */
+        protected LineReaderInputStream(InputStream in, String encoding) {
+            super(in);
+            if (encoding == null) {
+                this.encoding = "ASCII";
+            } else {
+                this.encoding = encoding;
+            }
+        }
+
+        public String readLine() throws IOException {
+            ByteArrayOutputStream baos = null;
+            int b;
+            while ((b = read()) > -1) {
+                if (b == '\r') {
+                    int next = read();
+                    if (next != '\n') {
+                        unread(next);
+                    }
+                    break;
+                } else if (b == '\n') {
+                    break;
+                }
+                if (baos == null) {
+                    baos = new ByteArrayOutputStream();
+                }
+                baos.write(b);
+            }
+            if (baos != null) {
+                return new String(baos.toByteArray(), encoding);
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Read byteSize bytes from inputStream, return content as String.
+         *
+         * @param byteSize content size
+         * @return content
+         * @throws IOException on error
+         */
+        public String readContentAsString(int byteSize) throws IOException {
+            return new String(readContent(byteSize), encoding);
+        }
+
+        /**
+         * Read byteSize bytes from inputStream, return content as byte array.
+         *
+         * @param byteSize content size
+         * @return content
+         * @throws IOException on error
+         */
+        public byte[] readContent(int byteSize) throws IOException {
+            byte[] buffer = new byte[byteSize];
+            int startIndex = 0;
+            int count = 0;
+            while (count >= 0 && startIndex < byteSize) {
+                count = in.read(buffer, startIndex, byteSize - startIndex);
+                startIndex += count;
+            }
+            if (startIndex < byteSize) {
+                throw new DavMailException("EXCEPTION_END_OF_STREAM");
+            }
+
+            return buffer;
+        }
+    }
+
+    protected final Socket client;
+
+    protected LineReaderInputStream in;
+    protected OutputStream os;
+    // user name and password initialized through connection
+    protected String userName;
+    protected String password;
+    // connection state
+    protected State state = State.INITIAL;
+    // Exchange session proxy
+    protected ExchangeSession session;
+
+    /**
+     * Only set the thread name and socket
+     *
+     * @param name         thread type name
+     * @param clientSocket client socket
+     */
+    public AbstractConnection(String name, Socket clientSocket) {
+        super(name + '-' + clientSocket.getPort());
+        this.client = clientSocket;
+        setDaemon(true);
+    }
+
+    /**
+     * Initialize the streams and set thread name.
+     *
+     * @param name         thread type name
+     * @param clientSocket client socket
+     * @param encoding     socket stream encoding
+     */
+    public AbstractConnection(String name, Socket clientSocket, String encoding) {
+        super(name + '-' + clientSocket.getPort());
+        this.client = clientSocket;
+        try {
+            in = new LineReaderInputStream(client.getInputStream(), encoding);
+            os = new BufferedOutputStream(client.getOutputStream());
+        } catch (IOException e) {
+            close();
+            DavGatewayTray.error(new BundleMessage("LOG_EXCEPTION_GETTING_SOCKET_STREAMS"), e);
+        }
+    }
+
+    /**
+     * Send message to client followed by CRLF.
+     *
+     * @param message message
+     * @throws IOException on error
+     */
+    public void sendClient(String message) throws IOException {
+        sendClient(null, message);
+    }
+
+    /**
+     * Send prefix and message to client followed by CRLF.
+     *
+     * @param prefix  prefix
+     * @param message message
+     * @throws IOException on error
+     */
+    public void sendClient(String prefix, String message) throws IOException {
+        if (prefix != null) {
+            os.write(prefix.getBytes());
+            DavGatewayTray.debug(new BundleMessage("LOG_SEND_CLIENT_PREFIX_MESSAGE", prefix, message));
+        } else {
+            DavGatewayTray.debug(new BundleMessage("LOG_SEND_CLIENT_MESSAGE", message));
+        }
+        os.write(message.getBytes());
+        os.write((char) 13);
+        os.write((char) 10);
+        os.flush();
+    }
+
+    /**
+     * Send only bytes to client.
+     *
+     * @param messageBytes content
+     * @throws IOException on error
+     */
+    public void sendClient(byte[] messageBytes) throws IOException {
+        sendClient(messageBytes, 0, messageBytes.length);
+    }
+
+    /**
+     * Send only bytes to client.
+     *
+     * @param messageBytes content
+     * @param offset       the start offset in the data.
+     * @param length       the number of bytes to write.
+     * @throws IOException on error
+     */
+    public void sendClient(byte[] messageBytes, int offset, int length) throws IOException {
+        //StringBuffer logBuffer = new StringBuffer("> ");
+        //logBuffer.append(new String(messageBytes, offset, length));
+        //DavGatewayTray.debug(logBuffer.toString());
+        os.write(messageBytes, offset, length);
+        os.flush();
+    }
+
+    /**
+     * Read a line from the client connection.
+     * Log message to logger
+     *
+     * @return command line or null
+     * @throws IOException when unable to read line
+     */
+    public String readClient() throws IOException {
+        String line = in.readLine();
+        if (line != null) {
+            if (line.startsWith("PASS")) {
+                DavGatewayTray.debug(new BundleMessage("LOG_READ_CLIENT_PASS"));
+                // SMTP LOGIN
+            } else if (line.startsWith("AUTH LOGIN ")) {
+                DavGatewayTray.debug(new BundleMessage("LOG_READ_CLIENT_AUTH_LOGIN"));
+                // IMAP LOGIN
+            } else if (state == State.INITIAL && line.indexOf(' ') >= 0 &&
+                    line.substring(line.indexOf(' ') + 1).toUpperCase().startsWith("LOGIN")) {
+                DavGatewayTray.debug(new BundleMessage("LOG_READ_CLIENT_LOGIN"));
+            } else if (state == State.PASSWORD) {
+                DavGatewayTray.debug(new BundleMessage("LOG_READ_CLIENT_PASSWORD"));
+                // HTTP Basic Authentication
+            } else if (line.startsWith("Authorization:")) {
+                DavGatewayTray.debug(new BundleMessage("LOG_READ_CLIENT_AUTHORIZATION"));
+            } else if (line.startsWith("AUTH PLAIN")) {
+                DavGatewayTray.debug(new BundleMessage("LOG_READ_CLIENT_AUTH_PLAIN"));
+            } else {
+                DavGatewayTray.debug(new BundleMessage("LOG_READ_CLIENT_LINE", line));
+            }
+        }
+        DavGatewayTray.switchIcon();
+        return line;
+    }
+
+    /**
+     * Close client connection, streams and Exchange session .
+     */
+    public void close() {
+        if (in != null) {
+            try {
+                in.close();
+            } catch (IOException e2) {
+                DavGatewayTray.warn(new BundleMessage("LOG_EXCEPTION_CLOSING_CLIENT_INPUT_STREAM"), e2);
+            }
+        }
+        if (os != null) {
+            try {
+                os.close();
+            } catch (IOException e2) {
+                DavGatewayTray.warn(new BundleMessage("LOG_EXCEPTION_CLOSING_CLIENT_OUTPUT_STREAM"), e2);
+            }
+        }
+        try {
+            client.close();
+        } catch (IOException e2) {
+            DavGatewayTray.warn(new BundleMessage("LOG_EXCEPTION_CLOSING_CLIENT_SOCKET"), e2);
+        }
+    }
+
+    protected String base64Encode(String value) {
+        return new String(new Base64().encode(value.getBytes()));
+    }
+
+    protected String base64Decode(String value) {
+        return new String(new Base64().decode(value.getBytes()));
+    }
+}
diff --git a/src/java/davmail/AbstractServer.java b/src/java/davmail/AbstractServer.java
new file mode 100644
index 0000000..7638e4b
--- /dev/null
+++ b/src/java/davmail/AbstractServer.java
@@ -0,0 +1,206 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail;
+
+import davmail.exception.DavMailException;
+import davmail.ui.tray.DavGatewayTray;
+
+import javax.net.ServerSocketFactory;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+
+/**
+ * Generic abstract server common to SMTP and POP3 implementations
+ */
+public abstract class AbstractServer extends Thread {
+    protected boolean nosslFlag; // will cause same behavior as before with unchanged config files
+    private final int port;
+    private ServerSocket serverSocket;
+
+    /**
+     * Get server protocol name (SMTP, POP, IMAP, ...).
+     *
+     * @return server protocol name
+     */
+    public abstract String getProtocolName();
+
+    /**
+     * Server socket TCP port
+     *
+     * @return port
+     */
+    public int getPort() {
+        return port;
+    }
+
+    /**
+     * Create a ServerSocket to listen for connections.
+     * Start the thread.
+     *
+     * @param name        thread name
+     * @param port        tcp socket chosen port
+     * @param defaultPort tcp socket default port
+     */
+    public AbstractServer(String name, int port, int defaultPort) {
+        super(name);
+        setDaemon(true);
+        if (port == 0) {
+            this.port = defaultPort;
+        } else {
+            this.port = port;
+        }
+    }
+
+    /**
+     * Bind server socket on defined port.
+     *
+     * @throws DavMailException unable to create server socket
+     */
+    public void bind() throws DavMailException {
+        String bindAddress = Settings.getProperty("davmail.bindAddress");
+        String keystoreFile = Settings.getProperty("davmail.ssl.keystoreFile");
+
+        ServerSocketFactory serverSocketFactory;
+        if (keystoreFile == null || keystoreFile.length() == 0 || nosslFlag) {
+            serverSocketFactory = ServerSocketFactory.getDefault();
+        } else {
+            FileInputStream keyStoreInputStream = null;
+            try {
+                keyStoreInputStream = new FileInputStream(keystoreFile);
+                // keystore for keys and certificates
+                // keystore and private keys should be password protected...
+                KeyStore keystore = KeyStore.getInstance(Settings.getProperty("davmail.ssl.keystoreType"));
+                keystore.load(keyStoreInputStream, Settings.getCharArrayProperty("davmail.ssl.keystorePass"));
+
+                // KeyManagerFactory to create key managers
+                KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+
+                // initialize KMF to work with keystore
+                kmf.init(keystore, Settings.getCharArrayProperty("davmail.ssl.keyPass"));
+
+                // SSLContext is environment for implementing JSSE...
+                // create ServerSocketFactory
+                SSLContext sslContext = SSLContext.getInstance("SSLv3");
+
+                // initialize sslContext to work with key managers
+                sslContext.init(kmf.getKeyManagers(), null, null);
+
+                // create ServerSocketFactory from sslContext
+                serverSocketFactory = sslContext.getServerSocketFactory();
+            } catch (IOException ex) {
+                throw new DavMailException("LOG_EXCEPTION_CREATING_SSL_SERVER_SOCKET", getProtocolName(), port, ex.getMessage() == null ? ex.toString() : ex.getMessage());
+            } catch (GeneralSecurityException ex) {
+                throw new DavMailException("LOG_EXCEPTION_CREATING_SSL_SERVER_SOCKET", getProtocolName(), port, ex.getMessage() == null ? ex.toString() : ex.getMessage());
+            } finally {
+                if (keyStoreInputStream != null) {
+                    try {
+                        keyStoreInputStream.close();
+                    } catch (IOException exc) {
+                        DavGatewayTray.warn(new BundleMessage("LOG_EXCEPTION_CLOSING_KEYSTORE_INPUT_STREAM"), exc);
+                    }
+                }
+            }
+        }
+        try {
+            // create the server socket
+            if (bindAddress == null || bindAddress.length() == 0) {
+                serverSocket = serverSocketFactory.createServerSocket(port);
+            } else {
+                serverSocket = serverSocketFactory.createServerSocket(port, 0, Inet4Address.getByName(bindAddress));
+            }
+        } catch (IOException e) {
+            throw new DavMailException("LOG_SOCKET_BIND_FAILED", getProtocolName(), port);
+        }
+    }
+
+
+    /**
+     * The body of the server thread.  Loop forever, listening for and
+     * accepting connections from clients.  For each connection,
+     * create a Connection object to handle communication through the
+     * new Socket.
+     */
+    @Override
+    public void run() {
+        Socket clientSocket = null;
+        AbstractConnection connection = null;
+        try {
+            //noinspection InfiniteLoopStatement
+            while (true) {
+                clientSocket = serverSocket.accept();
+                // set default timeout to 5 minutes
+                clientSocket.setSoTimeout(Settings.getIntProperty("davmail.clientSoTimeout", 300)*1000);
+                DavGatewayTray.debug(new BundleMessage("LOG_CONNECTION_FROM", clientSocket.getInetAddress(), port));
+                // only accept localhost connections for security reasons
+                if (Settings.getBooleanProperty("davmail.allowRemote") ||
+                        clientSocket.getInetAddress().isLoopbackAddress()) {
+                    connection = createConnectionHandler(clientSocket);
+                    connection.start();
+                } else {
+                    clientSocket.close();
+                    DavGatewayTray.warn(new BundleMessage("LOG_EXTERNAL_CONNECTION_REFUSED"));
+                }
+            }
+        } catch (IOException e) {
+            // do not warn if exception on socket close (gateway restart)
+            if (!serverSocket.isClosed()) {
+                DavGatewayTray.warn(new BundleMessage("LOG_EXCEPTION_LISTENING_FOR_CONNECTIONS"), e);
+            }
+        } finally {
+            try {
+                if (clientSocket != null) {
+                    clientSocket.close();
+                }
+            } catch (IOException e) {
+                DavGatewayTray.warn(new BundleMessage("LOG_EXCEPTION_CLOSING_CLIENT_SOCKET"), e);
+            }
+            if (connection != null) {
+                connection.close();
+            }
+        }
+    }
+
+    /**
+     * Create a connection handler for the current listener.
+     *
+     * @param clientSocket client socket
+     * @return connection handler
+     */
+    public abstract AbstractConnection createConnectionHandler(Socket clientSocket);
+
+    /**
+     * Close server socket
+     */
+    public void close() {
+        try {
+            if (serverSocket != null) {
+                serverSocket.close();
+            }
+        } catch (IOException e) {
+            DavGatewayTray.warn(new BundleMessage("LOG_EXCEPTION_CLOSING_SERVER_SOCKET"), e);
+        }
+    }
+}
diff --git a/src/java/davmail/BundleMessage.java b/src/java/davmail/BundleMessage.java
new file mode 100644
index 0000000..e020f4f
--- /dev/null
+++ b/src/java/davmail/BundleMessage.java
@@ -0,0 +1,218 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail;
+
+import davmail.exception.DavMailException;
+
+import java.io.Serializable;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Locale;
+import java.util.ResourceBundle;
+
+/**
+ * Internationalization message.
+ */
+public class BundleMessage implements Serializable {
+    /**
+     * Root locale to get english messages for logging.
+     */
+    public static final Locale ROOT_LOCALE = new Locale("", "");
+    protected static final String MESSAGE_BUNDLE_NAME = "davmailmessages";
+    protected final String key;
+    private final Object[] arguments;
+
+    /**
+     * Internationalization message.
+     *
+     * @param key       message key in resource bundle
+     * @param arguments message values
+     */
+    public BundleMessage(String key, Object... arguments) {
+        this.key = key;
+        this.arguments = arguments;
+    }
+
+
+    /**
+     * Format message with the default locale.
+     *
+     * @return formatted message
+     */
+    public String format() {
+        return format(null);
+    }
+
+    /**
+     * Format message with the given locale.
+     *
+     * @param locale resource bundle locale
+     * @return formatted message
+     */
+    public String format(Locale locale) {
+        return BundleMessage.format(locale, key, arguments);
+    }
+
+    /**
+     * Format message for logging (with the root locale).
+     * Log file should remain in english
+     *
+     * @return log formatted message
+     */
+    public String formatLog() {
+        return format(ROOT_LOCALE);
+    }
+
+    /**
+     * Format message for logging (with the root locale).
+     * Log file should remain in english
+     *
+     * @return log formatted message
+     */
+    @Override
+    public String toString() {
+        return formatLog();
+    }
+
+    /**
+     * Get bundle for the given locale.
+     * Load the properties file for the given locale in a resource bundle
+     *
+     * @param locale resource bundle locale
+     * @return resource bundle
+     */
+    protected static ResourceBundle getBundle(Locale locale) {
+        if (locale == null) {
+            return ResourceBundle.getBundle(MESSAGE_BUNDLE_NAME);
+        } else {
+            return ResourceBundle.getBundle(MESSAGE_BUNDLE_NAME, locale);
+        }
+    }
+
+    /**
+     * Get formatted message for message key and values with the default locale.
+     *
+     * @param key       message key in resource bundle
+     * @param arguments message values
+     * @return formatted message
+     */
+    public static String format(String key, Object... arguments) {
+        return format(null, key, arguments);
+    }
+
+    /**
+     * Get formatted message for message key and values with the given locale.
+     *
+     * @param locale    resource bundle locale
+     * @param key       message key in resource bundle
+     * @param arguments message values
+     * @return formatted message
+     */
+    public static String format(Locale locale, String key, Object... arguments) {
+        Object[] formattedArguments = null;
+        if (arguments != null) {
+            formattedArguments = new Object[arguments.length];
+            for (int i = 0; i < arguments.length; i++) {
+                if (arguments[i] instanceof BundleMessage) {
+                    formattedArguments[i] = ((BundleMessage) arguments[i]).format(locale);
+                } else if (arguments[i] instanceof BundleMessageList) {
+                    StringBuilder buffer = new StringBuilder();
+                    for (BundleMessage bundleMessage : (BundleMessageList) arguments[i]) {
+                        buffer.append(bundleMessage.format(locale));
+                    }
+                    formattedArguments[i] = buffer.toString();
+                } else if (arguments[i] instanceof DavMailException) {
+                    formattedArguments[i] = ((DavMailException) arguments[i]).getMessage(locale);
+                } else if (arguments[i] instanceof Throwable) {
+                    formattedArguments[i] = ((Throwable) arguments[i]).getMessage();
+                    if (formattedArguments[i] == null) {
+                        formattedArguments[i] = arguments[i].toString();
+                    }
+                } else {
+                    formattedArguments[i] = arguments[i];
+                }
+            }
+        }
+        return MessageFormat.format(getBundle(locale).getString(key), formattedArguments);
+    }
+
+    /**
+     * Get formatted log message for message key and values.
+     * Use the root locale
+     *
+     * @param key       message key in resource bundle
+     * @param arguments message values
+     * @return formatted message
+     */
+    public static String formatLog(String key, Object... arguments) {
+        return format(ROOT_LOCALE, key, arguments);
+    }
+
+    /**
+     * Get formatted error message for bundle message and exception for logging.
+     * Use the root locale
+     *
+     * @param message bundle message
+     * @param e       exception
+     * @return formatted message
+     */
+    public static String getExceptionLogMessage(BundleMessage message, Exception e) {
+        return getExceptionMessage(message, e, ROOT_LOCALE);
+    }
+
+    /**
+     * Get formatted error message for bundle message and exception with default locale.
+     *
+     * @param message bundle message
+     * @param e       exception
+     * @return formatted message
+     */
+    public static String getExceptionMessage(BundleMessage message, Exception e) {
+        return getExceptionMessage(message, e, null);
+    }
+
+    /**
+     * Get formatted error message for bundle message and exception with given locale.
+     *
+     * @param message bundle message
+     * @param e       exception
+     * @param locale  bundle locale
+     * @return formatted message
+     */
+    public static String getExceptionMessage(BundleMessage message, Exception e, Locale locale) {
+        StringBuilder buffer = new StringBuilder();
+        if (message != null) {
+            buffer.append(message.format(locale)).append(' ');
+        }
+        if (e instanceof DavMailException) {
+            buffer.append(((DavMailException) e).getMessage(locale));
+        } else if (e.getMessage() != null) {
+            buffer.append(e.getMessage());
+        } else {
+            buffer.append(e.toString());
+        }
+        return buffer.toString();
+    }
+
+    /**
+     * Typed bundle message collection
+     */
+    public static class BundleMessageList extends ArrayList<BundleMessage> {
+    }
+}
diff --git a/src/java/davmail/DavGateway.java b/src/java/davmail/DavGateway.java
new file mode 100644
index 0000000..008b356
--- /dev/null
+++ b/src/java/davmail/DavGateway.java
@@ -0,0 +1,246 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail;
+
+import davmail.caldav.CaldavServer;
+import davmail.exception.DavMailException;
+import davmail.exchange.ExchangeSessionFactory;
+import davmail.http.DavGatewayHttpClientFacade;
+import davmail.http.DavGatewaySSLProtocolSocketFactory;
+import davmail.imap.ImapServer;
+import davmail.ldap.LdapServer;
+import davmail.pop.PopServer;
+import davmail.smtp.SmtpServer;
+import davmail.ui.tray.DavGatewayTray;
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.log4j.Logger;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+
+/**
+ * DavGateway main class
+ */
+public final class DavGateway {
+    private static final Logger LOGGER = Logger.getLogger(DavGateway.class);
+    private static final String HTTP_DAVMAIL_SOURCEFORGE_NET_VERSION_TXT = "http://davmail.sourceforge.net/version.txt";
+
+    private static boolean stopped;
+
+    private DavGateway() {
+    }
+
+    private static final ArrayList<AbstractServer> SERVER_LIST = new ArrayList<AbstractServer>();
+
+    /**
+     * Start the gateway, listen on specified smtp and pop3 ports
+     *
+     * @param args command line parameter config file path
+     */
+    public static void main(String[] args) {
+
+        if (args.length >= 1) {
+            Settings.setConfigFilePath(args[0]);
+        }
+
+        Settings.load();
+        DavGatewayTray.init();
+
+        start();
+
+        // server mode: all threads are daemon threads, do not let main stop
+        if (Settings.getBooleanProperty("davmail.server")) {
+            Runtime.getRuntime().addShutdownHook(new Thread("Shutdown") {
+                @Override
+                public void run() {
+                    DavGatewayTray.debug(new BundleMessage("LOG_GATEWAY_INTERRUPTED"));
+                    DavGateway.stop();
+                    stopped = true;
+                }
+            });
+
+            try {
+                while (!stopped) {
+                    Thread.sleep(1000);
+                }
+            } catch (InterruptedException e) {
+                DavGatewayTray.debug(new BundleMessage("LOG_GATEWAY_INTERRUPTED"));
+                stop();
+                DavGatewayTray.debug(new BundleMessage("LOG_GATEWAY_STOP"));
+            }
+
+        }
+    }
+
+    /**
+     * Start DavMail listeners.
+     */
+    public static void start() {
+        // register custom SSL Socket factory
+        DavGatewaySSLProtocolSocketFactory.register();
+
+        // prepare HTTP connection pool
+        DavGatewayHttpClientFacade.start();
+
+        SERVER_LIST.clear();
+
+        int smtpPort = Settings.getIntProperty("davmail.smtpPort");
+        if (smtpPort != 0) {
+            SERVER_LIST.add(new SmtpServer(smtpPort));
+        }
+        int popPort = Settings.getIntProperty("davmail.popPort");
+        if (popPort != 0) {
+            SERVER_LIST.add(new PopServer(popPort));
+        }
+        int imapPort = Settings.getIntProperty("davmail.imapPort");
+        if (imapPort != 0) {
+            SERVER_LIST.add(new ImapServer(imapPort));
+        }
+        int caldavPort = Settings.getIntProperty("davmail.caldavPort");
+        if (caldavPort != 0) {
+            SERVER_LIST.add(new CaldavServer(caldavPort));
+        }
+        int ldapPort = Settings.getIntProperty("davmail.ldapPort");
+        if (ldapPort != 0) {
+            SERVER_LIST.add(new LdapServer(ldapPort));
+        }
+
+        BundleMessage.BundleMessageList messages = new BundleMessage.BundleMessageList();
+        BundleMessage.BundleMessageList errorMessages = new BundleMessage.BundleMessageList();
+        for (AbstractServer server : SERVER_LIST) {
+            try {
+                server.bind();
+                server.start();
+                messages.add(new BundleMessage("LOG_PROTOCOL_PORT", server.getProtocolName(), server.getPort()));
+            } catch (DavMailException e) {
+                errorMessages.add(e.getBundleMessage());
+            } catch (IOException e) {
+                errorMessages.add(new BundleMessage("LOG_SOCKET_BIND_FAILED", server.getProtocolName(), server.getPort()));
+            }
+        }
+
+        final String currentVersion = getCurrentVersion();
+        boolean showStartupBanner = Settings.getBooleanProperty("davmail.showStartupBanner", true);
+        if (showStartupBanner) {
+            DavGatewayTray.info(new BundleMessage("LOG_DAVMAIL_GATEWAY_LISTENING",
+                    currentVersion == null ? "" : currentVersion, messages));
+        }
+        if (!errorMessages.isEmpty()) {
+            DavGatewayTray.error(new BundleMessage("LOG_MESSAGE", errorMessages));
+        }
+
+        // check for new version in a separate thread
+        new Thread("CheckRelease") {
+            @Override
+            public void run() {
+                String releasedVersion = getReleasedVersion();
+                if (currentVersion != null && currentVersion.length() > 0 && releasedVersion != null && currentVersion.compareTo(releasedVersion) < 0) {
+                    DavGatewayTray.info(new BundleMessage("LOG_NEW_VERSION_AVAILABLE", releasedVersion));
+                }
+
+            }
+        }.start();
+
+    }
+
+    /**
+     * Stop all listeners, shutdown connection pool and clear session cache.
+     */
+    public static void stop() {
+        DavGateway.stopServers();
+        // close pooled connections
+        DavGatewayHttpClientFacade.stop();
+        // clear session cache
+        ExchangeSessionFactory.reset();
+        DavGatewayTray.info(new BundleMessage("LOG_GATEWAY_STOP"));
+    }
+
+    /**
+     * Stop all listeners and clear session cache.
+     */
+    public static void restart() {
+        DavGateway.stopServers();
+        // clear session cache
+        ExchangeSessionFactory.reset();
+        DavGateway.start();
+    }
+
+    private static void stopServers() {
+        for (AbstractServer server : SERVER_LIST) {
+            server.close();
+            try {
+                server.join();
+            } catch (InterruptedException e) {
+                DavGatewayTray.warn(new BundleMessage("LOG_EXCEPTION_WAITING_SERVER_THREAD_DIE"), e);
+            }
+        }
+    }
+
+    /**
+     * Get current DavMail version.
+     *
+     * @return current version
+     */
+    public static String getCurrentVersion() {
+        Package davmailPackage = DavGateway.class.getPackage();
+        String currentVersion = davmailPackage.getImplementationVersion();
+        if (currentVersion == null) {
+            currentVersion = "";
+        }
+        return currentVersion;
+    }
+
+    /**
+     * Get latest released version from SourceForge.
+     *
+     * @return latest version
+     */
+    public static String getReleasedVersion() {
+        String version = null;
+        if (!Settings.getBooleanProperty("davmail.disableUpdateCheck")) {
+            BufferedReader versionReader = null;
+            GetMethod getMethod = new GetMethod(HTTP_DAVMAIL_SOURCEFORGE_NET_VERSION_TXT);
+            try {
+                HttpClient httpClient = DavGatewayHttpClientFacade.getInstance(HTTP_DAVMAIL_SOURCEFORGE_NET_VERSION_TXT);
+                int status = httpClient.executeMethod(getMethod);
+                if (status == HttpStatus.SC_OK) {
+                    versionReader = new BufferedReader(new InputStreamReader(getMethod.getResponseBodyAsStream()));
+                    version = versionReader.readLine();
+                    LOGGER.debug("DavMail released version: " + version);
+                }
+            } catch (IOException e) {
+                DavGatewayTray.debug(new BundleMessage("LOG_UNABLE_TO_GET_RELEASED_VERSION"));
+            } finally {
+                if (versionReader != null) {
+                    try {
+                        versionReader.close();
+                    } catch (IOException e) {
+                        // ignore
+                    }
+                }
+                getMethod.releaseConnection();
+            }
+        }
+        return version;
+    }
+}
diff --git a/src/java/davmail/Settings.java b/src/java/davmail/Settings.java
new file mode 100644
index 0000000..7794709
--- /dev/null
+++ b/src/java/davmail/Settings.java
@@ -0,0 +1,493 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail;
+
+import davmail.ui.tray.DavGatewayTray;
+import org.apache.log4j.*;
+
+import java.io.*;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Properties;
+import java.util.TreeSet;
+
+/**
+ * Settings facade.
+ * DavMail settings are stored in the .davmail.properties file in current
+ * user home directory or in the file specified on the command line.
+ */
+public final class Settings {
+    private Settings() {
+    }
+
+    private static final Properties SETTINGS = new Properties() {
+        @Override
+        public synchronized Enumeration<Object> keys() {
+            Enumeration keysEnumeration = super.keys();
+            TreeSet<String> sortedKeySet = new TreeSet<String>();
+            while (keysEnumeration.hasMoreElements()) {
+                sortedKeySet.add((String) keysEnumeration.nextElement());
+            }
+            final Iterator<String> sortedKeysIterator = sortedKeySet.iterator();
+            return new Enumeration<Object>() {
+
+                public boolean hasMoreElements() {
+                    return sortedKeysIterator.hasNext();
+                }
+
+                public Object nextElement() {
+                    return sortedKeysIterator.next();
+                }
+            };
+        }
+
+    };
+    private static String configFilePath;
+    private static boolean isFirstStart;
+
+    /**
+     * Set config file path (from command line parameter).
+     *
+     * @param path davmail properties file path
+     */
+    public static synchronized void setConfigFilePath(String path) {
+        configFilePath = path;
+    }
+
+    /**
+     * Detect first launch (properties file does not exist).
+     *
+     * @return true if this is the first start with the current file path
+     */
+    public static synchronized boolean isFirstStart() {
+        return isFirstStart;
+    }
+
+    /**
+     * Load properties from provided stream (used in webapp mode).
+     *
+     * @param inputStream properties stream
+     * @throws IOException on error
+     */
+    public static synchronized void load(InputStream inputStream) throws IOException {
+        SETTINGS.load(inputStream);
+        updateLoggingConfig();
+    }
+
+    /**
+     * Load properties from current file path (command line or default).
+     */
+    public static synchronized void load() {
+        FileInputStream fileInputStream = null;
+        try {
+            if (configFilePath == null) {
+                //noinspection AccessOfSystemProperties
+                configFilePath = System.getProperty("user.home") + "/.davmail.properties";
+            }
+            File configFile = new File(configFilePath);
+            if (configFile.exists()) {
+                fileInputStream = new FileInputStream(configFile);
+                load(fileInputStream);
+            } else {
+                isFirstStart = true;
+
+                // first start : set default values, ports above 1024 for unix/linux
+                setDefaultSettings();
+                save();
+            }
+        } catch (IOException e) {
+            DavGatewayTray.error(new BundleMessage("LOG_UNABLE_TO_LOAD_SETTINGS"), e);
+        } finally {
+            if (fileInputStream != null) {
+                try {
+                    fileInputStream.close();
+                } catch (IOException e) {
+                    DavGatewayTray.debug(new BundleMessage("LOG_ERROR_CLOSING_CONFIG_FILE"), e);
+                }
+            }
+        }
+        updateLoggingConfig();
+    }
+
+    /**
+     * Set all settings to default values.
+     * Ports above 1024 for unix/linux
+     */
+    public static void setDefaultSettings() {
+        SETTINGS.put("davmail.url", "https://exchangeServer/exchange/");
+        SETTINGS.put("davmail.popPort", "1110");
+        SETTINGS.put("davmail.imapPort", "1143");
+        SETTINGS.put("davmail.smtpPort", "1025");
+        SETTINGS.put("davmail.caldavPort", "1080");
+        SETTINGS.put("davmail.ldapPort", "1389");
+        SETTINGS.put("davmail.clientSoTimeout", "");
+        SETTINGS.put("davmail.keepDelay", "30");
+        SETTINGS.put("davmail.sentKeepDelay", "90");
+        SETTINGS.put("davmail.caldavPastDelay", "90");
+        SETTINGS.put("davmail.imapIdleDelay", "");
+        SETTINGS.put("davmail.allowRemote", Boolean.FALSE.toString());
+        SETTINGS.put("davmail.bindAddress", "");
+        SETTINGS.put("davmail.useSystemProxies", Boolean.FALSE.toString());
+        SETTINGS.put("davmail.enableProxy", Boolean.FALSE.toString());
+        SETTINGS.put("davmail.enableEws", "auto");
+        SETTINGS.put("davmail.proxyHost", "");
+        SETTINGS.put("davmail.proxyPort", "");
+        SETTINGS.put("davmail.proxyUser", "");
+        SETTINGS.put("davmail.proxyPassword", "");
+        SETTINGS.put("davmail.server", Boolean.FALSE.toString());
+        SETTINGS.put("davmail.server.certificate.hash", "");
+        SETTINGS.put("davmail.caldavAlarmSound", "");
+        SETTINGS.put("davmail.forceActiveSyncUpdate", Boolean.FALSE.toString());
+        SETTINGS.put("davmail.showStartupBanner", Boolean.TRUE.toString());
+        SETTINGS.put("davmail.disableGuiNotifications", Boolean.FALSE.toString());
+        SETTINGS.put("davmail.imapAutoExpunge", Boolean.TRUE.toString());
+        SETTINGS.put("davmail.popMarkReadOnRetr", Boolean.FALSE.toString());
+        SETTINGS.put("davmail.smtpSaveInSent", Boolean.TRUE.toString());
+        SETTINGS.put("davmail.ssl.keystoreType", "");
+        SETTINGS.put("davmail.ssl.keystoreFile", "");
+        SETTINGS.put("davmail.ssl.keystorePass", "");
+        SETTINGS.put("davmail.ssl.keyPass", "");
+        SETTINGS.put("davmail.ssl.clientKeystoreType", "");
+        SETTINGS.put("davmail.ssl.clientKeystoreFile", "");
+        SETTINGS.put("davmail.ssl.clientKeystorePass", "");
+        SETTINGS.put("davmail.ssl.pkcs11Library", "");
+        SETTINGS.put("davmail.ssl.pkcs11Config", "");
+        SETTINGS.put("davmail.ssl.nosecurepop", Boolean.FALSE.toString());
+        SETTINGS.put("davmail.ssl.nosecureimap", Boolean.FALSE.toString());
+        SETTINGS.put("davmail.ssl.nosecuresmtp", Boolean.FALSE.toString());
+        SETTINGS.put("davmail.ssl.nosecurecaldav", Boolean.FALSE.toString());
+        SETTINGS.put("davmail.ssl.nosecureldap", Boolean.FALSE.toString());
+
+        // logging
+        SETTINGS.put("log4j.rootLogger", Level.WARN.toString());
+        SETTINGS.put("log4j.logger.davmail", Level.DEBUG.toString());
+        SETTINGS.put("log4j.logger.httpclient.wire", Level.WARN.toString());
+        SETTINGS.put("log4j.logger.org.apache.commons.httpclient", Level.WARN.toString());
+        SETTINGS.put("davmail.logFilePath", "");
+    }
+
+    /**
+     * Return DavMail log file path
+     *
+     * @return full log file path
+     */
+    public static String getLogFilePath() {
+        String logFilePath = Settings.getProperty("davmail.logFilePath");
+        // set default log file path
+        if ((logFilePath == null || logFilePath.length() == 0)) {
+            if (System.getProperty("os.name").toLowerCase().startsWith("mac os x")) {
+                // store davmail.log in OSX Logs directory
+                logFilePath = System.getProperty("user.home") + "/Library/Logs/DavMail/davmail.log";
+            } else {
+                // store davmail.log in user home folder
+                logFilePath = System.getProperty("user.home") + "/davmail.log";
+            }
+        } else {
+            File logFile = new File(logFilePath);
+            if (logFile.isDirectory()) {
+                logFilePath += "/davmail.log";
+            }
+        }
+        return logFilePath;
+    }
+
+    /**
+     * Return DavMail log file directory
+     *
+     * @return full log file directory
+     */
+    public static String getLogFileDirectory() {
+        String logFilePath = getLogFilePath();
+        if (logFilePath == null || logFilePath.length() == 0) {
+            return ".";
+        }
+        int lastSlashIndex = logFilePath.lastIndexOf('/');
+        if (lastSlashIndex == -1) {
+            lastSlashIndex = logFilePath.lastIndexOf('\\');
+        }
+        if (lastSlashIndex >= 0) {
+            return logFilePath.substring(0, lastSlashIndex);
+        } else {
+            return ".";
+        }
+    }
+
+    /**
+     * Update Log4J config from settings.
+     */
+    private static void updateLoggingConfig() {
+        String logFilePath = getLogFilePath();
+
+        Logger rootLogger = Logger.getRootLogger();
+        try {
+            if (logFilePath != null && logFilePath.length() > 0) {
+                File logFile = new File(logFilePath);
+                // create parent directory if needed
+                File logFileDir = logFile.getParentFile();
+                if (logFileDir != null && !logFileDir.exists()) {
+                    if (!logFileDir.mkdirs()) {
+                        DavGatewayTray.error(new BundleMessage("LOG_UNABLE_TO_CREATE_LOG_FILE_DIR"));
+                        throw new IOException();
+                    }
+                }
+            } else {
+                logFilePath = "davmail.log";
+            }
+            // Build file appender
+            RollingFileAppender fileAppender = ((RollingFileAppender) rootLogger.getAppender("FileAppender"));
+            if (fileAppender == null) {
+                String logFileSize = Settings.getProperty("davmail.logFileSize");
+                if (logFileSize == null || logFileSize.length() == 0) {
+                    logFileSize = "1MB";
+                }
+                fileAppender = new RollingFileAppender();
+                fileAppender.setName("FileAppender");
+                fileAppender.setMaxBackupIndex(2);
+                fileAppender.setMaxFileSize(logFileSize);
+                fileAppender.setEncoding("UTF-8");
+                fileAppender.setLayout(new PatternLayout("%d{ISO8601} %-5p [%t] %c %x - %m%n"));
+            }
+            fileAppender.setFile(logFilePath, true, false, 8192);
+            rootLogger.addAppender(fileAppender);
+
+            // disable ConsoleAppender in gui mode
+            if (!Settings.getBooleanProperty("davmail.server")) {
+                ConsoleAppender consoleAppender = (ConsoleAppender) rootLogger.getAppender("ConsoleAppender");
+                if (consoleAppender != null) {
+                    consoleAppender.setThreshold(Level.OFF);
+                }
+            }
+
+        } catch (IOException e) {
+            DavGatewayTray.error(new BundleMessage("LOG_UNABLE_TO_SET_LOG_FILE_PATH"));
+        }
+
+        // update logging levels
+        Settings.setLoggingLevel("rootLogger", Settings.getLoggingLevel("rootLogger"));
+        Settings.setLoggingLevel("davmail", Settings.getLoggingLevel("davmail"));
+        Settings.setLoggingLevel("httpclient.wire", Settings.getLoggingLevel("httpclient.wire"));
+        Settings.setLoggingLevel("org.apache.commons.httpclient", Settings.getLoggingLevel("org.apache.commons.httpclient"));
+    }
+
+    /**
+     * Save settings in current file path (command line or default).
+     */
+    public static synchronized void save() {
+        // configFilePath is null in some test cases
+        if (configFilePath != null) {
+            FileOutputStream fileOutputStream = null;
+            try {
+                fileOutputStream = new FileOutputStream(configFilePath);
+                SETTINGS.store(fileOutputStream, "DavMail settings");
+            } catch (IOException e) {
+                DavGatewayTray.error(new BundleMessage("LOG_UNABLE_TO_STORE_SETTINGS"), e);
+            } finally {
+                if (fileOutputStream != null) {
+                    try {
+                        fileOutputStream.close();
+                    } catch (IOException e) {
+                        DavGatewayTray.debug(new BundleMessage("LOG_ERROR_CLOSING_CONFIG_FILE"), e);
+                    }
+                }
+            }
+        }
+        updateLoggingConfig();
+    }
+
+    /**
+     * Get a property value as String.
+     *
+     * @param property property name
+     * @return property value
+     */
+    public static synchronized String getProperty(String property) {
+        String value = SETTINGS.getProperty(property);
+        // return null on empty value
+        if (value != null && value.length() == 0) {
+            value = null;
+        }
+        return value;
+    }
+
+    /**
+     * Get property value or default.
+     *
+     * @param property     property name
+     * @param defaultValue default property value
+     * @return property value
+     */
+    public static synchronized String getProperty(String property, String defaultValue) {
+        String value = SETTINGS.getProperty(property);
+        if (value == null) {
+            value = defaultValue;
+        }
+        return value;
+    }
+
+
+    /**
+     * Get a property value as char[].
+     *
+     * @param property property name
+     * @return property value
+     */
+    public static synchronized char[] getCharArrayProperty(String property) {
+        String propertyValue = Settings.getProperty(property);
+        char[] value = null;
+        if (propertyValue != null) {
+            value = propertyValue.toCharArray();
+        }
+        return value;
+    }
+
+    /**
+     * Set a property value.
+     *
+     * @param property property name
+     * @param value    property value
+     */
+    public static synchronized void setProperty(String property, String value) {
+        if (value != null) {
+            SETTINGS.setProperty(property, value);
+        } else {
+            SETTINGS.setProperty(property, "");
+        }
+    }
+
+    /**
+     * Get a property value as int.
+     *
+     * @param property property name
+     * @return property value
+     */
+    public static synchronized int getIntProperty(String property) {
+        return getIntProperty(property, 0);
+    }
+
+    /**
+     * Get a property value as int, return default value if null.
+     *
+     * @param property     property name
+     * @param defaultValue default property value
+     * @return property value
+     */
+    public static synchronized int getIntProperty(String property, int defaultValue) {
+        int value = defaultValue;
+        try {
+            String propertyValue = SETTINGS.getProperty(property);
+            if (propertyValue != null && propertyValue.length() > 0) {
+                value = Integer.parseInt(propertyValue);
+            }
+        } catch (NumberFormatException e) {
+            DavGatewayTray.error(new BundleMessage("LOG_INVALID_SETTING_VALUE", property), e);
+        }
+        return value;
+    }
+
+    /**
+     * Get a property value as boolean.
+     *
+     * @param property property name
+     * @return property value
+     */
+    public static synchronized boolean getBooleanProperty(String property) {
+        String propertyValue = SETTINGS.getProperty(property);
+        return Boolean.parseBoolean(propertyValue);
+    }
+
+    /**
+     * Get a property value as boolean.
+     *
+     * @param property     property name
+     * @param defaultValue default property value
+     * @return property value
+     */
+    public static synchronized boolean getBooleanProperty(String property, boolean defaultValue) {
+        boolean value = defaultValue;
+        String propertyValue = SETTINGS.getProperty(property);
+        if (propertyValue != null && propertyValue.length() > 0) {
+            value = Boolean.parseBoolean(propertyValue);
+        }
+        return value;
+    }
+
+    /**
+     * Build logging properties prefix.
+     *
+     * @param category logging category
+     * @return prefix
+     */
+    private static String getLoggingPrefix(String category) {
+        String prefix;
+        if ("rootLogger".equals(category)) {
+            prefix = "log4j.";
+        } else {
+            prefix = "log4j.logger.";
+        }
+        return prefix;
+    }
+
+    /**
+     * Return Log4J logging level for the category.
+     *
+     * @param category logging category
+     * @return logging level
+     */
+    public static synchronized Level getLoggingLevel(String category) {
+        String prefix = getLoggingPrefix(category);
+        String currentValue = SETTINGS.getProperty(prefix + category);
+
+        if (currentValue != null && currentValue.length() > 0) {
+            return Level.toLevel(currentValue);
+        } else if ("rootLogger".equals(category)) {
+            return Logger.getRootLogger().getLevel();
+        } else {
+            return Logger.getLogger(category).getLevel();
+        }
+    }
+
+    /**
+     * Set Log4J logging level for the category
+     *
+     * @param category logging category
+     * @param level    logging level
+     */
+    public static synchronized void setLoggingLevel(String category, Level level) {
+        String prefix = getLoggingPrefix(category);
+        SETTINGS.setProperty(prefix + category, level.toString());
+        if ("rootLogger".equals(category)) {
+            Logger.getRootLogger().setLevel(level);
+        } else {
+            Logger.getLogger(category).setLevel(level);
+        }
+    }
+
+    /**
+     * Change and save a single property.
+     *
+     * @param property property name
+     * @param value    property value
+     */
+    public static synchronized void saveProperty(String property, String value) {
+        Settings.load();
+        Settings.setProperty(property, value);
+        Settings.save();
+    }
+
+}
diff --git a/src/java/davmail/caldav/CaldavConnection.java b/src/java/davmail/caldav/CaldavConnection.java
new file mode 100644
index 0000000..ec2f1d9
--- /dev/null
+++ b/src/java/davmail/caldav/CaldavConnection.java
@@ -0,0 +1,1806 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.caldav;
+
+import davmail.AbstractConnection;
+import davmail.BundleMessage;
+import davmail.DavGateway;
+import davmail.Settings;
+import davmail.exception.DavMailAuthenticationException;
+import davmail.exception.DavMailException;
+import davmail.exception.HttpNotFoundException;
+import davmail.exception.HttpPreconditionFailedException;
+import davmail.exchange.ExchangeSession;
+import davmail.exchange.ExchangeSessionFactory;
+import davmail.exchange.ICSBufferedReader;
+import davmail.exchange.XMLStreamUtil;
+import davmail.ui.tray.DavGatewayTray;
+import davmail.util.StringUtil;
+import org.apache.commons.httpclient.HttpException;
+import org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.httpclient.URI;
+import org.apache.commons.httpclient.URIException;
+import org.apache.commons.httpclient.util.URIUtil;
+import org.apache.log4j.Logger;
+
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+import java.io.*;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.SocketTimeoutException;
+import java.net.URL;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * Handle a caldav connection.
+ */
+public class CaldavConnection extends AbstractConnection {
+    /**
+     * Maximum keep alive time in seconds
+     */
+    protected static final int MAX_KEEP_ALIVE_TIME = 300;
+    protected final Logger wireLogger = Logger.getLogger(this.getClass());
+
+    protected boolean closed;
+
+    /**
+     * custom url encode path set for iCal 5
+     */
+    public static final BitSet ical_allowed_abs_path = new BitSet(256);
+    static {
+        ical_allowed_abs_path.or(URI.allowed_abs_path);
+        ical_allowed_abs_path.clear('@');
+    }
+
+    static String encodePath(CaldavRequest request, String path) throws URIException {
+        if (request.isIcal5()) {
+            return URIUtil.encode(path, ical_allowed_abs_path, "UTF-8");
+        } else {
+            return URIUtil.encodePath(path, "UTF-8");
+        }
+    }
+
+    /**
+     * Initialize the streams and start the thread.
+     *
+     * @param clientSocket Caldav client socket
+     */
+    public CaldavConnection(Socket clientSocket) {
+        super(CaldavConnection.class.getSimpleName(), clientSocket, "UTF-8");
+        // set caldav logging to davmail logging level
+        wireLogger.setLevel(Settings.getLoggingLevel("davmail"));
+    }
+
+    protected Map<String, String> parseHeaders() throws IOException {
+        HashMap<String, String> headers = new HashMap<String, String>();
+        String line;
+        while ((line = readClient()) != null && line.length() > 0) {
+            int index = line.indexOf(':');
+            if (index <= 0) {
+                wireLogger.warn("Invalid header: "+line);
+                throw new DavMailException("EXCEPTION_INVALID_HEADER");
+            }
+            headers.put(line.substring(0, index).toLowerCase(), line.substring(index + 1).trim());
+        }
+        return headers;
+    }
+
+    protected String getContent(String contentLength) throws IOException {
+        if (contentLength == null || contentLength.length() == 0) {
+            return null;
+        } else {
+            int size;
+            try {
+                size = Integer.parseInt(contentLength);
+            } catch (NumberFormatException e) {
+                throw new DavMailException("EXCEPTION_INVALID_CONTENT_LENGTH", contentLength);
+            }
+            String content = in.readContentAsString(size);
+            if (wireLogger.isDebugEnabled()) {
+                wireLogger.debug("< " + content);
+            }
+            return content;
+        }
+    }
+
+    protected void setSocketTimeout(String keepAliveValue) throws IOException {
+        if (keepAliveValue != null && keepAliveValue.length() > 0) {
+            int keepAlive;
+            try {
+                keepAlive = Integer.parseInt(keepAliveValue);
+            } catch (NumberFormatException e) {
+                throw new DavMailException("EXCEPTION_INVALID_KEEPALIVE", keepAliveValue);
+            }
+            if (keepAlive > MAX_KEEP_ALIVE_TIME) {
+                keepAlive = MAX_KEEP_ALIVE_TIME;
+            }
+            client.setSoTimeout(keepAlive * 1000);
+            DavGatewayTray.debug(new BundleMessage("LOG_SET_SOCKET_TIMEOUT", keepAlive));
+        }
+    }
+
+    @Override
+    public void run() {
+        String line;
+        StringTokenizer tokens;
+
+        try {
+            while (!closed) {
+                line = readClient();
+                // unable to read line, connection closed ?
+                if (line == null) {
+                    break;
+                }
+                tokens = new StringTokenizer(line);
+                String command = tokens.nextToken();
+                Map<String, String> headers = parseHeaders();
+                String encodedPath = StringUtil.encodePlusSign(tokens.nextToken());
+                String path = URIUtil.decode(encodedPath);
+                String content = getContent(headers.get("content-length"));
+                setSocketTimeout(headers.get("keep-alive"));
+                // client requested connection close
+                closed = "close".equals(headers.get("connection"));
+                if ("OPTIONS".equals(command)) {
+                    sendOptions();
+                } else if (!headers.containsKey("authorization")) {
+                    sendUnauthorized();
+                } else {
+                    decodeCredentials(headers.get("authorization"));
+                    // need to check session on each request, credentials may have changed or session expired
+                    try {
+                        session = ExchangeSessionFactory.getInstance(userName, password);
+                        handleRequest(command, path, headers, content);
+                    } catch (DavMailAuthenticationException e) {
+                        sendUnauthorized();
+                    }
+                }
+
+                os.flush();
+                DavGatewayTray.resetIcon();
+            }
+        } catch (SocketTimeoutException e) {
+            DavGatewayTray.debug(new BundleMessage("LOG_CLOSE_CONNECTION_ON_TIMEOUT"));
+        } catch (SocketException e) {
+            DavGatewayTray.debug(new BundleMessage("LOG_CONNECTION_CLOSED"));
+        } catch (Exception e) {
+            if (!(e instanceof HttpNotFoundException)) {
+                DavGatewayTray.log(e);
+            }
+            try {
+                sendErr(e);
+            } catch (IOException e2) {
+                DavGatewayTray.debug(new BundleMessage("LOG_EXCEPTION_SENDING_ERROR_TO_CLIENT"), e2);
+            }
+        } finally {
+            close();
+        }
+        DavGatewayTray.resetIcon();
+    }
+
+    /**
+     * Handle caldav request.
+     *
+     * @param command Http command
+     * @param path    request path
+     * @param headers Http headers map
+     * @param body    request body
+     * @throws IOException on error
+     */
+    public void handleRequest(String command, String path, Map<String, String> headers, String body) throws IOException {
+        CaldavRequest request = new CaldavRequest(command, path, headers, body);
+        if (request.isOptions()) {
+            sendOptions();
+        } else if (request.isPropFind() && request.isRoot()) {
+            sendRoot(request);
+        } else if (request.isGet() && request.isRoot()) {
+            sendGetRoot();
+        } else if (request.isPath(1, "principals")) {
+            handlePrincipals(request);
+        } else if (request.isPath(1, "users")) {
+            if (request.isPropFind() && request.isPathLength(3)) {
+                sendUserRoot(request);
+            } else {
+                handleFolderOrItem(request);
+            }
+        } else if (request.isPath(1, "public")) {
+            handleFolderOrItem(request);
+        } else if (request.isPath(1, "directory")) {
+            sendDirectory(request);
+        } else if (request.isPath(1, ".well-known")) {
+            sendWellKnown();
+        } else {
+            sendUnsupported(request);
+        }
+    }
+
+    protected void handlePrincipals(CaldavRequest request) throws IOException {
+        if (request.isPath(2, "users")) {
+            if (request.isPropFind() && request.isPathLength(4)) {
+                sendPrincipal(request, "users", URIUtil.decode(request.getPathElement(3)));
+                // send back principal on search
+            } else if (request.isReport() && request.isPathLength(3)) {
+                sendPrincipal(request, "users", session.getEmail());
+                // iCal current-user-principal request
+            } else if (request.isPropFind() && request.isPathLength(3)) {
+                sendPrincipalsFolder(request);
+            } else {
+                sendUnsupported(request);
+            }
+        } else if (request.isPath(2, "public")) {
+            StringBuilder prefixBuffer = new StringBuilder("public");
+            for (int i = 3; i < request.getPathLength() - 1; i++) {
+                prefixBuffer.append('/').append(request.getPathElement(i));
+            }
+            sendPrincipal(request, URIUtil.decode(prefixBuffer.toString()), URIUtil.decode(request.getLastPath()));
+        } else {
+            sendUnsupported(request);
+        }
+    }
+
+    protected void handleFolderOrItem(CaldavRequest request) throws IOException {
+        String lastPath = StringUtil.xmlDecode(request.getLastPath());
+        // folder requests
+        if (request.isPropFind() && "inbox".equals(lastPath)) {
+            sendInbox(request);
+        } else if (request.isPropFind() && "outbox".equals(lastPath)) {
+            sendOutbox(request);
+        } else if (request.isPost() && "outbox".equals(lastPath)) {
+            if (request.isFreeBusy()) {
+                sendFreeBusy(request.getBody());
+            } else {
+                int status = session.sendEvent(request.getBody());
+                // TODO: implement Itip response body
+                sendHttpResponse(status);
+            }
+        } else if (request.isPropFind()) {
+            sendFolderOrItem(request);
+        } else if (request.isPropPatch()) {
+            patchCalendar(request);
+        } else if (request.isReport()) {
+            reportItems(request);
+            // event requests
+        } else if (request.isPut()) {
+            String etag = request.getHeader("if-match");
+            String noneMatch = request.getHeader("if-none-match");
+            ExchangeSession.ItemResult itemResult = session.createOrUpdateItem(request.getFolderPath(), lastPath, request.getBody(), etag, noneMatch);
+            sendHttpResponse(itemResult.status, buildEtagHeader(itemResult.etag), null, "", true);
+
+        } else if (request.isDelete()) {
+            if (request.getFolderPath().endsWith("inbox")) {
+                session.processItem(request.getFolderPath(), lastPath);
+            } else {
+                session.deleteItem(request.getFolderPath(), lastPath);
+            }
+            sendHttpResponse(HttpStatus.SC_OK);
+        } else if (request.isGet()) {
+            if (request.path.endsWith("/")) {
+                // GET request on a folder => build ics content of all folder events
+                String folderPath = request.getFolderPath();
+                ExchangeSession.Folder folder = session.getFolder(folderPath);
+                if (folder.isContact()) {
+                    List<ExchangeSession.Contact> contacts = session.getAllContacts(folderPath);
+                    ChunkedResponse response = new ChunkedResponse(HttpStatus.SC_OK, "text/vcard;charset=UTF-8");
+
+                    for (ExchangeSession.Contact contact : contacts) {
+                        String contactBody = contact.getBody();
+                        if (contactBody != null) {
+                            response.append(contactBody);
+                            response.append("\n");
+                        }
+                    }
+                    response.close();
+
+                } else if (folder.isCalendar() || folder.isTask()) {
+                    List<ExchangeSession.Event> events = session.getAllEvents(folderPath);
+                    ChunkedResponse response = new ChunkedResponse(HttpStatus.SC_OK, "text/calendar;charset=UTF-8");
+                    response.append("BEGIN:VCALENDAR\r\n");
+                    response.append("VERSION:2.0\r\n");
+                    response.append("PRODID:-//davmail.sf.net/NONSGML DavMail Calendar V1.1//EN\r\n");
+                    response.append("METHOD:PUBLISH\r\n");
+
+                    for (ExchangeSession.Event event : events) {
+                        String icsContent = StringUtil.getToken(event.getBody(), "BEGIN:VTIMEZONE", "END:VCALENDAR");
+                        if (icsContent != null) {
+                            response.append("BEGIN:VTIMEZONE");
+                            response.append(icsContent);
+                        } else {
+                            icsContent = StringUtil.getToken(event.getBody(), "BEGIN:VEVENT", "END:VCALENDAR");
+                            if (icsContent != null) {
+                                response.append("BEGIN:VEVENT");
+                                response.append(icsContent);
+                            }
+                        }
+                    }
+                    response.append("END:VCALENDAR");
+                    response.close();
+                } else {
+                    sendHttpResponse(HttpStatus.SC_OK, buildEtagHeader(folder.etag), "text/html", (byte[]) null, true);
+                }
+            } else {
+                ExchangeSession.Item item = session.getItem(request.getFolderPath(), lastPath);
+                sendHttpResponse(HttpStatus.SC_OK, buildEtagHeader(item.getEtag()), item.getContentType(), item.getBody(), true);
+            }
+        } else if (request.isHead()) {
+            // test event
+            ExchangeSession.Item item = session.getItem(request.getFolderPath(), lastPath);
+            sendHttpResponse(HttpStatus.SC_OK, buildEtagHeader(item.getEtag()), item.getContentType(), (byte[]) null, true);
+        } else if (request.isMkCalendar()) {
+            HashMap<String, String> properties = new HashMap<String, String>();
+            //properties.put("displayname", request.getProperty("displayname"));
+            int status = session.createCalendarFolder(request.getFolderPath(), properties);
+            sendHttpResponse(status, null);
+        } else if (request.isMove()) {
+            String destinationUrl = request.getHeader("destination");
+            session.moveItem(request.path, URIUtil.decode(new URL(destinationUrl).getPath()));
+            sendHttpResponse(HttpStatus.SC_CREATED, null);
+        } else {
+            sendUnsupported(request);
+        }
+
+    }
+
+    protected HashMap<String, String> buildEtagHeader(String etag) {
+        if (etag != null) {
+            HashMap<String, String> etagHeader = new HashMap<String, String>();
+            etagHeader.put("ETag", etag);
+            return etagHeader;
+        } else {
+            return null;
+        }
+    }
+
+    private void appendContactsResponses(CaldavResponse response, CaldavRequest request, List<ExchangeSession.Contact> contacts) throws IOException {
+        int size = contacts.size();
+        int count = 0;
+        for (ExchangeSession.Contact contact : contacts) {
+            DavGatewayTray.debug(new BundleMessage("LOG_LISTING_ITEM", ++count, size));
+            DavGatewayTray.switchIcon();
+            appendItemResponse(response, request, contact);
+        }
+    }
+
+    protected void appendEventsResponses(CaldavResponse response, CaldavRequest request, List<ExchangeSession.Event> events) throws IOException {
+        int size = events.size();
+        int count = 0;
+        for (ExchangeSession.Event event : events) {
+            DavGatewayTray.debug(new BundleMessage("LOG_LISTING_ITEM", ++count, size));
+            DavGatewayTray.switchIcon();
+            appendItemResponse(response, request, event);
+        }
+    }
+
+    protected void appendItemResponse(CaldavResponse response, CaldavRequest request, ExchangeSession.Item item) throws IOException {
+        StringBuilder eventPath = new StringBuilder();
+        eventPath.append(encodePath(request, request.getPath()));
+        if (!(eventPath.charAt(eventPath.length() - 1) == '/')) {
+            eventPath.append('/');
+        }
+        String itemName = StringUtil.xmlEncode(item.getName());
+        eventPath.append(URIUtil.encodeWithinQuery(itemName));
+        response.startResponse(eventPath.toString());
+        response.startPropstat();
+        if (request.hasProperty("calendar-data") && item instanceof ExchangeSession.Event) {
+            response.appendCalendarData(item.getBody());
+        }
+        if (request.hasProperty("address-data") && item instanceof ExchangeSession.Contact) {
+            response.appendContactData(item.getBody());
+        }
+        if (request.hasProperty("getcontenttype")) {
+            if (item instanceof ExchangeSession.Event) {
+                response.appendProperty("D:getcontenttype", "text/calendar; component=vevent");
+            } else if (item instanceof ExchangeSession.Contact) {
+                response.appendProperty("D:getcontenttype", "text/vcard");
+            }
+        }
+        if (request.hasProperty("getetag")) {
+            response.appendProperty("D:getetag", item.getEtag());
+        }
+        if (request.hasProperty("resourcetype")) {
+            response.appendProperty("D:resourcetype");
+        }
+        if (request.hasProperty("displayname")) {
+            response.appendProperty("D:displayname", itemName);
+        }
+        response.endPropStatOK();
+        response.endResponse();
+    }
+
+    /**
+     * Append folder object to Caldav response.
+     *
+     * @param response  Caldav response
+     * @param request   Caldav request
+     * @param folder    folder object
+     * @param subFolder calendar folder path relative to request path
+     * @throws IOException on error
+     */
+    public void appendFolderOrItem(CaldavResponse response, CaldavRequest request, ExchangeSession.Folder folder, String subFolder) throws IOException {
+        response.startResponse(encodePath(request, request.getPath(subFolder)));
+        response.startPropstat();
+
+        if (request.hasProperty("resourcetype")) {
+            if (folder.isContact()) {
+                response.appendProperty("D:resourcetype", "<D:collection/>" +
+                        "<E:addressbook/>");
+            } else if (folder.isCalendar() || folder.isTask()) {
+                response.appendProperty("D:resourcetype", "<D:collection/>" + "<C:calendar/>");
+            } else {
+                response.appendProperty("D:resourcetype", "<D:collection/>");
+            }
+
+        }
+        if (request.hasProperty("owner")) {
+            if ("users".equals(request.getPathElement(1))) {
+                response.appendHrefProperty("D:owner", "/principals/users/" + request.getPathElement(2));
+            } else {
+                response.appendHrefProperty("D:owner", "/principals" + request.getPath());
+            }
+        }
+        if (request.hasProperty("getcontenttype")) {
+            if (folder.isContact()) {
+                response.appendProperty("D:getcontenttype", "text/x-vcard");
+            } else if (folder.isCalendar()) {
+                response.appendProperty("D:getcontenttype", "text/calendar; component=vevent");
+            } else if (folder.isTask()) {
+                response.appendProperty("D:getcontenttype", "text/calendar; component=vtodo");
+            }
+        }
+        if (request.hasProperty("getetag")) {
+            response.appendProperty("D:getetag", folder.etag);
+        }
+        if (request.hasProperty("getctag")) {
+            response.appendProperty("CS:getctag", "CS=\"http://calendarserver.org/ns/\"",
+                    base64Encode(folder.ctag));
+        }
+        if (request.hasProperty("displayname")) {
+            if (subFolder == null || subFolder.length() == 0) {
+                // use i18n calendar name as display name
+                String displayname = request.getLastPath();
+                if ("calendar".equals(displayname)) {
+                    displayname = folder.displayName;
+                }
+                response.appendProperty("D:displayname", displayname);
+            } else {
+                response.appendProperty("D:displayname", subFolder);
+            }
+        }
+        if (request.hasProperty("calendar-description")) {
+            response.appendProperty("C:calendar-description", "");
+        }
+        if (request.hasProperty("supported-calendar-component-set")) {
+            if (folder.isCalendar()) {
+                response.appendProperty("C:supported-calendar-component-set", "<C:comp name=\"VEVENT\"/><C:comp name=\"VTODO\"/>");
+            } else if (folder.isTask()) {
+                response.appendProperty("C:supported-calendar-component-set", "<C:comp name=\"VTODO\"/>");
+            }
+        }
+
+        if (request.hasProperty("current-user-privilege-set")) {
+            response.appendProperty("D:current-user-privilege-set", "<D:privilege><D:read/><D:write/></D:privilege>");
+        }
+
+        response.endPropStatOK();
+        response.endResponse();
+    }
+
+    /**
+     * Append calendar inbox object to Caldav response.
+     *
+     * @param response  Caldav response
+     * @param request   Caldav request
+     * @param subFolder inbox folder path relative to request path
+     * @throws IOException on error
+     */
+    public void appendInbox(CaldavResponse response, CaldavRequest request, String subFolder) throws IOException {
+        String ctag = "0";
+        String etag = "0";
+        String folderPath = request.getFolderPath(subFolder);
+        // do not try to access inbox on shared calendar
+        if (!session.isSharedFolder(folderPath)) {
+            try {
+                ExchangeSession.Folder folder = session.getFolder(folderPath);
+                ctag = base64Encode(folder.ctag);
+                etag = base64Encode(folder.etag);
+            } catch (HttpException e) {
+                // unauthorized access, probably an inbox on shared calendar
+                DavGatewayTray.debug(new BundleMessage("LOG_ACCESS_FORBIDDEN", folderPath, e.getMessage()));
+            }
+        }
+        response.startResponse(encodePath(request, request.getPath(subFolder)));
+        response.startPropstat();
+
+        if (request.hasProperty("resourcetype")) {
+            response.appendProperty("D:resourcetype", "<D:collection/>" +
+                    "<C:schedule-inbox xmlns:C=\"urn:ietf:params:xml:ns:caldav\"/>");
+        }
+        if (request.hasProperty("getcontenttype")) {
+            response.appendProperty("D:getcontenttype", "text/calendar; component=vevent");
+        }
+        if (request.hasProperty("getctag")) {
+            response.appendProperty("CS:getctag", "CS=\"http://calendarserver.org/ns/\"", ctag);
+        }
+        if (request.hasProperty("getetag")) {
+            response.appendProperty("D:getetag", etag);
+        }
+        if (request.hasProperty("displayname")) {
+            response.appendProperty("D:displayname", "inbox");
+        }
+        response.endPropStatOK();
+        response.endResponse();
+    }
+
+    /**
+     * Append calendar outbox object to Caldav response.
+     *
+     * @param response  Caldav response
+     * @param request   Caldav request
+     * @param subFolder outbox folder path relative to request path
+     * @throws IOException on error
+     */
+    public void appendOutbox(CaldavResponse response, CaldavRequest request, String subFolder) throws IOException {
+        response.startResponse(encodePath(request, request.getPath(subFolder)));
+        response.startPropstat();
+
+        if (request.hasProperty("resourcetype")) {
+            response.appendProperty("D:resourcetype", "<D:collection/>" +
+                    "<C:schedule-outbox xmlns:C=\"urn:ietf:params:xml:ns:caldav\"/>");
+        }
+        if (request.hasProperty("getctag")) {
+            response.appendProperty("CS:getctag", "CS=\"http://calendarserver.org/ns/\"",
+                    "0");
+        }
+        if (request.hasProperty("getetag")) {
+            response.appendProperty("D:getetag", "0");
+        }
+        if (request.hasProperty("displayname")) {
+            response.appendProperty("D:displayname", "outbox");
+        }
+        response.endPropStatOK();
+        response.endResponse();
+    }
+
+    /**
+     * Send simple html response to GET /.
+     *
+     * @throws IOException on error
+     */
+    public void sendGetRoot() throws IOException {
+        StringBuilder buffer = new StringBuilder();
+        buffer.append("Connected to DavMail<br/>");
+        buffer.append("UserName :").append(userName).append("<br/>");
+        buffer.append("Email :").append(session.getEmail()).append("<br/>");
+        sendHttpResponse(HttpStatus.SC_OK, null, "text/html;charset=UTF-8", buffer.toString(), true);
+    }
+
+    /**
+     * Send inbox response for request.
+     *
+     * @param request Caldav request
+     * @throws IOException on error
+     */
+    public void sendInbox(CaldavRequest request) throws IOException {
+        CaldavResponse response = new CaldavResponse(HttpStatus.SC_MULTI_STATUS);
+        response.startMultistatus();
+        appendInbox(response, request, null);
+        // do not try to access inbox on shared calendar
+        if (!session.isSharedFolder(request.getFolderPath(null)) && request.getDepth() == 1
+                && !request.isLightning()) {
+            try {
+                DavGatewayTray.debug(new BundleMessage("LOG_SEARCHING_CALENDAR_MESSAGES"));
+                List<ExchangeSession.Event> events = session.getEventMessages(request.getFolderPath());
+                DavGatewayTray.debug(new BundleMessage("LOG_FOUND_CALENDAR_MESSAGES", events.size()));
+                appendEventsResponses(response, request, events);
+            } catch (HttpException e) {
+                // unauthorized access, probably an inbox on shared calendar
+                DavGatewayTray.debug(new BundleMessage("LOG_ACCESS_FORBIDDEN", request.getFolderPath(), e.getMessage()));
+            }
+        }
+        response.endMultistatus();
+        response.close();
+    }
+
+    /**
+     * Send outbox response for request.
+     *
+     * @param request Caldav request
+     * @throws IOException on error
+     */
+    public void sendOutbox(CaldavRequest request) throws IOException {
+        CaldavResponse response = new CaldavResponse(HttpStatus.SC_MULTI_STATUS);
+        response.startMultistatus();
+        appendOutbox(response, request, null);
+        response.endMultistatus();
+        response.close();
+    }
+
+    /**
+     * Send calendar response for request.
+     *
+     * @param request Caldav request
+     * @throws IOException on error
+     */
+    public void sendFolderOrItem(CaldavRequest request) throws IOException {
+        String folderPath = request.getFolderPath();
+        // process request before sending response to avoid sending headers twice on error
+        ExchangeSession.Folder folder = session.getFolder(folderPath);
+        List<ExchangeSession.Contact> contacts = null;
+        List<ExchangeSession.Event> events = null;
+        List<ExchangeSession.Folder> folderList = null;
+        if (request.getDepth() == 1) {
+            if (folder.isContact()) {
+                contacts = session.getAllContacts(folderPath);
+            } else if (folder.isCalendar() || folder.isTask()) {
+                events = session.getAllEvents(folderPath);
+                if (!folderPath.startsWith("/public")) {
+                    folderList = session.getSubCalendarFolders(folderPath, false);
+                }
+            }
+        }
+
+        CaldavResponse response = new CaldavResponse(HttpStatus.SC_MULTI_STATUS);
+        response.startMultistatus();
+        appendFolderOrItem(response, request, folder, null);
+        if (request.getDepth() == 1) {
+            if (folder.isContact()) {
+                appendContactsResponses(response, request, contacts);
+            } else if (folder.isCalendar() || folder.isTask()) {
+                appendEventsResponses(response, request, events);
+                // Send sub folders for multi-calendar support under iCal, except for public folders
+                if (folderList != null) {
+                    for (ExchangeSession.Folder subFolder : folderList) {
+                        appendFolderOrItem(response, request, subFolder, subFolder.folderPath.substring(subFolder.folderPath.indexOf('/') + 1));
+                    }
+                }
+            }
+        }
+        response.endMultistatus();
+        response.close();
+    }
+
+    /**
+     * Fake PROPPATCH response for request.
+     *
+     * @param request Caldav request
+     * @throws IOException on error
+     */
+    public void patchCalendar(CaldavRequest request) throws IOException {
+        String displayname = request.getProperty("displayname");
+        String folderPath = request.getFolderPath();
+        if (displayname != null) {
+            String targetPath = request.getParentFolderPath() + '/' + displayname;
+            if (!targetPath.equals(folderPath)) {
+                session.moveFolder(folderPath, targetPath);
+            }
+        }
+        CaldavResponse response = new CaldavResponse(HttpStatus.SC_MULTI_STATUS);
+        response.startMultistatus();
+        // ical calendar folder proppatch
+        if (request.hasProperty("calendar-color") || request.hasProperty("calendar-order")) {
+            response.startPropstat();
+            if (request.hasProperty("calendar-color")) {
+                response.appendProperty("x1:calendar-color", "x1=\"http://apple.com/ns/ical/\"", null);
+            }
+            if (request.hasProperty("calendar-order")) {
+                response.appendProperty("x1:calendar-order", "x1=\"http://apple.com/ns/ical/\"", null);
+            }
+            response.endPropStatOK();
+        }
+        response.endMultistatus();
+        response.close();
+    }
+
+    protected String getEventFileNameFromPath(String path) {
+        int index = path.lastIndexOf('/');
+        if (index < 0) {
+            return null;
+        } else {
+            return StringUtil.xmlDecode(path.substring(index + 1));
+        }
+    }
+
+    /**
+     * Report items listed in request.
+     *
+     * @param request Caldav request
+     * @throws IOException on error
+     */
+    public void reportItems(CaldavRequest request) throws IOException {
+        String folderPath = request.getFolderPath();
+        List<ExchangeSession.Event> events;
+        List<String> notFound = new ArrayList<String>();
+
+        CaldavResponse response = new CaldavResponse(HttpStatus.SC_MULTI_STATUS);
+        response.startMultistatus();
+        if (request.isMultiGet()) {
+            int count = 0;
+            int total = request.getHrefs().size();
+            for (String href : request.getHrefs()) {
+                DavGatewayTray.debug(new BundleMessage("LOG_REPORT_ITEM", ++count, total));
+                DavGatewayTray.switchIcon();
+                String eventName = getEventFileNameFromPath(href);
+                try {
+                    // ignore cases for Sunbird
+                    if (eventName != null && eventName.length() > 0
+                            && !"inbox".equals(eventName) && !"calendar".equals(eventName)) {
+                        ExchangeSession.Item item;
+                        try {
+                            item = session.getItem(folderPath, eventName);
+                        } catch (HttpNotFoundException e) {
+                            // workaround for Lightning bug
+                            if (request.isBrokenLightning() && eventName.indexOf('%') >= 0) {
+                                item = session.getItem(folderPath, URIUtil.decode(StringUtil.encodePlusSign(eventName)));
+                            } else {
+                                throw e;
+                            }
+
+                        }
+                        appendItemResponse(response, request, item);
+                    }
+                } catch (SocketException e) {
+                    // rethrow SocketException (client closed connection)
+                    throw e;
+                } catch (Exception e) {
+                    wireLogger.debug(e.getMessage(), e);
+                    DavGatewayTray.warn(new BundleMessage("LOG_ITEM_NOT_AVAILABLE", eventName, href));
+                    notFound.add(href);
+                }
+            }
+        } else if (request.isPath(1, "users") && request.isPath(3, "inbox")) {
+            events = session.getEventMessages(request.getFolderPath());
+            appendEventsResponses(response, request, events);
+        } else {
+            // TODO: handle contacts ?
+            if (request.vTodoOnly) {
+                events = session.searchTasksOnly(request.getFolderPath());
+            } else if (request.vEventOnly) {
+                events = session.searchEventsOnly(request.getFolderPath(), request.timeRangeStart, request.timeRangeEnd);
+            } else {
+                events = session.searchEvents(request.getFolderPath(), request.timeRangeStart, request.timeRangeEnd);
+            }
+            appendEventsResponses(response, request, events);
+        }
+
+        // send not found events errors
+        for (String href : notFound) {
+            response.startResponse(encodePath(request, href));
+            response.appendPropstatNotFound();
+            response.endResponse();
+        }
+        response.endMultistatus();
+        response.close();
+    }
+
+    /**
+     * Send principals folder.
+     *
+     * @param request Caldav request
+     * @throws IOException on error
+     */
+    public void sendPrincipalsFolder(CaldavRequest request) throws IOException {
+        CaldavResponse response = new CaldavResponse(HttpStatus.SC_MULTI_STATUS);
+        response.startMultistatus();
+        response.startResponse(encodePath(request, request.getPath()));
+        response.startPropstat();
+
+        if (request.hasProperty("current-user-principal")) {
+            response.appendHrefProperty("D:current-user-principal", encodePath(request, "/principals/users/" + session.getEmail()));
+        }
+        response.endPropStatOK();
+        response.endResponse();
+        response.endMultistatus();
+        response.close();
+    }
+
+    /**
+     * Send user response for request.
+     *
+     * @param request Caldav request
+     * @throws IOException on error
+     */
+    public void sendUserRoot(CaldavRequest request) throws IOException {
+        CaldavResponse response = new CaldavResponse(HttpStatus.SC_MULTI_STATUS);
+        response.startMultistatus();
+        response.startResponse(encodePath(request, request.getPath()));
+        response.startPropstat();
+
+        if (request.hasProperty("resourcetype")) {
+            response.appendProperty("D:resourcetype", "<D:collection/>");
+        }
+        if (request.hasProperty("displayname")) {
+            response.appendProperty("D:displayname", request.getLastPath());
+        }
+        if (request.hasProperty("getctag")) {
+            ExchangeSession.Folder rootFolder = session.getFolder("");
+            response.appendProperty("CS:getctag", "CS=\"http://calendarserver.org/ns/\"",
+                    base64Encode(rootFolder.ctag));
+        }
+        response.endPropStatOK();
+        if (request.getDepth() == 1) {
+            appendInbox(response, request, "inbox");
+            appendOutbox(response, request, "outbox");
+            appendFolderOrItem(response, request, session.getFolder(request.getFolderPath("calendar")), "calendar");
+            appendFolderOrItem(response, request, session.getFolder(request.getFolderPath("contacts")), "contacts");
+        }
+        response.endResponse();
+        response.endMultistatus();
+        response.close();
+    }
+
+    /**
+     * Send caldav response for / request.
+     *
+     * @param request Caldav request
+     * @throws IOException on error
+     */
+    public void sendRoot(CaldavRequest request) throws IOException {
+        CaldavResponse response = new CaldavResponse(HttpStatus.SC_MULTI_STATUS);
+        response.startMultistatus();
+        response.startResponse("/");
+        response.startPropstat();
+
+        if (request.hasProperty("principal-collection-set")) {
+            response.appendHrefProperty("D:principal-collection-set", "/principals/users/");
+        }
+        if (request.hasProperty("displayname")) {
+            response.appendProperty("D:displayname", "ROOT");
+        }
+        if (request.hasProperty("resourcetype")) {
+            response.appendProperty("D:resourcetype", "<D:collection/>");
+        }
+        if (request.hasProperty("current-user-principal")) {
+            response.appendHrefProperty("D:current-user-principal", encodePath(request, "/principals/users/" + session.getEmail()));
+        }
+        response.endPropStatOK();
+        response.endResponse();
+        if (request.depth == 1) {
+            // iPhone workaround: send calendar subfolder
+            response.startResponse("/users/" + session.getEmail() + "/calendar");
+            response.startPropstat();
+            if (request.hasProperty("resourcetype")) {
+                response.appendProperty("D:resourcetype", "<D:collection/>" +
+                        "<C:calendar xmlns:C=\"urn:ietf:params:xml:ns:caldav\"/>");
+            }
+            if (request.hasProperty("displayname")) {
+                response.appendProperty("D:displayname", session.getEmail());
+            }
+            if (request.hasProperty("supported-calendar-component-set")) {
+                response.appendProperty("C:supported-calendar-component-set", "<C:comp name=\"VEVENT\"/>");
+            }
+            response.endPropStatOK();
+            response.endResponse();
+
+            response.startResponse("/users");
+            response.startPropstat();
+            if (request.hasProperty("displayname")) {
+                response.appendProperty("D:displayname", "users");
+            }
+            if (request.hasProperty("resourcetype")) {
+                response.appendProperty("D:resourcetype", "<D:collection/>");
+            }
+            response.endPropStatOK();
+            response.endResponse();
+
+            response.startResponse("/principals");
+            response.startPropstat();
+            if (request.hasProperty("displayname")) {
+                response.appendProperty("D:displayname", "principals");
+            }
+            if (request.hasProperty("resourcetype")) {
+                response.appendProperty("D:resourcetype", "<D:collection/>");
+            }
+            response.endPropStatOK();
+            response.endResponse();
+        }
+        response.endMultistatus();
+        response.close();
+    }
+
+    /**
+     * Send caldav response for /directory/ request.
+     *
+     * @param request Caldav request
+     * @throws IOException on error
+     */
+    public void sendDirectory(CaldavRequest request) throws IOException {
+        CaldavResponse response = new CaldavResponse(HttpStatus.SC_MULTI_STATUS);
+        response.startMultistatus();
+        response.startResponse("/directory/");
+        response.startPropstat();
+        if (request.hasProperty("current-user-privilege-set")) {
+            response.appendProperty("D:current-user-privilege-set", "<D:privilege><D:read/></D:privilege>");
+        }
+        response.endPropStatOK();
+        response.endResponse();
+        response.endMultistatus();
+        response.close();
+    }
+
+    /**
+     * Send caldav response for /.well-known/ request.
+     *
+     * @throws IOException on error
+     */
+    public void sendWellKnown() throws IOException {
+        HashMap<String, String> headers = new HashMap<String, String>();
+        headers.put("Location", "/");
+        sendHttpResponse(HttpStatus.SC_MOVED_PERMANENTLY, headers);
+    }
+
+    /**
+     * Send Caldav principal response.
+     *
+     * @param request   Caldav request
+     * @param prefix    principal prefix (users or public)
+     * @param principal principal name (email address for users)
+     * @throws IOException on error
+     */
+    public void sendPrincipal(CaldavRequest request, String prefix, String principal) throws IOException {
+        // actual principal is email address
+        String actualPrincipal = principal;
+        if ("users".equals(prefix) &&
+                (principal.equalsIgnoreCase(session.getAlias()) || (principal.equalsIgnoreCase(session.getAliasFromLogin())))) {
+            actualPrincipal = session.getEmail();
+        }
+
+        CaldavResponse response = new CaldavResponse(HttpStatus.SC_MULTI_STATUS);
+        response.startMultistatus();
+        response.startResponse(encodePath(request, "/principals/" + prefix + '/' + principal));
+        response.startPropstat();
+
+        if (request.hasProperty("principal-URL") && request.isIcal5()) {
+            response.appendHrefProperty("D:principal-URL", encodePath(request, "/principals/" + prefix + '/' + actualPrincipal));
+        }
+        
+
+        if (request.hasProperty("calendar-home-set")) {
+            if ("users".equals(prefix)) {
+                response.appendHrefProperty("C:calendar-home-set", encodePath(request, "/users/" + actualPrincipal + "/calendar/"));
+            } else {
+                response.appendHrefProperty("C:calendar-home-set", encodePath(request, '/' + prefix + '/' + actualPrincipal));
+            }
+        }
+
+        if (request.hasProperty("calendar-user-address-set") && "users".equals(prefix)) {
+            response.appendHrefProperty("C:calendar-user-address-set", "mailto:" + actualPrincipal);
+        }
+
+        if (request.hasProperty("addressbook-home-set")) {
+            if (request.isUserAgent("Address%20Book") || request.isUserAgent("Darwin")) {
+                response.appendHrefProperty("E:addressbook-home-set", encodePath(request, '/' + prefix + '/' + actualPrincipal + '/'));
+            } else if ("users".equals(prefix)) {
+                response.appendHrefProperty("E:addressbook-home-set", encodePath(request, "/users/" + actualPrincipal + "/contacts/"));
+            } else {
+                response.appendHrefProperty("E:addressbook-home-set", encodePath(request, '/' + prefix + '/' + actualPrincipal + '/'));
+            }
+        }
+
+        if ("users".equals(prefix)) {
+            if (request.hasProperty("schedule-inbox-URL")) {
+                response.appendHrefProperty("C:schedule-inbox-URL", encodePath(request, "/users/" + actualPrincipal + "/inbox/"));
+            }
+
+            if (request.hasProperty("schedule-outbox-URL")) {
+                response.appendHrefProperty("C:schedule-outbox-URL", encodePath(request, "/users/" + actualPrincipal + "/outbox/"));
+            }
+        } else {
+            // public calendar, send root href as inbox url (always empty) for Lightning
+            if (request.isLightning() && request.hasProperty("schedule-inbox-URL")) {
+                response.appendHrefProperty("C:schedule-inbox-URL", "/");
+            }
+            // send user outbox
+            if (request.hasProperty("schedule-outbox-URL")) {
+                response.appendHrefProperty("C:schedule-outbox-URL", encodePath(request, "/users/" + session.getEmail() + "/outbox/"));
+            }
+        }
+
+        if (request.hasProperty("displayname")) {
+            response.appendProperty("D:displayname", actualPrincipal);
+        }
+        if (request.hasProperty("resourcetype")) {
+            response.appendProperty("D:resourcetype", "<D:collection/><D:principal/>");
+        }
+        if (request.hasProperty("supported-report-set")) {
+            response.appendProperty("D:supported-report-set", "<D:supported-report><D:report><C:calendar-multiget/></D:report></D:supported-report>");
+        }
+        response.endPropStatOK();
+        response.endResponse();
+        response.endMultistatus();
+        response.close();
+    }
+
+    /**
+     * Send free busy response for body request.
+     *
+     * @param body request body
+     * @throws IOException on error
+     */
+    public void sendFreeBusy(String body) throws IOException {
+        HashMap<String, String> valueMap = new HashMap<String, String>();
+        ArrayList<String> attendees = new ArrayList<String>();
+        HashMap<String, String> attendeeKeyMap = new HashMap<String, String>();
+        ICSBufferedReader reader = new ICSBufferedReader(new StringReader(body));
+        String line;
+        String key;
+        while ((line = reader.readLine()) != null) {
+            int index = line.indexOf(':');
+            if (index <= 0) {
+                throw new DavMailException("EXCEPTION_INVALID_REQUEST", body);
+            }
+            String fullkey = line.substring(0, index);
+            String value = line.substring(index + 1);
+            int semicolonIndex = fullkey.indexOf(';');
+            if (semicolonIndex > 0) {
+                key = fullkey.substring(0, semicolonIndex);
+            } else {
+                key = fullkey;
+            }
+            if ("ATTENDEE".equals(key)) {
+                attendees.add(value);
+                attendeeKeyMap.put(value, fullkey);
+            } else {
+                valueMap.put(key, value);
+            }
+        }
+        // get freebusy for each attendee
+        HashMap<String, ExchangeSession.FreeBusy> freeBusyMap = new HashMap<String, ExchangeSession.FreeBusy>();
+        for (String attendee : attendees) {
+            ExchangeSession.FreeBusy freeBusy = session.getFreebusy(attendee, valueMap.get("DTSTART"), valueMap.get("DTEND"));
+            if (freeBusy != null) {
+                freeBusyMap.put(attendee, freeBusy);
+            }
+        }
+        CaldavResponse response = new CaldavResponse(HttpStatus.SC_OK);
+        response.startScheduleResponse();
+
+        for (Map.Entry<String, ExchangeSession.FreeBusy> entry : freeBusyMap.entrySet()) {
+            String attendee = entry.getKey();
+            response.startRecipientResponse(attendee);
+
+            StringBuilder ics = new StringBuilder();
+            ics.append("BEGIN:VCALENDAR").append((char) 13).append((char) 10)
+                    .append("VERSION:2.0").append((char) 13).append((char) 10)
+                    .append("PRODID:-//davmail.sf.net/NONSGML DavMail Calendar V1.1//EN").append((char) 13).append((char) 10)
+                    .append("METHOD:REPLY").append((char) 13).append((char) 10)
+                    .append("BEGIN:VFREEBUSY").append((char) 13).append((char) 10)
+                    .append("DTSTAMP:").append(valueMap.get("DTSTAMP")).append("").append((char) 13).append((char) 10)
+                    .append("ORGANIZER:").append(valueMap.get("ORGANIZER")).append("").append((char) 13).append((char) 10)
+                    .append("DTSTART:").append(valueMap.get("DTSTART")).append("").append((char) 13).append((char) 10)
+                    .append("DTEND:").append(valueMap.get("DTEND")).append("").append((char) 13).append((char) 10)
+                    .append("UID:").append(valueMap.get("UID")).append("").append((char) 13).append((char) 10)
+                    .append(attendeeKeyMap.get(attendee)).append(':').append(attendee).append("").append((char) 13).append((char) 10);
+            entry.getValue().appendTo(ics);
+            ics.append("END:VFREEBUSY").append((char) 13).append((char) 10)
+                    .append("END:VCALENDAR");
+            response.appendCalendarData(ics.toString());
+            response.endRecipientResponse();
+
+        }
+        response.endScheduleResponse();
+        response.close();
+
+    }
+
+
+    /**
+     * Send Http error response for exception
+     *
+     * @param e exception
+     * @throws IOException on error
+     */
+    public void sendErr(Exception e) throws IOException {
+        String message = e.getMessage();
+        if (message == null) {
+            message = e.toString();
+        }
+        if (e instanceof HttpNotFoundException) {
+            sendErr(HttpStatus.SC_NOT_FOUND, message);
+        } else if (e instanceof HttpPreconditionFailedException) {
+            sendErr(HttpStatus.SC_PRECONDITION_FAILED, message);
+        } else {
+            sendErr(HttpStatus.SC_SERVICE_UNAVAILABLE, message);
+        }
+    }
+
+    /**
+     * Send 400 bad response for unsupported request.
+     *
+     * @param request Caldav request
+     * @throws IOException on error
+     */
+    public void sendUnsupported(CaldavRequest request) throws IOException {
+        BundleMessage message = new BundleMessage("LOG_UNSUPPORTED_REQUEST", request);
+        DavGatewayTray.error(message);
+        sendErr(HttpStatus.SC_BAD_REQUEST, message.format());
+    }
+
+    /**
+     * Send Http error status and message.
+     *
+     * @param status  Http status
+     * @param message error messagee
+     * @throws IOException on error
+     */
+    public void sendErr(int status, String message) throws IOException {
+        sendHttpResponse(status, null, "text/plain;charset=UTF-8", message, false);
+    }
+
+    /**
+     * Send OPTIONS response.
+     *
+     * @throws IOException on error
+     */
+    public void sendOptions() throws IOException {
+        HashMap<String, String> headers = new HashMap<String, String>();
+        headers.put("Allow", "OPTIONS, PROPFIND, HEAD, GET, REPORT, PROPPATCH, PUT, DELETE, POST");
+        sendHttpResponse(HttpStatus.SC_OK, headers);
+    }
+
+    /**
+     * Send 401 Unauthorized response.
+     *
+     * @throws IOException on error
+     */
+    public void sendUnauthorized() throws IOException {
+        HashMap<String, String> headers = new HashMap<String, String>();
+        headers.put("WWW-Authenticate", "Basic realm=\"" + BundleMessage.format("UI_DAVMAIL_GATEWAY") + '\"');
+        sendHttpResponse(HttpStatus.SC_UNAUTHORIZED, headers, null, (byte[]) null, true);
+    }
+
+    /**
+     * Send Http response with given status.
+     *
+     * @param status Http status
+     * @throws IOException on error
+     */
+    public void sendHttpResponse(int status) throws IOException {
+        sendHttpResponse(status, null, null, (byte[]) null, true);
+    }
+
+    /**
+     * Send Http response with given status and headers.
+     *
+     * @param status  Http status
+     * @param headers Http headers
+     * @throws IOException on error
+     */
+    public void sendHttpResponse(int status, Map<String, String> headers) throws IOException {
+        sendHttpResponse(status, headers, null, (byte[]) null, true);
+    }
+
+    /**
+     * Send Http response with given status in chunked mode.
+     *
+     * @param status      Http status
+     * @param contentType MIME content type
+     * @throws IOException on error
+     */
+    public void sendChunkedHttpResponse(int status, String contentType) throws IOException {
+        HashMap<String, String> headers = new HashMap<String, String>();
+        headers.put("Transfer-Encoding", "chunked");
+        sendHttpResponse(status, headers, contentType, (byte[]) null, true);
+    }
+
+    /**
+     * Send Http response with given status, headers, content type and content.
+     * Close connection if keepAlive is false
+     *
+     * @param status      Http status
+     * @param headers     Http headers
+     * @param contentType MIME content type
+     * @param content     response body as string
+     * @param keepAlive   keep connection open
+     * @throws IOException on error
+     */
+    public void sendHttpResponse(int status, Map<String, String> headers, String contentType, String content, boolean keepAlive) throws IOException {
+        sendHttpResponse(status, headers, contentType, content.getBytes("UTF-8"), keepAlive);
+    }
+
+    /**
+     * Send Http response with given status, headers, content type and content.
+     * Close connection if keepAlive is false
+     *
+     * @param status      Http status
+     * @param headers     Http headers
+     * @param contentType MIME content type
+     * @param content     response body as byte array
+     * @param keepAlive   keep connection open
+     * @throws IOException on error
+     */
+    public void sendHttpResponse(int status, Map<String, String> headers, String contentType, byte[] content, boolean keepAlive) throws IOException {
+        sendClient("HTTP/1.1 " + status + ' ' + HttpStatus.getStatusText(status));
+        if (status != HttpStatus.SC_UNAUTHORIZED) {
+            sendClient("Server: DavMail Gateway " + DavGateway.getCurrentVersion());
+            sendClient("DAV: 1, calendar-access, calendar-schedule, calendarserver-private-events, addressbook");
+            SimpleDateFormat formatter = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.ENGLISH);
+            // force GMT timezone
+            formatter.setTimeZone(ExchangeSession.GMT_TIMEZONE);
+            String now = formatter.format(new Date());
+            sendClient("Date: " + now);
+            sendClient("Expires: " + now);
+            sendClient("Cache-Control: private, max-age=0");
+        }
+        if (headers != null) {
+            for (Map.Entry<String, String> header : headers.entrySet()) {
+                sendClient(header.getKey() + ": " + header.getValue());
+            }
+        }
+        if (contentType != null) {
+            sendClient("Content-Type: " + contentType);
+        }
+        closed = closed || !keepAlive;
+        sendClient("Connection: " + (closed ? "close" : "keep-alive"));
+        if (content != null && content.length > 0) {
+            sendClient("Content-Length: " + content.length);
+        } else if (headers == null || !"chunked".equals(headers.get("Transfer-Encoding"))) {
+            sendClient("Content-Length: 0");
+        }
+        sendClient("");
+        if (content != null && content.length > 0) {
+            // full debug trace
+            if (wireLogger.isDebugEnabled()) {
+                wireLogger.debug("> " + new String(content, "UTF-8"));
+            }
+            sendClient(content);
+        }
+    }
+
+    /**
+     * Decode HTTP credentials
+     *
+     * @param authorization http authorization header value
+     * @throws IOException if invalid credentials
+     */
+    protected void decodeCredentials(String authorization) throws IOException {
+        int index = authorization.indexOf(' ');
+        if (index > 0) {
+            String mode = authorization.substring(0, index).toLowerCase();
+            if (!"basic".equals(mode)) {
+                throw new DavMailException("EXCEPTION_UNSUPPORTED_AUTHORIZATION_MODE", mode);
+            }
+            String encodedCredentials = authorization.substring(index + 1);
+            String decodedCredentials = base64Decode(encodedCredentials);
+            index = decodedCredentials.indexOf(':');
+            if (index > 0) {
+                userName = decodedCredentials.substring(0, index);
+                password = decodedCredentials.substring(index + 1);
+            } else {
+                throw new DavMailException("EXCEPTION_INVALID_CREDENTIALS");
+            }
+        } else {
+            throw new DavMailException("EXCEPTION_INVALID_CREDENTIALS");
+        }
+
+    }
+
+    protected static class CaldavRequest {
+        protected final String command;
+        protected final String path;
+        protected final String[] pathElements;
+        protected final Map<String, String> headers;
+        protected int depth;
+        protected final String body;
+        protected final HashMap<String, String> properties = new HashMap<String, String>();
+        protected HashSet<String> hrefs;
+        protected boolean isMultiGet;
+        protected String timeRangeStart;
+        protected String timeRangeEnd;
+        protected boolean vTodoOnly;
+        protected boolean vEventOnly;
+
+        protected CaldavRequest(String command, String path, Map<String, String> headers, String body) throws IOException {
+            this.command = command;
+            this.path = path.replaceAll("//", "/");
+            pathElements = this.path.split("/");
+            this.headers = headers;
+            buildDepth();
+            this.body = body;
+
+            if (isPropFind() || isReport() || isMkCalendar() || isPropPatch()) {
+                parseXmlBody();
+            }
+        }
+
+        public boolean isOptions() {
+            return "OPTIONS".equals(command);
+        }
+
+        public boolean isPropFind() {
+            return "PROPFIND".equals(command);
+        }
+
+        public boolean isPropPatch() {
+            return "PROPPATCH".equals(command);
+        }
+
+        public boolean isReport() {
+            return "REPORT".equals(command);
+        }
+
+        public boolean isGet() {
+            return "GET".equals(command);
+        }
+
+        public boolean isHead() {
+            return "HEAD".equals(command);
+        }
+
+        public boolean isPut() {
+            return "PUT".equals(command);
+        }
+
+        public boolean isPost() {
+            return "POST".equals(command);
+        }
+
+        public boolean isDelete() {
+            return "DELETE".equals(command);
+        }
+
+        public boolean isMkCalendar() {
+            return "MKCALENDAR".equals(command);
+        }
+
+        public boolean isMove() {
+            return "MOVE".equals(command);
+        }
+
+        /**
+         * Check if this request is a folder request.
+         *
+         * @return true if this is a folder (not event) request
+         */
+        public boolean isFolder() {
+            return path.endsWith("/") || isPropFind() || isReport() || isPropPatch() || isOptions() || isPost();
+        }
+
+        public boolean isRoot() {
+            return (pathElements.length == 0 || pathElements.length == 1);
+        }
+
+        public boolean isPathLength(int length) {
+            return pathElements.length == length;
+        }
+
+        public int getPathLength() {
+            return pathElements.length;
+        }
+
+        public String getPath() {
+            return path;
+        }
+
+        public String getPath(String subFolder) {
+            String folderPath;
+            if (subFolder == null || subFolder.length() == 0) {
+                folderPath = path;
+            } else if (path.endsWith("/")) {
+                folderPath = path + subFolder;
+            } else {
+                folderPath = path + '/' + subFolder;
+            }
+            if (folderPath.endsWith("/")) {
+                return folderPath;
+            } else {
+                return folderPath + '/';
+            }
+        }
+
+        /**
+         * Check if path element at index is value
+         *
+         * @param index path element index
+         * @param value path value
+         * @return true if path element at index is value
+         */
+        public boolean isPath(int index, String value) {
+            return value != null && value.equals(getPathElement(index));
+        }
+
+        protected String getPathElement(int index) {
+            if (index < pathElements.length) {
+                return pathElements[index];
+            } else {
+                return null;
+            }
+        }
+
+        public String getLastPath() {
+            return getPathElement(getPathLength() - 1);
+        }
+
+        protected boolean isBrokenHrefEncoding() {
+            return isUserAgent("DAVKit/3") || isUserAgent("eM Client/") || isBrokenLightning();
+        }
+
+        protected boolean isBrokenLightning() {
+            return isUserAgent("Lightning/1.0b2");
+        }
+
+        protected boolean isLightning() {
+            return isUserAgent("Lightning/");
+        }
+
+        protected boolean isIcal5() {
+            return isUserAgent("CoreDAV/") || isUserAgent("iOS/5");
+        }
+
+        protected boolean isUserAgent(String key) {
+            String userAgent = headers.get("user-agent");
+            return userAgent != null && userAgent.indexOf(key) >= 0;
+        }
+
+        public boolean isFreeBusy() {
+            return body != null && body.indexOf("VFREEBUSY") >= 0;
+        }
+
+        protected void buildDepth() {
+            String depthValue = headers.get("depth");
+            if ("infinity".equalsIgnoreCase(depthValue)) {
+                depth = Integer.MAX_VALUE;
+            } else if (depthValue != null) {
+                try {
+                    depth = Integer.valueOf(depthValue);
+                } catch (NumberFormatException e) {
+                    DavGatewayTray.warn(new BundleMessage("LOG_INVALID_DEPTH", depthValue));
+                }
+            }
+        }
+
+        public int getDepth() {
+            return depth;
+        }
+
+        public String getBody() {
+            return body;
+        }
+
+        public String getHeader(String headerName) {
+            return headers.get(headerName);
+        }
+
+        protected void parseXmlBody() throws IOException {
+            XMLStreamReader streamReader = null;
+            try {
+                streamReader = XMLStreamUtil.createXMLStreamReader(body);
+                while (streamReader.hasNext()) {
+                    streamReader.next();
+                    if (XMLStreamUtil.isStartTag(streamReader)) {
+                        String tagLocalName = streamReader.getLocalName();
+                        if ("prop".equals(tagLocalName)) {
+                            handleProp(streamReader);
+                        } else if ("calendar-multiget".equals(tagLocalName)
+                                || "addressbook-multiget".equals(tagLocalName)) {
+                            isMultiGet = true;
+                        } else if ("comp-filter".equals(tagLocalName)) {
+                            handleCompFilter(streamReader);
+                        } else if ("href".equals(tagLocalName)) {
+                            if (hrefs == null) {
+                                hrefs = new HashSet<String>();
+                            }
+                            if (isBrokenHrefEncoding()) {
+                                hrefs.add(streamReader.getElementText());
+                            } else {
+                                hrefs.add(URIUtil.decode(StringUtil.encodePlusSign(streamReader.getElementText())));
+                            }
+                        }
+                    }
+                }
+            } catch (XMLStreamException e) {
+                throw new DavMailException("EXCEPTION_INVALID_CALDAV_REQUEST", e.getMessage());
+            } finally {
+                try {
+                    if (streamReader != null) {
+                        streamReader.close();
+                    }
+                } catch (XMLStreamException e) {
+                    DavGatewayTray.error(e);
+                }
+            }
+        }
+
+        protected boolean isEndTag(XMLStreamReader reader, String tagLocalName) {
+            return (reader.getEventType() == XMLStreamConstants.END_ELEMENT) && (reader.getLocalName().equals(tagLocalName));
+        }
+
+        public void handleCompFilter(XMLStreamReader reader) throws XMLStreamException {
+            while (reader.hasNext() && !isEndTag(reader, "comp-filter")) {
+                reader.next();
+                if (XMLStreamUtil.isStartTag(reader, "comp-filter")) {
+                    String name = reader.getAttributeValue(null, "name");
+                    if ("VEVENT".equals(name)) {
+                        vEventOnly = true;
+                    } else if ("VTODO".equals(name)) {
+                        vTodoOnly = true;
+                    }
+                } else if (XMLStreamUtil.isStartTag(reader, "time-range")) {
+                    timeRangeStart = reader.getAttributeValue(null, "start");
+                    timeRangeEnd = reader.getAttributeValue(null, "end");
+                }
+            }
+        }
+
+        public void handleProp(XMLStreamReader reader) throws XMLStreamException {
+            while (reader.hasNext() && !isEndTag(reader, "prop")) {
+                reader.next();
+                if (XMLStreamUtil.isStartTag(reader)) {
+                    String tagLocalName = reader.getLocalName();
+                    String tagText = null;
+                    if ("displayname".equals(tagLocalName) || reader.hasText()) {
+                        tagText = XMLStreamUtil.getElementText(reader);
+                    }
+                    properties.put(tagLocalName, tagText);
+                }
+            }
+        }
+
+        public boolean hasProperty(String propertyName) {
+            return properties.containsKey(propertyName);
+        }
+
+        public String getProperty(String propertyName) {
+            return properties.get(propertyName);
+        }
+
+        public boolean isMultiGet() {
+            return isMultiGet && hrefs != null;
+        }
+
+        public Set<String> getHrefs() {
+            return hrefs;
+        }
+
+        @Override
+        public String toString() {
+            return command + ' ' + path + " Depth: " + depth + '\n' + body;
+        }
+
+        /**
+         * Get request folder path.
+         *
+         * @return exchange folder path
+         */
+        public String getFolderPath() {
+            return getFolderPath(null);
+        }
+
+        public String getParentFolderPath() {
+            int endIndex;
+            if (isFolder()) {
+                endIndex = getPathLength() - 1;
+            } else {
+                endIndex = getPathLength() - 2;
+            }
+            return getFolderPath(endIndex, null);
+        }
+
+        /**
+         * Get request folder path with subFolder.
+         *
+         * @param subFolder sub folder path
+         * @return folder path
+         */
+        public String getFolderPath(String subFolder) {
+            int endIndex;
+            if (isFolder()) {
+                endIndex = getPathLength();
+            } else {
+                endIndex = getPathLength() - 1;
+            }
+            return getFolderPath(endIndex, subFolder);
+        }
+
+        protected String getFolderPath(int endIndex, String subFolder) {
+
+            StringBuilder calendarPath = new StringBuilder();
+            for (int i = 0; i < endIndex; i++) {
+                if (getPathElement(i).length() > 0) {
+                    calendarPath.append('/').append(getPathElement(i));
+                }
+            }
+            if (subFolder != null && subFolder.length() > 0) {
+                calendarPath.append('/').append(subFolder);
+            }
+            if (this.isUserAgent("Address%20Book") || this.isUserAgent("Darwin")) {
+                /* WARNING - This is a kludge -
+                 * If your public folder address book path has spaces, then Address Book app just ignores that account
+                 * This kludge allows you to specify the path in which spaces are encoded as ___
+                 * It'll make Address book to not ignore the account and communicate with DavMail.
+                 * Here we replace the ___ in the path with spaces. Be warned if your actual address book path has ___ 
+                 * it'll fail.
+                 */
+                String result = calendarPath.toString();
+                // replace unsupported spaces
+                if (result.indexOf(' ') >=0 ) {
+                   result = result.replaceAll("___", " ");
+                }
+                // replace /addressbook suffix on public folders
+                if (result.startsWith("/public")) {
+                   result = result.replaceAll("/addressbook", "");
+                }
+
+                return result;
+            } else {
+                return calendarPath.toString();
+            }
+        }
+    }
+
+    /**
+     * Http chunked response.
+     */
+    protected class ChunkedResponse {
+        Writer writer;
+
+        protected ChunkedResponse(int status, String contentType) throws IOException {
+            writer = new OutputStreamWriter(new BufferedOutputStream(new OutputStream() {
+                @Override
+                public void write(byte[] data, int offset, int length) throws IOException {
+                    sendClient(Integer.toHexString(length));
+                    sendClient(data, offset, length);
+                    if (wireLogger.isDebugEnabled()) {
+                        StringBuilder logBuffer = new StringBuilder("> ");
+                        logBuffer.append(new String(data, offset, length, "UTF-8"));
+                        wireLogger.debug(logBuffer.toString());
+                    }
+                    sendClient("");
+                }
+
+                @Override
+                public void write(int b) throws IOException {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public void close() throws IOException {
+                    sendClient("0");
+                    sendClient("");
+                }
+            }), "UTF-8");
+            sendChunkedHttpResponse(status, contentType);
+        }
+
+        public void append(String data) throws IOException {
+            writer.write(data);
+        }
+
+        public void close() throws IOException {
+            writer.close();
+        }
+    }
+
+    /**
+     * Caldav response wrapper, content sent in chunked mode to avoid timeout
+     */
+    protected class CaldavResponse extends ChunkedResponse {
+
+        protected CaldavResponse(int status) throws IOException {
+            super(status, "text/xml;charset=UTF-8");
+            writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+        }
+
+
+        public void startMultistatus() throws IOException {
+            writer.write("<D:multistatus xmlns:D=\"DAV:\" xmlns:C=\"urn:ietf:params:xml:ns:caldav\" xmlns:E=\"urn:ietf:params:xml:ns:carddav\">");
+        }
+
+        public void startResponse(String href) throws IOException {
+            writer.write("<D:response>");
+            writer.write("<D:href>");
+            writer.write(StringUtil.xmlEncode(href));
+            writer.write("</D:href>");
+        }
+
+        public void startPropstat() throws IOException {
+            writer.write("<D:propstat>");
+            writer.write("<D:prop>");
+        }
+
+        public void appendCalendarData(String ics) throws IOException {
+            if (ics != null && ics.length() > 0) {
+                writer.write("<C:calendar-data xmlns:C=\"urn:ietf:params:xml:ns:caldav\"");
+                writer.write(" C:content-type=\"text/calendar\" C:version=\"2.0\">");
+                writer.write(StringUtil.xmlEncode(ics));
+                writer.write("</C:calendar-data>");
+            }
+        }
+
+        public void appendContactData(String vcard) throws IOException {
+            if (vcard != null && vcard.length() > 0) {
+                writer.write("<E:address-data>");
+                writer.write(StringUtil.xmlEncode(vcard));
+                writer.write("</E:address-data>");
+            }
+        }
+
+        public void appendHrefProperty(String propertyName, String propertyValue) throws IOException {
+            appendProperty(propertyName, null, "<D:href>" + StringUtil.xmlEncode(propertyValue) + "</D:href>");
+        }
+
+        public void appendProperty(String propertyName) throws IOException {
+            appendProperty(propertyName, null);
+        }
+
+        public void appendProperty(String propertyName, String propertyValue) throws IOException {
+            appendProperty(propertyName, null, propertyValue);
+        }
+
+        public void appendProperty(String propertyName, String namespace, String propertyValue) throws IOException {
+            if (propertyValue != null) {
+                writer.write('<');
+                writer.write(propertyName);
+                if (namespace != null) {
+                    writer.write("  xmlns:");
+                    writer.write(namespace);
+                }
+                writer.write('>');
+                writer.write(propertyValue);
+                writer.write("</");
+                writer.write(propertyName);
+                writer.write('>');
+            } else {
+                writer.write('<');
+                writer.write(propertyName);
+                if (namespace != null) {
+                    writer.write("  xmlns:");
+                    writer.write(namespace);
+                }
+                writer.write("/>");
+            }
+        }
+
+        public void endPropStatOK() throws IOException {
+            writer.write("</D:prop><D:status>HTTP/1.1 200 OK</D:status></D:propstat>");
+        }
+
+        public void appendPropstatNotFound() throws IOException {
+            writer.write("<D:propstat><D:status>HTTP/1.1 404 Not Found</D:status></D:propstat>");
+        }
+
+        public void endResponse() throws IOException {
+            writer.write("</D:response>");
+        }
+
+        public void endMultistatus() throws IOException {
+            writer.write("</D:multistatus>");
+        }
+
+        public void startScheduleResponse() throws IOException {
+            writer.write("<C:schedule-response xmlns:D=\"DAV:\" xmlns:C=\"urn:ietf:params:xml:ns:caldav\">");
+        }
+
+        public void startRecipientResponse(String recipient) throws IOException {
+            writer.write("<C:response><C:recipient><D:href>");
+            writer.write(recipient);
+            writer.write("</D:href></C:recipient><C:request-status>2.0;Success</C:request-status>");
+        }
+
+        public void endRecipientResponse() throws IOException {
+            writer.write("</C:response>");
+        }
+
+        public void endScheduleResponse() throws IOException {
+            writer.write("</C:schedule-response>");
+        }
+
+    }
+}
+
diff --git a/src/java/davmail/caldav/CaldavServer.java b/src/java/davmail/caldav/CaldavServer.java
new file mode 100644
index 0000000..d52d2bb
--- /dev/null
+++ b/src/java/davmail/caldav/CaldavServer.java
@@ -0,0 +1,56 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.caldav;
+
+import davmail.AbstractConnection;
+import davmail.AbstractServer;
+import davmail.Settings;
+
+import java.net.Socket;
+
+/**
+ * Calendar server, handle HTTP Caldav requests.
+ */
+public class CaldavServer extends AbstractServer {
+    /**
+     * Default HTTP Caldav port
+     */
+    public static final int DEFAULT_PORT = 80;
+
+    /**
+     * Create a ServerSocket to listen for connections.
+     * Start the thread.
+     *
+     * @param port pop listen port, 80 if not defined (0)
+     */
+    public CaldavServer(int port) {
+        super(CaldavServer.class.getName(), port, CaldavServer.DEFAULT_PORT);
+        nosslFlag = Settings.getBooleanProperty("davmail.ssl.nosecurecaldav");
+    }
+
+    @Override
+    public String getProtocolName() {
+        return "CALDAV";
+    }
+
+    @Override
+    public AbstractConnection createConnectionHandler(Socket clientSocket) {
+        return new CaldavConnection(clientSocket);
+    }
+}
\ No newline at end of file
diff --git a/src/java/davmail/exception/DavMailAuthenticationException.java b/src/java/davmail/exception/DavMailAuthenticationException.java
new file mode 100644
index 0000000..05154a9
--- /dev/null
+++ b/src/java/davmail/exception/DavMailAuthenticationException.java
@@ -0,0 +1,44 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exception;
+
+/**
+ * I18 AuthenticationException subclass.
+ */
+public class DavMailAuthenticationException extends DavMailException {
+    /**
+     * Create a DavMail authentication exception with the given BundleMessage key.
+     *
+     * @param key message key
+     */
+    public DavMailAuthenticationException(String key) {
+        super(key);
+    }
+
+    /**
+     * Create a DavMail authentication exception with the given BundleMessage key and arguments.
+     *
+     * @param key       message key
+     * @param arguments message values
+     */
+    public DavMailAuthenticationException(String key, Object... arguments) {
+        super(key, arguments);
+    }
+
+}
diff --git a/src/java/davmail/exception/DavMailException.java b/src/java/davmail/exception/DavMailException.java
new file mode 100644
index 0000000..d97d5a3
--- /dev/null
+++ b/src/java/davmail/exception/DavMailException.java
@@ -0,0 +1,70 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exception;
+
+import davmail.BundleMessage;
+
+import java.io.IOException;
+import java.util.Locale;
+
+/**
+ * I18 IOException subclass.
+ */
+public class DavMailException extends IOException {
+    private final BundleMessage message;
+
+    /**
+     * Create a DavMail exception with the given BundleMessage key and arguments.
+     *
+     * @param key       message key
+     * @param arguments message values
+     */
+    public DavMailException(String key, Object... arguments) {
+        this.message = new BundleMessage(key, arguments);
+    }
+
+    /**
+     * Get formatted message
+     *
+     * @return english formatted message
+     */
+    @Override
+    public String getMessage() {
+        return message.formatLog();
+    }
+
+    /**
+     * Get formatted message using locale.
+     *
+     * @param locale locale
+     * @return localized formatted message
+     */
+    public String getMessage(Locale locale) {
+        return message.format(locale);
+    }
+
+    /**
+     * Get internal exception BundleMessage.
+     *
+     * @return unformatted message
+     */
+    public BundleMessage getBundleMessage() {
+        return message;
+    }
+}
diff --git a/src/java/davmail/exception/HttpForbiddenException.java b/src/java/davmail/exception/HttpForbiddenException.java
new file mode 100644
index 0000000..30faa49
--- /dev/null
+++ b/src/java/davmail/exception/HttpForbiddenException.java
@@ -0,0 +1,35 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exception;
+
+import org.apache.commons.httpclient.HttpException;
+
+/**
+ * HttpException with 403 forbidden status.
+ */
+public class HttpForbiddenException extends HttpException {
+    /**
+     * HttpException with 403 forbidden status.
+     *
+     * @param message exception message
+     */
+    public HttpForbiddenException(String message) {
+        super(message);
+    }
+}
diff --git a/src/java/davmail/exception/HttpNotFoundException.java b/src/java/davmail/exception/HttpNotFoundException.java
new file mode 100644
index 0000000..6604d0e
--- /dev/null
+++ b/src/java/davmail/exception/HttpNotFoundException.java
@@ -0,0 +1,35 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exception;
+
+import org.apache.commons.httpclient.HttpException;
+
+/**
+ * HttpException with 404 not found status.
+ */
+public class HttpNotFoundException extends HttpException {
+    /**
+     * HttpException with 404 not found status.
+     *
+     * @param message exception message
+     */
+    public HttpNotFoundException(String message) {
+        super(message);
+    }
+}
diff --git a/src/java/davmail/exception/HttpPreconditionFailedException.java b/src/java/davmail/exception/HttpPreconditionFailedException.java
new file mode 100644
index 0000000..458452c
--- /dev/null
+++ b/src/java/davmail/exception/HttpPreconditionFailedException.java
@@ -0,0 +1,35 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exception;
+
+import org.apache.commons.httpclient.HttpException;
+
+/**
+ * HttpException with 412 precondition failed status.
+ */
+public class HttpPreconditionFailedException extends HttpException {
+    /**
+     * HttpException with 412 precondition failed status.
+     *
+     * @param message exception message
+     */
+    public HttpPreconditionFailedException(String message) {
+        super(message);
+    }
+}
diff --git a/src/java/davmail/exception/HttpServerErrorException.java b/src/java/davmail/exception/HttpServerErrorException.java
new file mode 100644
index 0000000..170b983
--- /dev/null
+++ b/src/java/davmail/exception/HttpServerErrorException.java
@@ -0,0 +1,35 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exception;
+
+import org.apache.commons.httpclient.HttpException;
+
+/**
+ * HttpException with 500 internal server error status.
+ */
+public class HttpServerErrorException extends HttpException {
+    /**
+     * HttpException with 500 internal server error status.
+     *
+     * @param message exception message
+     */
+    public HttpServerErrorException(String message) {
+        super(message);
+    }
+}
diff --git a/src/java/davmail/exception/InsufficientStorageException.java b/src/java/davmail/exception/InsufficientStorageException.java
new file mode 100644
index 0000000..3418087
--- /dev/null
+++ b/src/java/davmail/exception/InsufficientStorageException.java
@@ -0,0 +1,35 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exception;
+
+import org.apache.commons.httpclient.HttpException;
+
+/**
+ * HttpException with 507 Insufficient Storage status.
+ */
+public class InsufficientStorageException extends HttpException {
+    /**
+     * HttpException with 507 Insufficient Storage status.
+     *
+     * @param message exception message
+     */
+    public InsufficientStorageException(String message) {
+        super(message);
+    }
+}
\ No newline at end of file
diff --git a/src/java/davmail/exception/LoginTimeoutException.java b/src/java/davmail/exception/LoginTimeoutException.java
new file mode 100644
index 0000000..3cc9289
--- /dev/null
+++ b/src/java/davmail/exception/LoginTimeoutException.java
@@ -0,0 +1,35 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exception;
+
+import org.apache.commons.httpclient.HttpException;
+
+/**
+ * HttpException with 440 login timeout status.
+ */
+public class LoginTimeoutException extends HttpException {
+    /**
+     * HttpException with 550 login timeout status.
+     *
+     * @param message exception message
+     */
+    public LoginTimeoutException(String message) {
+        super(message);
+    }
+}
diff --git a/src/java/davmail/exception/WebdavNotAvailableException.java b/src/java/davmail/exception/WebdavNotAvailableException.java
new file mode 100644
index 0000000..fabd820
--- /dev/null
+++ b/src/java/davmail/exception/WebdavNotAvailableException.java
@@ -0,0 +1,34 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exception;
+
+/**
+ * Exchange 2007 with Webdav disabled will trigger this exception.
+ */
+public class WebdavNotAvailableException extends DavMailException {
+    /**
+     * Create a DavMail exception with the given BundleMessage key and arguments.
+     *
+     * @param key       message key
+     * @param arguments message values
+     */
+    public WebdavNotAvailableException(String key, Object... arguments) {
+        super(key, arguments);
+    }
+}
diff --git a/src/java/davmail/exchange/DoubleDotInputStream.java b/src/java/davmail/exchange/DoubleDotInputStream.java
new file mode 100644
index 0000000..f8c85f2
--- /dev/null
+++ b/src/java/davmail/exchange/DoubleDotInputStream.java
@@ -0,0 +1,85 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PushbackInputStream;
+
+/**
+ * Replace double dot lines with single dot in input stream.
+ * A line with a single dot means end of stream
+ */
+public class DoubleDotInputStream extends PushbackInputStream {
+    final int[] buffer = new int[4];
+    int index = -1;
+
+    /**
+     * @inheritDoc
+     */
+    public DoubleDotInputStream(InputStream in) {
+        super(in, 4);
+    }
+
+    /**
+     * Push current byte to buffer and read next byte.
+     *
+     * @return next byte
+     * @throws IOException on error
+     */
+    protected int readNextByte() throws IOException {
+        int b = super.read();
+        buffer[++index] = b;
+        return b;
+    }
+
+    @Override
+    public int read() throws IOException {
+        int b = super.read();
+        if (b == '\r') {
+            // \r\n
+            if (readNextByte() == '\n') {
+                // \r\n.
+                if (readNextByte() == '.') {
+                    // \r\n.\r
+                    if (readNextByte() == '\r') {
+                        // \r\n.\r\n
+                        if (readNextByte() == '\n') {
+                            // end of stream
+                            index = -1;
+                            b = -1;
+                        }
+                        // \r\n..
+                    } else if (buffer[index] == '.') {
+                        // replace double dot
+                        index--;
+                    }
+                }
+            }
+            // push back characters
+            if (index >= 0) {
+                while (index >= 0) {
+                    unread(buffer[index--]);
+                }
+            }
+        }
+        return b;
+    }
+
+}
diff --git a/src/java/davmail/exchange/DoubleDotOutputStream.java b/src/java/davmail/exchange/DoubleDotOutputStream.java
new file mode 100644
index 0000000..6973f86
--- /dev/null
+++ b/src/java/davmail/exchange/DoubleDotOutputStream.java
@@ -0,0 +1,76 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * RFC 1939: 3 Basic Operations
+ * [...]
+ * If any line begins with the termination octet, the line is "byte-stuffed" by
+ * pre-pending the termination octet to that line of the response.
+ */
+public class DoubleDotOutputStream extends FilterOutputStream {
+
+    // remember last 2 bytes written
+    final int[] buf = {0, 0};
+
+    /**
+     * @inheritDoc
+     */
+    public DoubleDotOutputStream(OutputStream out) {
+        super(out);
+    }
+
+    @Override
+    public void write(int b) throws IOException {
+        if (b == '.' && (buf[0] == '\r' || buf[0] == '\n' || buf[0] == 0)) {
+            // line starts with '.', prepend it with an additional '.'
+            out.write('.');
+        }
+        out.write(b);
+
+        buf[1] = buf[0];
+        buf[0] = b;
+    }
+
+    /**
+     * RFC 1939: 3 Basic Operations
+     * [...]
+     * Hence a multi-line response is terminated with the five octets
+     * "CRLF.CRLF"
+     * <p/>
+     * Do not close actual outputstream
+     *
+     * @throws IOException on error
+     */
+    @Override
+    public void close() throws IOException {
+        if (buf[1] != '\r' || buf[0] != '\n') {
+            out.write('\r');
+            out.write('\n');
+        }
+        out.write('.');
+        out.write('\r');
+        out.write('\n');
+    }
+
+}
diff --git a/src/java/davmail/exchange/ExchangeSession.java b/src/java/davmail/exchange/ExchangeSession.java
new file mode 100644
index 0000000..d3ba208
--- /dev/null
+++ b/src/java/davmail/exchange/ExchangeSession.java
@@ -0,0 +1,3378 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange;
+
+import davmail.BundleMessage;
+import davmail.Settings;
+import davmail.exception.DavMailAuthenticationException;
+import davmail.exception.DavMailException;
+import davmail.exception.WebdavNotAvailableException;
+import davmail.http.DavGatewayHttpClientFacade;
+import davmail.http.DavGatewayOTPPrompt;
+import davmail.ui.NotificationDialog;
+import davmail.util.StringUtil;
+import org.apache.commons.httpclient.*;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.httpclient.methods.PostMethod;
+import org.apache.commons.httpclient.params.HttpClientParams;
+import org.apache.commons.httpclient.util.URIUtil;
+import org.apache.log4j.Logger;
+import org.htmlcleaner.CommentToken;
+import org.htmlcleaner.ContentToken;
+import org.htmlcleaner.HtmlCleaner;
+import org.htmlcleaner.TagNode;
+
+import javax.imageio.ImageIO;
+import javax.mail.MessagingException;
+import javax.mail.internet.*;
+import javax.mail.util.SharedByteArrayInputStream;
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.net.NoRouteToHostException;
+import java.net.UnknownHostException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * Exchange session through Outlook Web Access (DAV)
+ */
+public abstract class ExchangeSession {
+
+    protected static final Logger LOGGER = Logger.getLogger("davmail.exchange.ExchangeSession");
+
+    /**
+     * Reference GMT timezone to format dates
+     */
+    public static final SimpleTimeZone GMT_TIMEZONE = new SimpleTimeZone(0, "GMT");
+
+    protected static final Set<String> USER_NAME_FIELDS = new HashSet<String>();
+
+    static {
+        USER_NAME_FIELDS.add("username");
+        USER_NAME_FIELDS.add("txtUserName");
+        USER_NAME_FIELDS.add("userid");
+        USER_NAME_FIELDS.add("SafeWordUser");
+        USER_NAME_FIELDS.add("user_name");
+    }
+
+    protected static final Set<String> PASSWORD_FIELDS = new HashSet<String>();
+
+    static {
+        PASSWORD_FIELDS.add("password");
+        PASSWORD_FIELDS.add("txtUserPass");
+        PASSWORD_FIELDS.add("pw");
+        PASSWORD_FIELDS.add("basicPassword");
+    }
+
+    protected static final Set<String> TOKEN_FIELDS = new HashSet<String>();
+
+    static {
+        TOKEN_FIELDS.add("SafeWordPassword");
+        TOKEN_FIELDS.add("passcode");
+    }
+
+    protected static final int FREE_BUSY_INTERVAL = 15;
+
+    protected static final String PUBLIC_ROOT = "/public/";
+    protected static final String CALENDAR = "calendar";
+    protected static final String TASKS = "tasks";
+    /**
+     * Contacts folder logical name
+     */
+    public static final String CONTACTS = "contacts";
+    protected static final String ADDRESSBOOK = "addressbook";
+    protected static final String INBOX = "INBOX";
+    protected static final String LOWER_CASE_INBOX = "inbox";
+    protected static final String SENT = "Sent";
+    protected static final String SENDMSG = "##DavMailSubmissionURI##";
+    protected static final String DRAFTS = "Drafts";
+    protected static final String TRASH = "Trash";
+    protected static final String JUNK = "Junk";
+    protected static final String UNSENT = "Unsent Messages";
+
+    static {
+        // Adjust Mime decoder settings
+        System.setProperty("mail.mime.ignoreunknownencoding", "true");
+        System.setProperty("mail.mime.decodetext.strict", "false");
+    }
+
+    protected String publicFolderUrl;
+
+    /**
+     * Base user mailboxes path (used to select folder)
+     */
+    protected String mailPath;
+    protected String rootPath;
+    protected String email;
+    protected String alias;
+    /**
+     * Lower case Caldav path to current user mailbox.
+     * /users/<i>email</i>
+     */
+    protected String currentMailboxPath;
+    protected final HttpClient httpClient;
+
+    protected String userName;
+
+    protected String serverVersion;
+
+    protected static final String YYYY_MM_DD_HH_MM_SS = "yyyy/MM/dd HH:mm:ss";
+    private static final String YYYYMMDD_T_HHMMSS_Z = "yyyyMMdd'T'HHmmss'Z'";
+    protected static final String YYYY_MM_DD_T_HHMMSS_Z = "yyyy-MM-dd'T'HH:mm:ss'Z'";
+    private static final String YYYY_MM_DD = "yyyy-MM-dd";
+    private static final String YYYY_MM_DD_T_HHMMSS_SSS_Z = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
+
+    /**
+     * Logon form user name fields.
+     */
+    private final List<String> userNameInputs = new ArrayList<String>();
+    /**
+     * Logon form password field, default is password.
+     */
+    private String passwordInput = "password";
+
+    /**
+     * Create an exchange session for the given URL.
+     * The session is established for given userName and password
+     *
+     * @param url      Exchange url
+     * @param userName user login name
+     * @param password user password
+     * @throws IOException on error
+     */
+    public ExchangeSession(String url, String userName, String password) throws IOException {
+        this.userName = userName;
+        try {
+            httpClient = DavGatewayHttpClientFacade.getInstance(url);
+            // set private connection pool
+            DavGatewayHttpClientFacade.createMultiThreadedHttpConnectionManager(httpClient);
+            boolean isBasicAuthentication = isBasicAuthentication(httpClient, url);
+
+            DavGatewayHttpClientFacade.setCredentials(httpClient, userName, password);
+
+            // get webmail root url
+            // providing credentials
+            // manually follow redirect
+            HttpMethod method = DavGatewayHttpClientFacade.executeFollowRedirects(httpClient, url);
+
+            if (!this.isAuthenticated()) {
+                if (isBasicAuthentication) {
+                    int status = method.getStatusCode();
+
+                    if (status == HttpStatus.SC_UNAUTHORIZED) {
+                        method.releaseConnection();
+                        throw new DavMailAuthenticationException("EXCEPTION_AUTHENTICATION_FAILED");
+                    } else if (status != HttpStatus.SC_OK) {
+                        method.releaseConnection();
+                        throw DavGatewayHttpClientFacade.buildHttpException(method);
+                    }
+                    // workaround for basic authentication on /exchange and form based authentication at /owa
+                    if ("/owa/auth/logon.aspx".equals(method.getPath())) {
+                        method = formLogin(httpClient, method, userName, password);
+                    }
+                } else {
+                    method = formLogin(httpClient, method, userName, password);
+                }
+            }
+
+            // avoid 401 roundtrips, only if NTLM is disabled and basic authentication enabled
+            if (isBasicAuthentication && !DavGatewayHttpClientFacade.hasNTLM(httpClient)) {
+                httpClient.getParams().setParameter(HttpClientParams.PREEMPTIVE_AUTHENTICATION, true);
+            }
+
+            buildSessionInfo(method);
+
+        } catch (DavMailAuthenticationException exc) {
+            LOGGER.error(exc.getMessage());
+            throw exc;
+        } catch (UnknownHostException exc) {
+            BundleMessage message = new BundleMessage("EXCEPTION_CONNECT", exc.getClass().getName(), exc.getMessage());
+            ExchangeSession.LOGGER.error(message);
+            throw new DavMailException("EXCEPTION_DAVMAIL_CONFIGURATION", message);
+        } catch (WebdavNotAvailableException exc) {
+            throw exc;
+        } catch (IOException exc) {
+            LOGGER.error(BundleMessage.formatLog("EXCEPTION_EXCHANGE_LOGIN_FAILED", exc));
+            throw new DavMailException("EXCEPTION_EXCHANGE_LOGIN_FAILED", exc);
+        }
+        LOGGER.debug("Session " + this + " created");
+    }
+
+    /**
+     * Format date to exchange search format.
+     *
+     * @param date date object
+     * @return formatted search date
+     */
+    public abstract String formatSearchDate(Date date);
+
+    /**
+     * Return standard zulu date formatter.
+     *
+     * @return zulu date formatter
+     */
+    public static SimpleDateFormat getZuluDateFormat() {
+        SimpleDateFormat dateFormat = new SimpleDateFormat(YYYYMMDD_T_HHMMSS_Z, Locale.ENGLISH);
+        dateFormat.setTimeZone(GMT_TIMEZONE);
+        return dateFormat;
+    }
+
+    protected static SimpleDateFormat getVcardBdayFormat() {
+        SimpleDateFormat dateFormat = new SimpleDateFormat(YYYY_MM_DD, Locale.ENGLISH);
+        dateFormat.setTimeZone(GMT_TIMEZONE);
+        return dateFormat;
+    }
+
+    protected static SimpleDateFormat getExchangeZuluDateFormat() {
+        SimpleDateFormat dateFormat = new SimpleDateFormat(YYYY_MM_DD_T_HHMMSS_Z, Locale.ENGLISH);
+        dateFormat.setTimeZone(GMT_TIMEZONE);
+        return dateFormat;
+    }
+
+    protected static SimpleDateFormat getExchangeZuluDateFormatMillisecond() {
+        SimpleDateFormat dateFormat = new SimpleDateFormat(YYYY_MM_DD_T_HHMMSS_SSS_Z, Locale.ENGLISH);
+        dateFormat.setTimeZone(GMT_TIMEZONE);
+        return dateFormat;
+    }
+
+    protected static Date parseDate(String dateString) throws ParseException {
+        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
+        dateFormat.setTimeZone(GMT_TIMEZONE);
+        return dateFormat.parse(dateString);
+    }
+
+
+    /**
+     * Test if the session expired.
+     *
+     * @return true this session expired
+     * @throws NoRouteToHostException on error
+     * @throws UnknownHostException   on error
+     */
+    public boolean isExpired() throws NoRouteToHostException, UnknownHostException {
+        boolean isExpired = false;
+        try {
+            getFolder("");
+        } catch (UnknownHostException exc) {
+            throw exc;
+        } catch (NoRouteToHostException exc) {
+            throw exc;
+        } catch (IOException e) {
+            isExpired = true;
+        }
+
+        return isExpired;
+    }
+
+    /**
+     * Test authentication mode : form based or basic.
+     *
+     * @param url        exchange base URL
+     * @param httpClient httpClient instance
+     * @return true if basic authentication detected
+     */
+    protected boolean isBasicAuthentication(HttpClient httpClient, String url) {
+        return DavGatewayHttpClientFacade.getHttpStatus(httpClient, url) == HttpStatus.SC_UNAUTHORIZED;
+    }
+
+    protected String getAbsoluteUri(HttpMethod method, String path) throws URIException {
+        URI uri = method.getURI();
+        if (path != null) {
+            // reset query string
+            uri.setQuery(null);
+            if (path.startsWith("/")) {
+                // path is absolute, replace method path
+                uri.setPath(path);
+            } else if (path.startsWith("http://") || path.startsWith("https://")) {
+                return path;
+            } else {
+                // relative path, build new path
+                String currentPath = method.getPath();
+                int end = currentPath.lastIndexOf('/');
+                if (end >= 0) {
+                    uri.setPath(currentPath.substring(0, end + 1) + path);
+                } else {
+                    throw new URIException(uri.getURI());
+                }
+            }
+        }
+        return uri.getURI();
+    }
+
+    protected String getScriptBasedFormURL(HttpMethod initmethod, String pathQuery) throws URIException {
+        URI initmethodURI = initmethod.getURI();
+        int queryIndex = pathQuery.indexOf('?');
+        if (queryIndex >= 0) {
+            if (queryIndex > 0) {
+                // update path
+                String newPath = pathQuery.substring(0, queryIndex);
+                if (newPath.startsWith("/")) {
+                    // absolute path
+                    initmethodURI.setPath(newPath);
+                } else {
+                    String currentPath = initmethodURI.getPath();
+                    int folderIndex = currentPath.lastIndexOf('/');
+                    if (folderIndex >= 0) {
+                        // replace relative path
+                        initmethodURI.setPath(currentPath.substring(0, folderIndex + 1) + newPath);
+                    } else {
+                        // should not happen
+                        initmethodURI.setPath('/' + newPath);
+                    }
+                }
+            }
+            initmethodURI.setQuery(pathQuery.substring(queryIndex + 1));
+        }
+        return initmethodURI.getURI();
+    }
+
+    /**
+     * Try to find logon method path from logon form body.
+     *
+     * @param httpClient httpClient instance
+     * @param initmethod form body http method
+     * @return logon method
+     * @throws IOException on error
+     */
+    protected HttpMethod buildLogonMethod(HttpClient httpClient, HttpMethod initmethod) throws IOException {
+
+        HttpMethod logonMethod = null;
+
+        // create an instance of HtmlCleaner
+        HtmlCleaner cleaner = new HtmlCleaner();
+
+        try {
+            TagNode node = cleaner.clean(initmethod.getResponseBodyAsStream());
+            List forms = node.getElementListByName("form", true);
+            TagNode logonForm = null;
+            // select form
+            if (forms.size() == 1) {
+                logonForm = (TagNode) forms.get(0);
+            } else if (forms.size() > 1) {
+                for (Object form : forms) {
+                    if ("logonForm".equals(((TagNode) form).getAttributeByName("name"))) {
+                        logonForm = ((TagNode) form);
+                    }
+                }
+            }
+            if (logonForm != null) {
+                String logonMethodPath = logonForm.getAttributeByName("action");
+
+                // workaround for broken form with empty action
+                if (logonMethodPath != null && logonMethodPath.length() == 0) {
+                    logonMethodPath = "/owa/auth.owa";
+                }
+
+                logonMethod = new PostMethod(getAbsoluteUri(initmethod, logonMethodPath));
+
+                // retrieve lost inputs attached to body
+                List inputList = node.getElementListByName("input", true);
+
+                for (Object input : inputList) {
+                    String type = ((TagNode) input).getAttributeByName("type");
+                    String name = ((TagNode) input).getAttributeByName("name");
+                    String value = ((TagNode) input).getAttributeByName("value");
+                    if ("hidden".equalsIgnoreCase(type) && name != null && value != null) {
+                        ((PostMethod) logonMethod).addParameter(name, value);
+                    }
+                    // custom login form
+                    if (USER_NAME_FIELDS.contains(name)) {
+                        userNameInputs.add(name);
+                    } else if (PASSWORD_FIELDS.contains(name)) {
+                        passwordInput = name;
+                    } else if ("addr".equals(name)) {
+                        // this is not a logon form but a redirect form
+                        HttpMethod newInitMethod = DavGatewayHttpClientFacade.executeFollowRedirects(httpClient, logonMethod);
+                        logonMethod = buildLogonMethod(httpClient, newInitMethod);
+                    } else if (TOKEN_FIELDS.contains(name)) {
+                        // one time password, ask user
+                        ((PostMethod) logonMethod).addParameter(name, DavGatewayOTPPrompt.getOneTimePassword());
+                    } else if ("otc".equals(name)) {
+                        // captcha image, get image and ask user
+                        String pinsafeUser = getAliasFromLogin();
+                        if (pinsafeUser == null) {
+                            pinsafeUser = userName;
+                        }
+                        GetMethod getMethod = new GetMethod("/PINsafeISAFilter.dll?username=" + pinsafeUser);
+                        try {
+                            int status = httpClient.executeMethod(getMethod);
+                            if (status != HttpStatus.SC_OK) {
+                                throw DavGatewayHttpClientFacade.buildHttpException(getMethod);
+                            }
+                            BufferedImage captchaImage = ImageIO.read(getMethod.getResponseBodyAsStream());
+                            ((PostMethod) logonMethod).addParameter(name, DavGatewayOTPPrompt.getCaptchaValue(captchaImage));
+
+                        } finally {
+                            getMethod.releaseConnection();
+                        }
+                    }
+                }
+            } else {
+                List frameList = node.getElementListByName("frame", true);
+                if (frameList.size() == 1) {
+                    String src = ((TagNode) frameList.get(0)).getAttributeByName("src");
+                    if (src != null) {
+                        LOGGER.debug("Frames detected in form page, try frame content");
+                        initmethod.releaseConnection();
+                        HttpMethod newInitMethod = DavGatewayHttpClientFacade.executeFollowRedirects(httpClient, src);
+                        logonMethod = buildLogonMethod(httpClient, newInitMethod);
+                    }
+                } else {
+                    // another failover for script based logon forms (Exchange 2007)
+                    List scriptList = node.getElementListByName("script", true);
+                    for (Object script : scriptList) {
+                        List contents = ((TagNode) script).getChildren();
+                        for (Object content : contents) {
+                            if (content instanceof CommentToken) {
+                                String scriptValue = ((CommentToken) content).getCommentedContent();
+                                String sUrl = StringUtil.getToken(scriptValue, "var a_sUrl = \"", "\"");
+                                String sLgn = StringUtil.getToken(scriptValue, "var a_sLgnQS = \"", "\"");
+                                if (sLgn == null) {
+                                    sLgn = StringUtil.getToken(scriptValue, "var a_sLgn = \"", "\"");
+                                }
+                                if (sUrl != null && sLgn != null) {
+                                    String src = getScriptBasedFormURL(initmethod, sLgn + sUrl);
+                                    LOGGER.debug("Detected script based logon, redirect to form at " + src);
+                                    HttpMethod newInitMethod = DavGatewayHttpClientFacade.executeFollowRedirects(httpClient, src);
+                                    logonMethod = buildLogonMethod(httpClient, newInitMethod);
+                                }
+
+                            } else if (content instanceof ContentToken) {
+                                // Microsoft Forefront Unified Access Gateway redirect
+                                String scriptValue = ((ContentToken) content).getContent();
+                                String location = StringUtil.getToken(scriptValue, "window.location.replace(\"", "\"");
+                                if (location != null) {
+                                    LOGGER.debug("Post logon redirect to: " + location);
+                                    logonMethod = DavGatewayHttpClientFacade.executeFollowRedirects(httpClient, location);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        } catch (IOException e) {
+            LOGGER.error("Error parsing login form at " + initmethod.getURI());
+        } finally {
+            initmethod.releaseConnection();
+        }
+
+        return logonMethod;
+    }
+
+    protected HttpMethod postLogonMethod(HttpClient httpClient, HttpMethod logonMethod, String userName, String password) throws IOException {
+        String userNameInput;
+        if (userNameInputs.size() == 2) {
+            // multiple username fields, split userid|username on |
+            int pipeIndex = userName.indexOf('|');
+            if (pipeIndex < 0) {
+                LOGGER.warn("Multiple user fields detected, please use userid|username as user name in client");
+            } else {
+                String userid = userName.substring(0, pipeIndex);
+                ((PostMethod) logonMethod).removeParameter("userid");
+                ((PostMethod) logonMethod).addParameter("userid", userid);
+                userName = userName.substring(pipeIndex + 1);
+                // adjust credentials
+                this.userName = userName;
+                DavGatewayHttpClientFacade.setCredentials(httpClient, userName, password);
+            }
+
+            userNameInput = "username";
+        } else if (userNameInputs.size() == 1) {
+            // simple username field
+            userNameInput = userNameInputs.get(0);
+        } else {
+            // should not happen
+            userNameInput = "username";
+        }
+        // make sure username and password fields are empty
+        ((PostMethod) logonMethod).removeParameter(userNameInput);
+        ((PostMethod) logonMethod).removeParameter(passwordInput);
+        ((PostMethod) logonMethod).removeParameter("trusted");
+        ((PostMethod) logonMethod).removeParameter("flags");
+        ((PostMethod) logonMethod).addParameter(userNameInput, userName);
+        ((PostMethod) logonMethod).addParameter(passwordInput, password);
+        ((PostMethod) logonMethod).addParameter("trusted", "4");
+        ((PostMethod) logonMethod).addParameter("flags", "4");
+
+        // add exchange 2010 PBack cookie in compatibility mode
+        httpClient.getState().addCookie(new Cookie(httpClient.getHostConfiguration().getHost(), "PBack", "0", "/", null, false));
+
+        logonMethod = DavGatewayHttpClientFacade.executeFollowRedirects(httpClient, logonMethod);
+
+        // test form based authentication
+        checkFormLoginQueryString(logonMethod);
+
+        // workaround for post logon script redirect
+        if (!isAuthenticated()) {
+            // try to get new method from script based redirection
+            logonMethod = buildLogonMethod(httpClient, logonMethod);
+
+            if (logonMethod != null) {
+                // if logonMethod is not null, try to follow redirection
+                logonMethod = DavGatewayHttpClientFacade.executeFollowRedirects(httpClient, logonMethod);
+                checkFormLoginQueryString(logonMethod);
+                // also check cookies
+                if (!isAuthenticated()) {
+                    throwAuthenticationFailed();
+                }
+            } else {
+                // authentication failed
+                throwAuthenticationFailed();
+            }
+        }
+
+        // check for language selection form
+        if (logonMethod != null && "/owa/languageselection.aspx".equals(logonMethod.getPath())) {
+            // need to submit form
+            logonMethod = submitLanguageSelectionForm(logonMethod);
+        }
+        return logonMethod;
+    }
+
+    protected HttpMethod formLogin(HttpClient httpClient, HttpMethod initmethod, String userName, String password) throws IOException {
+        LOGGER.debug("Form based authentication detected");
+
+        HttpMethod logonMethod = buildLogonMethod(httpClient, initmethod);
+        if (logonMethod == null) {
+            LOGGER.debug("Authentication form not found at " + initmethod.getURI() + ", trying default url");
+            logonMethod = new PostMethod("/owa/auth/owaauth.dll");
+        }
+        logonMethod = postLogonMethod(httpClient, logonMethod, userName, password);
+
+        return logonMethod;
+    }
+
+    protected HttpMethod submitLanguageSelectionForm(HttpMethod logonMethod) throws IOException {
+        PostMethod postLanguageFormMethod;
+        // create an instance of HtmlCleaner
+        HtmlCleaner cleaner = new HtmlCleaner();
+
+        try {
+            TagNode node = cleaner.clean(logonMethod.getResponseBodyAsStream());
+            List forms = node.getElementListByName("form", true);
+            TagNode languageForm;
+            // select form
+            if (forms.size() == 1) {
+                languageForm = (TagNode) forms.get(0);
+            } else {
+                throw new IOException("Form not found");
+            }
+            String languageMethodPath = languageForm.getAttributeByName("action");
+
+            postLanguageFormMethod = new PostMethod(getAbsoluteUri(logonMethod, languageMethodPath));
+
+            List inputList = languageForm.getElementListByName("input", true);
+            for (Object input : inputList) {
+                String name = ((TagNode) input).getAttributeByName("name");
+                String value = ((TagNode) input).getAttributeByName("value");
+                if (name != null && value != null) {
+                    postLanguageFormMethod.addParameter(name, value);
+                }
+            }
+            List selectList = languageForm.getElementListByName("select", true);
+            for (Object select : selectList) {
+                String name = ((TagNode) select).getAttributeByName("name");
+                List optionList = ((TagNode) select).getElementListByName("option", true);
+                String value = null;
+                for (Object option : optionList) {
+                    if (((TagNode) option).getAttributeByName("selected") != null) {
+                        value = ((TagNode) option).getAttributeByName("value");
+                        break;
+                    }
+                }
+                if (name != null && value != null) {
+                    postLanguageFormMethod.addParameter(name, value);
+                }
+            }
+        } catch (IOException e) {
+            String errorMessage = "Error parsing language selection form at " + logonMethod.getURI();
+            LOGGER.error(errorMessage);
+            throw new IOException(errorMessage);
+        } finally {
+            logonMethod.releaseConnection();
+        }
+
+        return DavGatewayHttpClientFacade.executeFollowRedirects(httpClient, postLanguageFormMethod);
+    }
+
+    /**
+     * Look for session cookies.
+     *
+     * @return true if session cookies are available
+     */
+    protected boolean isAuthenticated() {
+        boolean authenticated = false;
+        for (Cookie cookie : httpClient.getState().getCookies()) {
+            // Exchange 2003 cookies
+            if (cookie.getName().startsWith("cadata") || "sessionid".equals(cookie.getName())
+                    // Exchange 2007 cookie
+                    || "UserContext".equals(cookie.getName())
+                    // Direct EWS access
+                    || "exchangecookie".equals(cookie.getName())
+                    ) {
+                authenticated = true;
+                break;
+            }
+        }
+        return authenticated;
+    }
+
+    protected void checkFormLoginQueryString(HttpMethod logonMethod) throws DavMailAuthenticationException {
+        String queryString = logonMethod.getQueryString();
+        if (queryString != null && (queryString.contains("reason=2") || queryString.contains("reason=4"))) {
+            logonMethod.releaseConnection();
+            throwAuthenticationFailed();
+        }
+    }
+
+    protected void throwAuthenticationFailed() throws DavMailAuthenticationException {
+        if (this.userName != null && this.userName.contains("\\")) {
+            throw new DavMailAuthenticationException("EXCEPTION_AUTHENTICATION_FAILED");
+        } else {
+            throw new DavMailAuthenticationException("EXCEPTION_AUTHENTICATION_FAILED_RETRY");
+        }
+    }
+
+    protected abstract void buildSessionInfo(HttpMethod method) throws DavMailException;
+
+    /**
+     * Create message in specified folder.
+     * Will overwrite an existing message with same subject in the same folder
+     *
+     * @param folderPath  Exchange folder path
+     * @param messageName message name
+     * @param properties  message properties (flags)
+     * @param mimeMessage MIME message
+     * @throws IOException when unable to create message
+     */
+    public abstract void createMessage(String folderPath, String messageName, HashMap<String, String> properties, MimeMessage mimeMessage) throws IOException;
+
+    /**
+     * Update given properties on message.
+     *
+     * @param message    Exchange message
+     * @param properties Webdav properties map
+     * @throws IOException on error
+     */
+    public abstract void updateMessage(Message message, Map<String, String> properties) throws IOException;
+
+
+    /**
+     * Delete Exchange message.
+     *
+     * @param message Exchange message
+     * @throws IOException on error
+     */
+    public abstract void deleteMessage(Message message) throws IOException;
+
+    /**
+     * Get raw MIME message content
+     *
+     * @param message Exchange message
+     * @return message body
+     * @throws IOException on error
+     */
+    protected abstract byte[] getContent(Message message) throws IOException;
+
+    protected static final Set<String> POP_MESSAGE_ATTRIBUTES = new HashSet<String>();
+
+    static {
+        POP_MESSAGE_ATTRIBUTES.add("uid");
+        POP_MESSAGE_ATTRIBUTES.add("imapUid");
+        POP_MESSAGE_ATTRIBUTES.add("messageSize");
+    }
+
+    /**
+     * Return folder message list with id and size only (for POP3 listener).
+     *
+     * @param folderName Exchange folder name
+     * @return folder message list
+     * @throws IOException on error
+     */
+    public MessageList getAllMessageUidAndSize(String folderName) throws IOException {
+        return searchMessages(folderName, POP_MESSAGE_ATTRIBUTES, null);
+    }
+
+    protected static final Set<String> IMAP_MESSAGE_ATTRIBUTES = new HashSet<String>();
+
+    static {
+        IMAP_MESSAGE_ATTRIBUTES.add("permanenturl");
+        IMAP_MESSAGE_ATTRIBUTES.add("urlcompname");
+        IMAP_MESSAGE_ATTRIBUTES.add("uid");
+        IMAP_MESSAGE_ATTRIBUTES.add("messageSize");
+        IMAP_MESSAGE_ATTRIBUTES.add("imapUid");
+        IMAP_MESSAGE_ATTRIBUTES.add("junk");
+        IMAP_MESSAGE_ATTRIBUTES.add("flagStatus");
+        IMAP_MESSAGE_ATTRIBUTES.add("messageFlags");
+        IMAP_MESSAGE_ATTRIBUTES.add("lastVerbExecuted");
+        IMAP_MESSAGE_ATTRIBUTES.add("read");
+        IMAP_MESSAGE_ATTRIBUTES.add("deleted");
+        IMAP_MESSAGE_ATTRIBUTES.add("date");
+        IMAP_MESSAGE_ATTRIBUTES.add("lastmodified");
+        // OSX IMAP requests content-class
+        IMAP_MESSAGE_ATTRIBUTES.add("contentclass");
+    }
+
+    protected static final Set<String> UID_MESSAGE_ATTRIBUTES = new HashSet<String>();
+
+    static {
+        UID_MESSAGE_ATTRIBUTES.add("uid");
+    }
+
+    /**
+     * Get all folder messages.
+     *
+     * @param folderPath Exchange folder name
+     * @return message list
+     * @throws IOException on error
+     */
+    public MessageList searchMessages(String folderPath) throws IOException {
+        return searchMessages(folderPath, IMAP_MESSAGE_ATTRIBUTES, null);
+    }
+
+    /**
+     * Search folder for messages matching conditions, with attributes needed by IMAP listener.
+     *
+     * @param folderName Exchange folder name
+     * @param condition  search filter
+     * @return message list
+     * @throws IOException on error
+     */
+    public MessageList searchMessages(String folderName, Condition condition) throws IOException {
+        return searchMessages(folderName, IMAP_MESSAGE_ATTRIBUTES, condition);
+    }
+
+    /**
+     * Search folder for messages matching conditions, with given attributes.
+     *
+     * @param folderName Exchange folder name
+     * @param attributes requested Webdav attributes
+     * @param condition  search filter
+     * @return message list
+     * @throws IOException on error
+     */
+    public abstract MessageList searchMessages(String folderName, Set<String> attributes, Condition condition) throws IOException;
+
+    /**
+     * Get server version (Exchange2003, Exchange2007 or Exchange2010)
+     *
+     * @return server version
+     */
+    public String getServerVersion() {
+        return serverVersion;
+    }
+
+    @SuppressWarnings({"JavaDoc"})
+    public enum Operator {
+        Or, And, Not, IsEqualTo,
+        IsGreaterThan, IsGreaterThanOrEqualTo,
+        IsLessThan, IsLessThanOrEqualTo,
+        IsNull, IsTrue, IsFalse,
+        Like, StartsWith, Contains
+    }
+
+    /**
+     * Exchange search filter.
+     */
+    public static interface Condition {
+        /**
+         * Append condition to buffer.
+         *
+         * @param buffer search filter buffer
+         */
+        void appendTo(StringBuilder buffer);
+
+        /**
+         * True if condition is empty.
+         *
+         * @return true if condition is empty
+         */
+        boolean isEmpty();
+
+        /**
+         * Test if the contact matches current condition.
+         *
+         * @param contact Exchange Contact
+         * @return true if contact matches condition
+         */
+        boolean isMatch(ExchangeSession.Contact contact);
+    }
+
+    /**
+     * Attribute condition.
+     */
+    public abstract static class AttributeCondition implements Condition {
+        protected final String attributeName;
+        protected final Operator operator;
+        protected final String value;
+
+        protected AttributeCondition(String attributeName, Operator operator, String value) {
+            this.attributeName = attributeName;
+            this.operator = operator;
+            this.value = value;
+        }
+
+        public boolean isEmpty() {
+            return false;
+        }
+
+        /**
+         * Get attribute name.
+         *
+         * @return attribute name
+         */
+        public String getAttributeName() {
+            return attributeName;
+        }
+
+        /**
+         * Condition value.
+         *
+         * @return value
+         */
+        public String getValue() {
+            return value;
+        }
+
+    }
+
+    /**
+     * Multiple condition.
+     */
+    public abstract static class MultiCondition implements Condition {
+        protected final Operator operator;
+        protected final List<Condition> conditions;
+
+        protected MultiCondition(Operator operator, Condition... conditions) {
+            this.operator = operator;
+            this.conditions = new ArrayList<Condition>();
+            for (Condition condition : conditions) {
+                if (condition != null) {
+                    this.conditions.add(condition);
+                }
+            }
+        }
+
+        /**
+         * Conditions list.
+         *
+         * @return conditions
+         */
+        public List<Condition> getConditions() {
+            return conditions;
+        }
+
+        /**
+         * Condition operator.
+         *
+         * @return operator
+         */
+        public Operator getOperator() {
+            return operator;
+        }
+
+        /**
+         * Add a new condition.
+         *
+         * @param condition single condition
+         */
+        public void add(Condition condition) {
+            if (condition != null) {
+                conditions.add(condition);
+            }
+        }
+
+        public boolean isEmpty() {
+            boolean isEmpty = true;
+            for (Condition condition : conditions) {
+                if (!condition.isEmpty()) {
+                    isEmpty = false;
+                    break;
+                }
+            }
+            return isEmpty;
+        }
+
+        public boolean isMatch(ExchangeSession.Contact contact) {
+            if (operator == Operator.And) {
+                for (Condition condition : conditions) {
+                    if (!condition.isMatch(contact)) {
+                        return false;
+                    }
+                }
+                return true;
+            } else if (operator == Operator.Or) {
+                for (Condition condition : conditions) {
+                    if (condition.isMatch(contact)) {
+                        return true;
+                    }
+                }
+                return false;
+            } else {
+                return false;
+            }
+        }
+
+    }
+
+    /**
+     * Not condition.
+     */
+    public abstract static class NotCondition implements Condition {
+        protected final Condition condition;
+
+        protected NotCondition(Condition condition) {
+            this.condition = condition;
+        }
+
+        public boolean isEmpty() {
+            return condition.isEmpty();
+        }
+
+        public boolean isMatch(ExchangeSession.Contact contact) {
+            return !condition.isMatch(contact);
+        }
+    }
+
+    /**
+     * Single search filter condition.
+     */
+    public abstract static class MonoCondition implements Condition {
+        protected final String attributeName;
+        protected final Operator operator;
+
+        protected MonoCondition(String attributeName, Operator operator) {
+            this.attributeName = attributeName;
+            this.operator = operator;
+        }
+
+        public boolean isEmpty() {
+            return false;
+        }
+
+        public boolean isMatch(ExchangeSession.Contact contact) {
+            String actualValue = contact.get(attributeName);
+            return (operator == Operator.IsNull && actualValue == null) ||
+                    (operator == Operator.IsFalse && "false".equals(actualValue)) ||
+                    (operator == Operator.IsTrue && "true".equals(actualValue));
+        }
+    }
+
+    /**
+     * And search filter.
+     *
+     * @param condition search conditions
+     * @return condition
+     */
+    public abstract MultiCondition and(Condition... condition);
+
+    /**
+     * Or search filter.
+     *
+     * @param condition search conditions
+     * @return condition
+     */
+    public abstract MultiCondition or(Condition... condition);
+
+    /**
+     * Not search filter.
+     *
+     * @param condition search condition
+     * @return condition
+     */
+    public abstract Condition not(Condition condition);
+
+    /**
+     * Equals condition.
+     *
+     * @param attributeName logical Exchange attribute name
+     * @param value         attribute value
+     * @return condition
+     */
+    public abstract Condition isEqualTo(String attributeName, String value);
+
+    /**
+     * Equals condition.
+     *
+     * @param attributeName logical Exchange attribute name
+     * @param value         attribute value
+     * @return condition
+     */
+    public abstract Condition isEqualTo(String attributeName, int value);
+
+    /**
+     * MIME header equals condition.
+     *
+     * @param headerName MIME header name
+     * @param value      attribute value
+     * @return condition
+     */
+    public abstract Condition headerIsEqualTo(String headerName, String value);
+
+    /**
+     * Greater than or equals condition.
+     *
+     * @param attributeName logical Exchange attribute name
+     * @param value         attribute value
+     * @return condition
+     */
+    public abstract Condition gte(String attributeName, String value);
+
+    /**
+     * Greater than condition.
+     *
+     * @param attributeName logical Exchange attribute name
+     * @param value         attribute value
+     * @return condition
+     */
+    public abstract Condition gt(String attributeName, String value);
+
+    /**
+     * Lower than condition.
+     *
+     * @param attributeName logical Exchange attribute name
+     * @param value         attribute value
+     * @return condition
+     */
+    public abstract Condition lt(String attributeName, String value);
+
+    /**
+     * Lower than or equals condition.
+     *
+     * @param attributeName logical Exchange attribute name
+     * @param value         attribute value
+     * @return condition
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    public abstract Condition lte(String attributeName, String value);
+
+    /**
+     * Contains condition.
+     *
+     * @param attributeName logical Exchange attribute name
+     * @param value         attribute value
+     * @return condition
+     */
+    public abstract Condition contains(String attributeName, String value);
+
+    /**
+     * Starts with condition.
+     *
+     * @param attributeName logical Exchange attribute name
+     * @param value         attribute value
+     * @return condition
+     */
+    public abstract Condition startsWith(String attributeName, String value);
+
+    /**
+     * Is null condition.
+     *
+     * @param attributeName logical Exchange attribute name
+     * @return condition
+     */
+    public abstract Condition isNull(String attributeName);
+
+    /**
+     * Is true condition.
+     *
+     * @param attributeName logical Exchange attribute name
+     * @return condition
+     */
+    public abstract Condition isTrue(String attributeName);
+
+    /**
+     * Is false condition.
+     *
+     * @param attributeName logical Exchange attribute name
+     * @return condition
+     */
+    public abstract Condition isFalse(String attributeName);
+
+    /**
+     * Search mail and generic folders under given folder.
+     * Exclude calendar and contacts folders
+     *
+     * @param folderName Exchange folder name
+     * @param recursive  deep search if true
+     * @return list of folders
+     * @throws IOException on error
+     */
+    public List<Folder> getSubFolders(String folderName, boolean recursive) throws IOException {
+        Condition folderCondition = null;
+        if (!Settings.getBooleanProperty("davmail.imapIncludeSpecialFolders", false)) {
+            folderCondition = or(isEqualTo("folderclass", "IPF.Note"), isNull("folderclass"));
+        }
+        List<Folder> results = getSubFolders(folderName, folderCondition,
+                recursive);
+        // need to include base folder in recursive search, except on root
+        if (recursive && folderName.length() > 0) {
+            results.add(getFolder(folderName));
+        }
+
+        return results;
+    }
+
+    /**
+     * Search calendar folders under given folder.
+     *
+     * @param folderName Exchange folder name
+     * @param recursive  deep search if true
+     * @return list of folders
+     * @throws IOException on error
+     */
+    public List<Folder> getSubCalendarFolders(String folderName, boolean recursive) throws IOException {
+        return getSubFolders(folderName, isEqualTo("folderclass", "IPF.Appointment"), recursive);
+    }
+
+    /**
+     * Search folders under given folder matching filter.
+     *
+     * @param folderName Exchange folder name
+     * @param condition  search filter
+     * @param recursive  deep search if true
+     * @return list of folders
+     * @throws IOException on error
+     */
+    public abstract List<Folder> getSubFolders(String folderName, Condition condition, boolean recursive) throws IOException;
+
+    /**
+     * Delete oldest messages in trash.
+     * keepDelay is the number of days to keep messages in trash before delete
+     *
+     * @throws IOException when unable to purge messages
+     */
+    public void purgeOldestTrashAndSentMessages() throws IOException {
+        int keepDelay = Settings.getIntProperty("davmail.keepDelay");
+        if (keepDelay != 0) {
+            purgeOldestFolderMessages(TRASH, keepDelay);
+        }
+        // this is a new feature, default is : do nothing
+        int sentKeepDelay = Settings.getIntProperty("davmail.sentKeepDelay");
+        if (sentKeepDelay != 0) {
+            purgeOldestFolderMessages(SENT, sentKeepDelay);
+        }
+    }
+
+    protected void purgeOldestFolderMessages(String folderPath, int keepDelay) throws IOException {
+        Calendar cal = Calendar.getInstance();
+        cal.add(Calendar.DAY_OF_MONTH, -keepDelay);
+        LOGGER.debug("Delete messages in " + folderPath + " not modified since " + cal.getTime());
+
+        MessageList messages = searchMessages(folderPath, UID_MESSAGE_ATTRIBUTES,
+                lt("lastmodified", formatSearchDate(cal.getTime())));
+
+        for (Message message : messages) {
+            message.delete();
+        }
+    }
+
+    protected void convertResentHeader(MimeMessage mimeMessage, String headerName) throws MessagingException {
+        String[] resentHeader = mimeMessage.getHeader("Resent-" + headerName);
+        if (resentHeader != null) {
+            mimeMessage.removeHeader("Resent-" + headerName);
+            mimeMessage.removeHeader(headerName);
+            for (String value : resentHeader) {
+                mimeMessage.addHeader(headerName, value);
+            }
+        }
+    }
+
+    protected String lastSentMessageId;
+
+    /**
+     * Send message in reader to recipients.
+     * Detect visible recipients in message body to determine bcc recipients
+     *
+     * @param rcptToRecipients recipients list
+     * @param mimeMessage      mime message
+     * @throws IOException        on error
+     * @throws MessagingException on error
+     */
+    public void sendMessage(List<String> rcptToRecipients, MimeMessage mimeMessage) throws IOException, MessagingException {
+        // detect duplicate send command
+        String messageId = mimeMessage.getMessageID();
+        if (lastSentMessageId != null && lastSentMessageId.equals(messageId)) {
+            LOGGER.debug("Dropping message id " + messageId + ": already sent");
+            return;
+        }
+        lastSentMessageId = messageId;
+
+        convertResentHeader(mimeMessage, "From");
+        convertResentHeader(mimeMessage, "To");
+        convertResentHeader(mimeMessage, "Cc");
+        convertResentHeader(mimeMessage, "Bcc");
+        convertResentHeader(mimeMessage, "Message-Id");
+
+        mimeMessage.removeHeader("From");
+
+        // remove visible recipients from list
+        Set<String> visibleRecipients = new HashSet<String>();
+        List<InternetAddress> recipients = getAllRecipients(mimeMessage);
+        for (InternetAddress address : recipients) {
+            visibleRecipients.add((address.getAddress().toLowerCase()));
+        }
+        for (String recipient : rcptToRecipients) {
+            if (!visibleRecipients.contains(recipient.toLowerCase())) {
+                mimeMessage.addRecipient(javax.mail.Message.RecipientType.BCC, new InternetAddress(recipient));
+            }
+        }
+        sendMessage(mimeMessage);
+
+    }
+
+    static final String[] RECIPIENT_HEADERS = {"to", "cc", "bcc"};
+
+    protected List<InternetAddress> getAllRecipients(MimeMessage mimeMessage) throws MessagingException {
+        List<InternetAddress> recipientList = new ArrayList<InternetAddress>();
+        for (String recipientHeader : RECIPIENT_HEADERS) {
+            final String recipientHeaderValue = mimeMessage.getHeader(recipientHeader, ",");
+            if (recipientHeaderValue != null) {
+                // parse headers in non strict mode
+                recipientList.addAll(Arrays.asList(InternetAddress.parseHeader(recipientHeaderValue, false)));
+            }
+
+        }
+        return recipientList;
+    }
+
+    /**
+     * Send Mime message.
+     *
+     * @param mimeMessage MIME message
+     * @throws IOException        on error
+     * @throws MessagingException on error
+     */
+    public abstract void sendMessage(MimeMessage mimeMessage) throws IOException, MessagingException;
+
+    /**
+     * Get folder object.
+     * Folder name can be logical names INBOX, Drafts, Trash or calendar,
+     * or a path relative to user base folder or absolute path.
+     *
+     * @param folderPath folder path
+     * @return Folder object
+     * @throws IOException on error
+     */
+    public ExchangeSession.Folder getFolder(String folderPath) throws IOException {
+        Folder folder = internalGetFolder(folderPath);
+        if (isMainCalendar(folderPath)) {
+            Folder taskFolder = internalGetFolder(TASKS);
+            folder.ctag += taskFolder.ctag;
+        }
+        return folder;
+    }
+
+    protected abstract Folder internalGetFolder(String folderName) throws IOException;
+
+    /**
+     * Check folder ctag and reload messages as needed.
+     *
+     * @param currentFolder current folder
+     * @return true if folder changed
+     * @throws IOException on error
+     */
+    public boolean refreshFolder(Folder currentFolder) throws IOException {
+        Folder newFolder = getFolder(currentFolder.folderPath);
+        if (currentFolder.ctag == null || !currentFolder.ctag.equals(newFolder.ctag)) {
+            if (LOGGER.isDebugEnabled()) {
+                LOGGER.debug("Contenttag changed on " + currentFolder.folderPath + ' '
+                        + currentFolder.ctag + " => " + newFolder.ctag + ", reloading messages");
+            }
+            currentFolder.hasChildren = newFolder.hasChildren;
+            currentFolder.noInferiors = newFolder.noInferiors;
+            currentFolder.unreadCount = newFolder.unreadCount;
+            currentFolder.ctag = newFolder.ctag;
+            currentFolder.etag = newFolder.etag;
+            currentFolder.loadMessages();
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Create Exchange message folder.
+     *
+     * @param folderName logical folder name
+     * @return status
+     * @throws IOException on error
+     */
+    public int createMessageFolder(String folderName) throws IOException {
+        return createFolder(folderName, "IPF.Note", null);
+    }
+
+    /**
+     * Create Exchange calendar folder.
+     *
+     * @param folderName logical folder name
+     * @param properties folder properties
+     * @return status
+     * @throws IOException on error
+     */
+    public int createCalendarFolder(String folderName, Map<String, String> properties) throws IOException {
+        return createFolder(folderName, "IPF.Appointment", properties);
+    }
+
+    /**
+     * Create Exchange contact folder.
+     *
+     * @param folderName logical folder name
+     * @param properties folder properties
+     * @return status
+     * @throws IOException on error
+     */
+    public int createContactFolder(String folderName, Map<String, String> properties) throws IOException {
+        return createFolder(folderName, "IPF.Contact", properties);
+    }
+
+    /**
+     * Create Exchange folder with given folder class.
+     *
+     * @param folderName  logical folder name
+     * @param folderClass folder class
+     * @param properties  folder properties
+     * @return status
+     * @throws IOException on error
+     */
+    public abstract int createFolder(String folderName, String folderClass, Map<String, String> properties) throws IOException;
+
+    /**
+     * Update Exchange folder properties.
+     *
+     * @param folderName logical folder name
+     * @param properties folder properties
+     * @return status
+     * @throws IOException on error
+     */
+    public abstract int updateFolder(String folderName, Map<String, String> properties) throws IOException;
+
+    /**
+     * Delete Exchange folder.
+     *
+     * @param folderName logical folder name
+     * @throws IOException on error
+     */
+    public abstract void deleteFolder(String folderName) throws IOException;
+
+    /**
+     * Copy message to target folder
+     *
+     * @param message      Exchange message
+     * @param targetFolder target folder
+     * @throws IOException on error
+     */
+    public abstract void copyMessage(Message message, String targetFolder) throws IOException;
+
+    /**
+     * Move message to target folder
+     *
+     * @param message      Exchange message
+     * @param targetFolder target folder
+     * @throws IOException on error
+     */
+    public abstract void moveMessage(Message message, String targetFolder) throws IOException;
+
+    /**
+     * Move folder to target name.
+     *
+     * @param folderName current folder name/path
+     * @param targetName target folder name/path
+     * @throws IOException on error
+     */
+    public abstract void moveFolder(String folderName, String targetName) throws IOException;
+
+    /**
+     * Move item from source path to target path.
+     *
+     * @param sourcePath item source path
+     * @param targetPath item target path
+     * @throws IOException on error
+     */
+    public abstract void moveItem(String sourcePath, String targetPath) throws IOException;
+
+    protected abstract void moveToTrash(Message message) throws IOException;
+
+    /**
+     * Exchange folder with IMAP properties
+     */
+    public class Folder {
+        /**
+         * Logical (IMAP) folder path.
+         */
+        public String folderPath;
+
+        /**
+         * Display Name.
+         */
+        public String displayName;
+        /**
+         * Folder class (PR_CONTAINER_CLASS).
+         */
+        public String folderClass;
+        /**
+         * Folder unread message count.
+         */
+        public int unreadCount;
+        /**
+         * true if folder has subfolders (DAV:hassubs).
+         */
+        public boolean hasChildren;
+        /**
+         * true if folder has no subfolders (DAV:nosubs).
+         */
+        public boolean noInferiors;
+        /**
+         * Folder content tag (to detect folder content changes).
+         */
+        public String ctag;
+        /**
+         * Folder etag (to detect folder object changes).
+         */
+        public String etag;
+        /**
+         * Next IMAP uid
+         */
+        public int uidNext;
+        /**
+         * recent count
+         */
+        public int recent;
+
+        /**
+         * Folder message list, empty before loadMessages call.
+         */
+        public ExchangeSession.MessageList messages;
+        /**
+         * Permanent uid (PR_SEARCH_KEY) to IMAP UID map.
+         */
+        private final HashMap<String, Long> permanentUrlToImapUidMap = new HashMap<String, Long>();
+
+        /**
+         * Get IMAP folder flags.
+         *
+         * @return folder flags in IMAP format
+         */
+        public String getFlags() {
+            if (noInferiors) {
+                return "\\NoInferiors";
+            } else if (hasChildren) {
+                return "\\HasChildren";
+            } else {
+                return "\\HasNoChildren";
+            }
+        }
+
+        /**
+         * Load folder messages.
+         *
+         * @throws IOException on error
+         */
+        public void loadMessages() throws IOException {
+            messages = ExchangeSession.this.searchMessages(folderPath, null);
+            fixUids(messages);
+            recent = 0;
+            for (Message message : messages) {
+                if (message.recent) {
+                    recent++;
+                }
+            }
+        }
+
+        /**
+         * Search messages in folder matching query.
+         *
+         * @param condition search query
+         * @return message list
+         * @throws IOException on error
+         */
+        public MessageList searchMessages(Condition condition) throws IOException {
+            MessageList localMessages = ExchangeSession.this.searchMessages(folderPath, condition);
+            fixUids(localMessages);
+            return localMessages;
+        }
+
+        /**
+         * Restore previous uids changed by a PROPPATCH (flag change).
+         *
+         * @param messages message list
+         */
+        protected void fixUids(MessageList messages) {
+            boolean sortNeeded = false;
+            for (Message message : messages) {
+                if (permanentUrlToImapUidMap.containsKey(message.getPermanentId())) {
+                    long previousUid = permanentUrlToImapUidMap.get(message.getPermanentId());
+                    if (message.getImapUid() != previousUid) {
+                        LOGGER.debug("Restoring IMAP uid " + message.getImapUid() + " -> " + previousUid + " for message " + message.getPermanentId());
+                        message.setImapUid(previousUid);
+                        sortNeeded = true;
+                    }
+                } else {
+                    // add message to uid map
+                    permanentUrlToImapUidMap.put(message.getPermanentId(), message.getImapUid());
+                }
+            }
+            if (sortNeeded) {
+                Collections.sort(messages);
+            }
+        }
+
+        /**
+         * Folder message count.
+         *
+         * @return message count
+         */
+        public int count() {
+            return messages.size();
+        }
+
+        /**
+         * Compute IMAP uidnext.
+         *
+         * @return max(messageuids)+1
+         */
+        public long getUidNext() {
+            return messages.get(messages.size() - 1).getImapUid() + 1;
+        }
+
+        /**
+         * Get message at index.
+         *
+         * @param index message index
+         * @return message
+         */
+        public Message get(int index) {
+            return messages.get(index);
+        }
+
+        /**
+         * Get current folder messages imap uids
+         *
+         * @return imap uid list
+         */
+        public List<Long> getImapUidList() {
+            ArrayList<Long> imapUidList = new ArrayList<Long>();
+            for (ExchangeSession.Message message : messages) {
+                imapUidList.add(message.getImapUid());
+            }
+            return imapUidList;
+        }
+
+        /**
+         * Calendar folder flag.
+         *
+         * @return true if this is a calendar folder
+         */
+        public boolean isCalendar() {
+            return "IPF.Appointment".equals(folderClass);
+        }
+
+        /**
+         * Contact folder flag.
+         *
+         * @return true if this is a calendar folder
+         */
+        public boolean isContact() {
+            return "IPF.Contact".equals(folderClass);
+        }
+
+        /**
+         * Task folder flag.
+         *
+         * @return true if this is a task folder
+         */
+        public boolean isTask() {
+            return "IPF.Task".equals(folderClass);
+        }
+
+        /**
+         * drop cached message
+         */
+        public void clearCache() {
+            messages.cachedMimeBody = null;
+            messages.cachedMimeMessage = null;
+            messages.cachedMessageImapUid = 0;
+        }
+    }
+
+    /**
+     * Exchange message.
+     */
+    public abstract class Message implements Comparable<Message> {
+        /**
+         * enclosing message list
+         */
+        public MessageList messageList;
+        /**
+         * Message url.
+         */
+        public String messageUrl;
+        /**
+         * Message permanent url (does not change on message move).
+         */
+        public String permanentUrl;
+        /**
+         * Message uid.
+         */
+        public String uid;
+        /**
+         * Message content class.
+         */
+        public String contentClass;
+        /**
+         * Message IMAP uid, unique in folder (x0e230003).
+         */
+        public long imapUid;
+        /**
+         * MAPI message size.
+         */
+        public int size;
+        /**
+         * Message date (urn:schemas:mailheader:date).
+         */
+        public String date;
+
+        /**
+         * Message flag: read.
+         */
+        public boolean read;
+        /**
+         * Message flag: deleted.
+         */
+        public boolean deleted;
+        /**
+         * Message flag: junk.
+         */
+        public boolean junk;
+        /**
+         * Message flag: flagged.
+         */
+        public boolean flagged;
+        /**
+         * Message flag: recent.
+         */
+        public boolean recent;
+        /**
+         * Message flag: draft.
+         */
+        public boolean draft;
+        /**
+         * Message flag: answered.
+         */
+        public boolean answered;
+        /**
+         * Message flag: fowarded.
+         */
+        public boolean forwarded;
+
+        /**
+         * Unparsed message content.
+         */
+        protected SharedByteArrayInputStream mimeBody;
+
+        /**
+         * Message content parsed in a MIME message.
+         */
+        protected MimeMessage mimeMessage;
+
+        /**
+         * Get permanent message id.
+         * permanentUrl over WebDav or IitemId over EWS
+         *
+         * @return permanent id
+         */
+        public abstract String getPermanentId();
+
+        /**
+         * IMAP uid , unique in folder (x0e230003)
+         *
+         * @return IMAP uid
+         */
+        public long getImapUid() {
+            return imapUid;
+        }
+
+        /**
+         * Set IMAP uid.
+         *
+         * @param imapUid new uid
+         */
+        public void setImapUid(long imapUid) {
+            this.imapUid = imapUid;
+        }
+
+        /**
+         * Exchange uid.
+         *
+         * @return uid
+         */
+        public String getUid() {
+            return uid;
+        }
+
+        /**
+         * Return message flags in IMAP format.
+         *
+         * @return IMAP flags
+         */
+        public String getImapFlags() {
+            StringBuilder buffer = new StringBuilder();
+            if (read) {
+                buffer.append("\\Seen ");
+            }
+            if (deleted) {
+                buffer.append("\\Deleted ");
+            }
+            if (recent) {
+                buffer.append("\\Recent ");
+            }
+            if (flagged) {
+                buffer.append("\\Flagged ");
+            }
+            if (junk) {
+                buffer.append("Junk ");
+            }
+            if (draft) {
+                buffer.append("\\Draft ");
+            }
+            if (answered) {
+                buffer.append("\\Answered ");
+            }
+            if (forwarded) {
+                buffer.append("$Forwarded ");
+            }
+            return buffer.toString().trim();
+        }
+
+        /**
+         * Load message content in a Mime message
+         *
+         * @throws IOException        on error
+         * @throws MessagingException on error
+         */
+        protected void loadMimeMessage() throws IOException, MessagingException {
+            if (mimeMessage == null) {
+                // try to get message content from cache
+                if (this.imapUid == messageList.cachedMessageImapUid) {
+                    mimeBody = messageList.cachedMimeBody;
+                    mimeMessage = messageList.cachedMimeMessage;
+                    LOGGER.debug("Got message content for " + imapUid + " from cache");
+                } else {
+                    // load and parse message
+                    mimeBody = new SharedByteArrayInputStream(getContent(this));
+                    mimeMessage = new MimeMessage(null, mimeBody);
+                    mimeBody.reset();
+                    // workaround for Exchange 2003 ActiveSync bug
+                    if (mimeMessage.getHeader("MAIL FROM") != null) {
+                        mimeBody = (SharedByteArrayInputStream)mimeMessage.getRawInputStream();
+                        mimeMessage = new MimeMessage(null, mimeBody);
+                        mimeBody.reset();
+                    }
+                    LOGGER.debug("Downloaded full message content for IMAP UID " + imapUid + " (" + mimeBody.available() + " bytes)");
+                }
+            }
+        }
+
+        /**
+         * Get message content as a Mime message.
+         *
+         * @return mime message
+         * @throws IOException        on error
+         * @throws MessagingException on error
+         */
+        public MimeMessage getMimeMessage() throws IOException, MessagingException {
+            loadMimeMessage();
+            return mimeMessage;
+        }
+
+        public Enumeration getMatchingHeaderLines(String[] headerNames) throws MessagingException, IOException {
+            Enumeration  result = null;
+            if (mimeMessage == null) {
+                // message not loaded, try to get headers only
+                InputStream headers = getMimeHeaders();
+                if (headers != null) {
+                    result = new InternetHeaders(headers).getMatchingHeaderLines(headerNames);
+                }
+            }
+            if (result == null) {
+                result = getMimeMessage().getMatchingHeaderLines(headerNames);
+            }
+            return result;
+        }
+
+        protected abstract InputStream getMimeHeaders();
+
+        /**
+         * Get message body size.
+         *
+         * @return mime message size
+         * @throws IOException        on error
+         * @throws MessagingException on error
+         */
+        public int getMimeMessageSize() throws IOException, MessagingException {
+            loadMimeMessage();
+            mimeBody.reset();
+            return mimeBody.available();
+        }
+
+        /**
+         * Get message body input stream.
+         *
+         * @return mime message InputStream
+         * @throws IOException        on error
+         * @throws MessagingException on error
+         */
+        public InputStream getRawInputStream() throws IOException, MessagingException {
+            loadMimeMessage();
+            mimeBody.reset();
+            return mimeBody;
+        }
+
+
+        /**
+         * Drop mime message to avoid keeping message content in memory,
+         * keep a single message in MessageList cache to handle chunked fetch.
+         */
+        public void dropMimeMessage() {
+            // update single message cache
+            if (mimeMessage != null) {
+                messageList.cachedMessageImapUid = imapUid;
+                messageList.cachedMimeBody = mimeBody;
+                messageList.cachedMimeMessage = mimeMessage;
+            }
+            // drop curent message body to save memory
+            mimeMessage = null;
+            mimeBody = null;
+        }
+
+        /**
+         * Delete message.
+         *
+         * @throws IOException on error
+         */
+        public void delete() throws IOException {
+            deleteMessage(this);
+        }
+
+        /**
+         * Move message to trash, mark message read.
+         *
+         * @throws IOException on error
+         */
+        public void moveToTrash() throws IOException {
+            markRead();
+
+            ExchangeSession.this.moveToTrash(this);
+        }
+
+        /**
+         * Mark message as read.
+         *
+         * @throws IOException on error
+         */
+        public void markRead() throws IOException {
+            HashMap<String, String> properties = new HashMap<String, String>();
+            properties.put("read", "1");
+            updateMessage(this, properties);
+        }
+
+        /**
+         * Comparator to sort messages by IMAP uid
+         *
+         * @param message other message
+         * @return imapUid comparison result
+         */
+        public int compareTo(Message message) {
+            long compareValue = (imapUid - message.imapUid);
+            if (compareValue > 0) {
+                return 1;
+            } else if (compareValue < 0) {
+                return -1;
+            } else {
+                return 0;
+            }
+        }
+
+        /**
+         * Override equals, compare IMAP uids
+         *
+         * @param message other message
+         * @return true if IMAP uids are equal
+         */
+        @Override
+        public boolean equals(Object message) {
+            return message instanceof Message && imapUid == ((Message) message).imapUid;
+        }
+
+        /**
+         * Override hashCode, return imapUid hashcode.
+         *
+         * @return imapUid hashcode
+         */
+        @Override
+        public int hashCode() {
+            return (int) (imapUid ^ (imapUid >>> 32));
+        }
+    }
+
+    /**
+     * Message list, includes a single messsage cache
+     */
+    public static class MessageList extends ArrayList<Message> {
+        /**
+         * Cached message content parsed in a MIME message.
+         */
+        protected transient MimeMessage cachedMimeMessage;
+        /**
+         * Cached message uid.
+         */
+        protected transient long cachedMessageImapUid;
+        /**
+         * Cached unparsed message
+         */
+        protected transient SharedByteArrayInputStream cachedMimeBody;
+
+    }
+
+    /**
+     * Generic folder item.
+     */
+    public abstract static class Item extends HashMap<String, String> {
+        protected String folderPath;
+        protected String itemName;
+        protected String permanentUrl;
+        /**
+         * Display name.
+         */
+        public String displayName;
+        /**
+         * item etag
+         */
+        public String etag;
+        protected String noneMatch;
+
+        /**
+         * Build item instance.
+         *
+         * @param folderPath folder path
+         * @param itemName   item name class
+         * @param etag       item etag
+         * @param noneMatch  none match flag
+         */
+        public Item(String folderPath, String itemName, String etag, String noneMatch) {
+            this.folderPath = folderPath;
+            this.itemName = itemName;
+            this.etag = etag;
+            this.noneMatch = noneMatch;
+        }
+
+        /**
+         * Default constructor.
+         */
+        protected Item() {
+        }
+
+        /**
+         * Return item content type
+         *
+         * @return content type
+         */
+        public abstract String getContentType();
+
+        /**
+         * Retrieve item body from Exchange
+         *
+         * @return item body
+         * @throws HttpException on error
+         */
+        public abstract String getBody() throws IOException;
+
+        /**
+         * Get event name (file name part in URL).
+         *
+         * @return event name
+         */
+        public String getName() {
+            return itemName;
+        }
+
+        /**
+         * Get event etag (last change tag).
+         *
+         * @return event etag
+         */
+        public String getEtag() {
+            return etag;
+        }
+
+        /**
+         * Set item href.
+         *
+         * @param href item href
+         */
+        public void setHref(String href) {
+            int index = href.lastIndexOf('/');
+            if (index >= 0) {
+                folderPath = href.substring(0, index);
+                itemName = href.substring(index + 1);
+            } else {
+                throw new IllegalArgumentException(href);
+            }
+        }
+
+        /**
+         * Return item href.
+         *
+         * @return item href
+         */
+        public String getHref() {
+            return folderPath + '/' + itemName;
+        }
+    }
+
+    /**
+     * Contact object
+     */
+    public abstract class Contact extends Item {
+
+        /**
+         * @inheritDoc
+         */
+        public Contact(String folderPath, String itemName, Map<String, String> properties, String etag, String noneMatch) {
+            super(folderPath, itemName.endsWith(".vcf") ? itemName.substring(0, itemName.length() - 3) + "EML" : itemName, etag, noneMatch);
+            this.putAll(properties);
+        }
+
+        /**
+         * @inheritDoc
+         */
+        protected Contact() {
+        }
+
+        /**
+         * Convert EML extension to vcf.
+         *
+         * @return item name
+         */
+        @Override
+        public String getName() {
+            String name = super.getName();
+            if (name.endsWith(".EML")) {
+                name = name.substring(0, name.length() - 3) + "vcf";
+            }
+            return name;
+        }
+
+        /**
+         * Set contact name
+         *
+         * @param name contact name
+         */
+        public void setName(String name) {
+            this.itemName = name;
+        }
+
+        /**
+         * Compute vcard uid from name.
+         *
+         * @return uid
+         * @throws URIException on error
+         */
+        protected String getUid() throws URIException {
+            String uid = getName();
+            int dotIndex = uid.lastIndexOf('.');
+            if (dotIndex > 0) {
+                uid = uid.substring(0, dotIndex);
+            }
+            return URIUtil.encodePath(uid);
+        }
+
+        @Override
+        public String getContentType() {
+            return "text/vcard";
+        }
+
+
+        @Override
+        public String getBody() throws HttpException {
+            // build RFC 2426 VCard from contact information
+            VCardWriter writer = new VCardWriter();
+            writer.startCard();
+            writer.appendProperty("UID", getUid());
+            // common name
+            writer.appendProperty("FN", get("cn"));
+            // RFC 2426: Family Name, Given Name, Additional Names, Honorific Prefixes, and Honorific Suffixes
+            writer.appendProperty("N", get("sn"), get("givenName"), get("middlename"), get("personaltitle"), get("namesuffix"));
+
+            writer.appendProperty("TEL;TYPE=cell", get("mobile"));
+            writer.appendProperty("TEL;TYPE=work", get("telephoneNumber"));
+            writer.appendProperty("TEL;TYPE=home", get("homePhone"));
+            writer.appendProperty("TEL;TYPE=fax", get("facsimiletelephonenumber"));
+            writer.appendProperty("TEL;TYPE=pager", get("pager"));
+            writer.appendProperty("TEL;TYPE=car", get("othermobile"));
+            writer.appendProperty("TEL;TYPE=home,fax", get("homefax"));
+            writer.appendProperty("TEL;TYPE=isdn", get("internationalisdnnumber"));
+            writer.appendProperty("TEL;TYPE=msg", get("otherTelephone"));
+
+            // The structured type value corresponds, in sequence, to the post office box; the extended address;
+            // the street address; the locality (e.g., city); the region (e.g., state or province);
+            // the postal code; the country name
+            writer.appendProperty("ADR;TYPE=home",
+                    get("homepostofficebox"), null, get("homeStreet"), get("homeCity"), get("homeState"), get("homePostalCode"), get("homeCountry"));
+            writer.appendProperty("ADR;TYPE=work",
+                    get("postofficebox"), get("roomnumber"), get("street"), get("l"), get("st"), get("postalcode"), get("co"));
+            writer.appendProperty("ADR;TYPE=other",
+                    get("otherpostofficebox"), null, get("otherstreet"), get("othercity"), get("otherstate"), get("otherpostalcode"), get("othercountry"));
+
+            writer.appendProperty("EMAIL;TYPE=work", get("smtpemail1"));
+            writer.appendProperty("EMAIL;TYPE=home", get("smtpemail2"));
+            writer.appendProperty("EMAIL;TYPE=other", get("smtpemail3"));
+
+            writer.appendProperty("ORG", get("o"), get("department"));
+            writer.appendProperty("URL;TYPE=work", get("businesshomepage"));
+            writer.appendProperty("URL;TYPE=home", get("personalHomePage"));
+            writer.appendProperty("TITLE", get("title"));
+            writer.appendProperty("NOTE", get("description"));
+
+            writer.appendProperty("CUSTOM1", get("extensionattribute1"));
+            writer.appendProperty("CUSTOM2", get("extensionattribute2"));
+            writer.appendProperty("CUSTOM3", get("extensionattribute3"));
+            writer.appendProperty("CUSTOM4", get("extensionattribute4"));
+
+            writer.appendProperty("ROLE", get("profession"));
+            writer.appendProperty("NICKNAME", get("nickname"));
+            writer.appendProperty("X-AIM", get("im"));
+
+            writer.appendProperty("BDAY", convertZuluDateToBday(get("bday")));
+            writer.appendProperty("ANNIVERSARY", convertZuluDateToBday(get("anniversary")));
+
+            String gender = get("gender");
+            if ("1".equals(gender)) {
+                writer.appendProperty("SEX", "2");
+            } else if ("2".equals(gender)) {
+                writer.appendProperty("SEX", "1");
+            }
+
+            writer.appendProperty("CATEGORIES", get("keywords"));
+
+            writer.appendProperty("FBURL", get("fburl"));
+
+            if ("1".equals(get("private"))) {
+                writer.appendProperty("CLASS", "PRIVATE");
+            }
+
+            writer.appendProperty("X-ASSISTANT", get("secretarycn"));
+            writer.appendProperty("X-MANAGER", get("manager"));
+            writer.appendProperty("X-SPOUSE", get("spousecn"));
+
+            writer.appendProperty("REV", get("lastmodified"));
+
+            if ("true".equals(get("haspicture"))) {
+                try {
+                    ContactPhoto contactPhoto = getContactPhoto(this);
+                    writer.writeLine("PHOTO;BASE64;TYPE=\"" + contactPhoto.contentType + "\";ENCODING=\"b\":");
+                    writer.writeLine(contactPhoto.content, true);
+                } catch (IOException e) {
+                    LOGGER.warn("Unable to get photo from contact " + this.get("cn"));
+                }
+            }
+
+            writer.endCard();
+            return writer.toString();
+        }
+
+    }
+
+    /**
+     * Calendar event object.
+     */
+    public abstract class Event extends Item {
+        protected String contentClass;
+        protected String subject;
+        protected VCalendar vCalendar;
+
+        /**
+         * @inheritDoc
+         */
+        public Event(String folderPath, String itemName, String contentClass, String itemBody, String etag, String noneMatch) throws IOException {
+            super(folderPath, itemName, etag, noneMatch);
+            this.contentClass = contentClass;
+            fixICS(itemBody.getBytes("UTF-8"), false);
+            // fix task item name
+            if (vCalendar.isTodo() && this.itemName.endsWith(".ics")) {
+                this.itemName = itemName.substring(0, itemName.length() - 3) + "EML";
+            }
+        }
+
+        /**
+         * @inheritDoc
+         */
+        protected Event() {
+        }
+
+        @Override
+        public String getContentType() {
+            return "text/calendar;charset=UTF-8";
+        }
+
+        @Override
+        public String getBody() throws IOException {
+            if (vCalendar == null) {
+                fixICS(getEventContent(), true);
+            }
+            return vCalendar.toString();
+        }
+
+        protected HttpException buildHttpException(Exception e) {
+            String message = "Unable to get event " + getName() + " subject: " + subject + " at " + permanentUrl + ": " + e.getMessage();
+            LOGGER.warn(message);
+            return new HttpException(message);
+        }
+
+        /**
+         * Retrieve item body from Exchange
+         *
+         * @return item content
+         * @throws HttpException on error
+         */
+        public abstract byte[] getEventContent() throws IOException;
+
+        protected static final String TEXT_CALENDAR = "text/calendar";
+        protected static final String APPLICATION_ICS = "application/ics";
+
+        protected boolean isCalendarContentType(String contentType) {
+            return TEXT_CALENDAR.regionMatches(true, 0, contentType, 0, TEXT_CALENDAR.length()) ||
+                    APPLICATION_ICS.regionMatches(true, 0, contentType, 0, APPLICATION_ICS.length());
+        }
+
+        protected MimePart getCalendarMimePart(MimeMultipart multiPart) throws IOException, MessagingException {
+            MimePart bodyPart = null;
+            for (int i = 0; i < multiPart.getCount(); i++) {
+                String contentType = multiPart.getBodyPart(i).getContentType();
+                if (isCalendarContentType(contentType)) {
+                    bodyPart = (MimePart) multiPart.getBodyPart(i);
+                    break;
+                } else if (contentType.startsWith("multipart")) {
+                    Object content = multiPart.getBodyPart(i).getContent();
+                    if (content instanceof MimeMultipart) {
+                        bodyPart = getCalendarMimePart((MimeMultipart) content);
+                    }
+                }
+            }
+
+            return bodyPart;
+        }
+
+        /**
+         * Load ICS content from MIME message input stream
+         *
+         * @param mimeInputStream mime message input stream
+         * @return mime message ics attachment body
+         * @throws IOException        on error
+         * @throws MessagingException on error
+         */
+        protected byte[] getICS(InputStream mimeInputStream) throws IOException, MessagingException {
+            byte[] result;
+            MimeMessage mimeMessage = new MimeMessage(null, mimeInputStream);
+            String[] contentClassHeader = mimeMessage.getHeader("Content-class");
+            // task item, return null
+            if (contentClassHeader != null && contentClassHeader.length > 0 && "urn:content-classes:task".equals(contentClassHeader[0])) {
+                return null;
+            }
+            Object mimeBody = mimeMessage.getContent();
+            MimePart bodyPart = null;
+            if (mimeBody instanceof MimeMultipart) {
+                bodyPart = getCalendarMimePart((MimeMultipart) mimeBody);
+            } else if (isCalendarContentType(mimeMessage.getContentType())) {
+                // no multipart, single body
+                bodyPart = mimeMessage;
+            }
+
+            if (bodyPart != null) {
+                ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                bodyPart.getDataHandler().writeTo(baos);
+                baos.close();
+                result = baos.toByteArray();
+            } else {
+                ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                mimeMessage.writeTo(baos);
+                baos.close();
+                throw new DavMailException("EXCEPTION_INVALID_MESSAGE_CONTENT", new String(baos.toByteArray(), "UTF-8"));
+            }
+            return result;
+        }
+
+        protected void fixICS(byte[] icsContent, boolean fromServer) throws IOException {
+            if (LOGGER.isDebugEnabled() && fromServer) {
+                dumpIndex++;
+                String icsBody = new String(icsContent);
+                dumpICS(icsBody, fromServer, false);
+                LOGGER.debug("Vcalendar body received from server:\n" + icsBody);
+            }
+            vCalendar = new VCalendar(icsContent, getEmail(), getVTimezone());
+            vCalendar.fixVCalendar(fromServer);
+            if (LOGGER.isDebugEnabled() && !fromServer) {
+                String resultString = vCalendar.toString();
+                LOGGER.debug("Fixed Vcalendar body to server:\n" + resultString);
+                dumpICS(resultString, fromServer, true);
+            }
+        }
+
+        protected void dumpICS(String icsBody, boolean fromServer, boolean after) {
+            String logFileDirectory = Settings.getLogFileDirectory();
+
+            // additional setting to activate ICS dump (not available in GUI)
+            int dumpMax = Settings.getIntProperty("davmail.dumpICS");
+            if (dumpMax > 0) {
+                if (dumpIndex > dumpMax) {
+                    // Delete the oldest dump file
+                    final int oldest = dumpIndex - dumpMax;
+                    try {
+                        File[] oldestFiles = (new File(logFileDirectory)).listFiles(new FilenameFilter() {
+                            public boolean accept(File dir, String name) {
+                                if (name.endsWith(".ics")) {
+                                    int dashIndex = name.indexOf('-');
+                                    if (dashIndex > 0) {
+                                        try {
+                                            int fileIndex = Integer.parseInt(name.substring(0, dashIndex));
+                                            return fileIndex < oldest;
+                                        } catch (NumberFormatException nfe) {
+                                            // ignore
+                                        }
+                                    }
+                                }
+                                return false;
+                            }
+                        });
+                        for (File file : oldestFiles) {
+                            if (!file.delete()) {
+                                LOGGER.warn("Unable to delete " + file.getAbsolutePath());
+                            }
+                        }
+                    } catch (Exception ex) {
+                        LOGGER.warn("Error deleting ics dump: " + ex.getMessage());
+                    }
+                }
+
+                StringBuilder filePath = new StringBuilder();
+                filePath.append(logFileDirectory).append('/')
+                        .append(dumpIndex)
+                        .append(after ? "-to" : "-from")
+                        .append((after ^ fromServer) ? "-server" : "-client")
+                        .append(".ics");
+                if ((icsBody != null) && (icsBody.length() > 0)) {
+                    FileWriter fileWriter = null;
+                    try {
+                        fileWriter = new FileWriter(filePath.toString());
+                        fileWriter.write(icsBody);
+                    } catch (IOException e) {
+                        LOGGER.error(e);
+                    } finally {
+                        if (fileWriter != null) {
+                            try {
+                                fileWriter.close();
+                            } catch (IOException e) {
+                                LOGGER.error(e);
+                            }
+                        }
+                    }
+
+
+                }
+            }
+
+        }
+
+        /**
+         * Build Mime body for event or event message.
+         *
+         * @return mimeContent as byte array or null
+         * @throws IOException on error
+         */
+        public byte[] createMimeContent() throws IOException {
+            String boundary = UUID.randomUUID().toString();
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            MimeOutputStreamWriter writer = new MimeOutputStreamWriter(baos);
+
+            writer.writeHeader("Content-Transfer-Encoding", "7bit");
+            writer.writeHeader("Content-class", contentClass);
+            // append date
+            writer.writeHeader("Date", new Date());
+
+            // Make sure invites have a proper subject line
+            String vEventSubject = vCalendar.getFirstVeventPropertyValue("SUMMARY");
+            if (vEventSubject == null) {
+                vEventSubject = BundleMessage.format("MEETING_REQUEST");
+            }
+
+            // Write a part of the message that contains the
+            // ICS description so that invites contain the description text
+            String description = vCalendar.getFirstVeventPropertyValue("DESCRIPTION");
+
+            // handle notifications
+            if ("urn:content-classes:calendarmessage".equals(contentClass)) {
+                // need to parse attendees and organizer to build recipients
+                VCalendar.Recipients recipients = vCalendar.getRecipients(true);
+                String to;
+                String cc;
+                String notificationSubject;
+                if (email.equalsIgnoreCase(recipients.organizer)) {
+                    // current user is organizer => notify all
+                    to = recipients.attendees;
+                    cc = recipients.optionalAttendees;
+                    notificationSubject = subject;
+                } else {
+                    String status = vCalendar.getAttendeeStatus();
+                    // notify only organizer
+                    to = recipients.organizer;
+                    cc = null;
+                    notificationSubject = (status != null) ? (BundleMessage.format(status) + vEventSubject) : subject;
+                    description = "";
+                }
+
+                // Allow end user notification edit
+                if (Settings.getBooleanProperty("davmail.caldavEditNotifications")) {
+                    // create notification edit dialog
+                    NotificationDialog notificationDialog = new NotificationDialog(to,
+                            cc, notificationSubject, description);
+                    if (!notificationDialog.getSendNotification()) {
+                        LOGGER.debug("Notification canceled by user");
+                        return null;
+                    }
+                    // get description from dialog
+                    to = notificationDialog.getTo();
+                    cc = notificationDialog.getCc();
+                    notificationSubject = notificationDialog.getSubject();
+                    description = notificationDialog.getBody();
+                }
+
+                // do not send notification if no recipients found
+                if ((to == null || to.length() == 0) && (cc == null || cc.length() == 0)) {
+                    return null;
+                }
+
+                writer.writeHeader("To", to);
+                writer.writeHeader("Cc", cc);
+                writer.writeHeader("Subject", notificationSubject);
+
+
+                if (LOGGER.isDebugEnabled()) {
+                    StringBuilder logBuffer = new StringBuilder("Sending notification ");
+                    if (to != null) {
+                        logBuffer.append("to: ").append(to);
+                    }
+                    if (cc != null) {
+                        logBuffer.append("cc: ").append(cc);
+                    }
+                    LOGGER.debug(logBuffer.toString());
+                }
+            } else {
+                // need to parse attendees and organizer to build recipients
+                VCalendar.Recipients recipients = vCalendar.getRecipients(false);
+                // storing appointment, full recipients header
+                if (recipients.attendees != null) {
+                    writer.writeHeader("To", recipients.attendees);
+                } else {
+                    // use current user as attendee
+                    writer.writeHeader("To", email);
+                }
+                writer.writeHeader("Cc", recipients.optionalAttendees);
+
+                if (recipients.organizer != null) {
+                    writer.writeHeader("From", recipients.organizer);
+                } else {
+                    writer.writeHeader("From", email);
+                }
+            }
+            if (vCalendar.getMethod() == null) {
+                vCalendar.setPropertyValue("METHOD", "REQUEST");
+            }
+            writer.writeHeader("MIME-Version", "1.0");
+            writer.writeHeader("Content-Type", "multipart/alternative;\r\n" +
+                    "\tboundary=\"----=_NextPart_" + boundary + '\"');
+            writer.writeLn();
+            writer.writeLn("This is a multi-part message in MIME format.");
+            writer.writeLn();
+            writer.writeLn("------=_NextPart_" + boundary);
+
+            if (description != null && description.length() > 0) {
+                writer.writeHeader("Content-Type", "text/plain;\r\n" +
+                        "\tcharset=\"utf-8\"");
+                writer.writeHeader("content-transfer-encoding", "8bit");
+                writer.writeLn();
+                writer.flush();
+                baos.write(description.getBytes("UTF-8"));
+                writer.writeLn();
+                writer.writeLn("------=_NextPart_" + boundary);
+            }
+            writer.writeHeader("Content-class", contentClass);
+            writer.writeHeader("Content-Type", "text/calendar;\r\n" +
+                    "\tmethod=" + vCalendar.getMethod() + ";\r\n" +
+                    "\tcharset=\"utf-8\""
+            );
+            writer.writeHeader("Content-Transfer-Encoding", "8bit");
+            writer.writeLn();
+            writer.flush();
+            baos.write(vCalendar.toString().getBytes("UTF-8"));
+            writer.writeLn();
+            writer.writeLn("------=_NextPart_" + boundary + "--");
+            writer.close();
+            return baos.toByteArray();
+        }
+
+        /**
+         * Create or update item
+         *
+         * @return action result
+         * @throws IOException on error
+         */
+        public abstract ItemResult createOrUpdate() throws IOException;
+
+    }
+
+    protected abstract Set<String> getItemProperties();
+
+    /**
+     * Search contacts in provided folder.
+     *
+     * @param folderPath Exchange folder path
+     * @return list of contacts
+     * @throws IOException on error
+     */
+    public List<ExchangeSession.Contact> getAllContacts(String folderPath) throws IOException {
+        return searchContacts(folderPath, ExchangeSession.CONTACT_ATTRIBUTES, isEqualTo("outlookmessageclass", "IPM.Contact"), 0);
+    }
+
+
+    /**
+     * Search contacts in provided folder matching the search query.
+     *
+     * @param folderPath Exchange folder path
+     * @param attributes requested attributes
+     * @param condition  Exchange search query
+     * @param maxCount   maximum item count
+     * @return list of contacts
+     * @throws IOException on error
+     */
+    public abstract List<Contact> searchContacts(String folderPath, Set<String> attributes, Condition condition, int maxCount) throws IOException;
+
+    /**
+     * Search calendar messages in provided folder.
+     *
+     * @param folderPath Exchange folder path
+     * @return list of calendar messages as Event objects
+     * @throws IOException on error
+     */
+    public abstract List<Event> getEventMessages(String folderPath) throws IOException;
+
+    /**
+     * Search calendar events in provided folder.
+     *
+     * @param folderPath Exchange folder path
+     * @return list of calendar events
+     * @throws IOException on error
+     */
+    public List<Event> getAllEvents(String folderPath) throws IOException {
+        List<Event> results = searchEvents(folderPath, getCalendarItemCondition(getPastDelayCondition("dtstart")));
+
+        if (!Settings.getBooleanProperty("davmail.caldavDisableTasks", false) && isMainCalendar(folderPath)) {
+            // retrieve tasks from main tasks folder
+            results.addAll(searchTasksOnly(TASKS));
+        }
+
+        return results;
+    }
+
+    protected abstract Condition getCalendarItemCondition(Condition dateCondition);
+
+    protected Condition getPastDelayCondition(String attribute) {
+        int caldavPastDelay = Settings.getIntProperty("davmail.caldavPastDelay");
+        Condition dateCondition = null;
+        if (caldavPastDelay != 0) {
+            Calendar cal = Calendar.getInstance();
+            cal.add(Calendar.DAY_OF_MONTH, -caldavPastDelay);
+            dateCondition = gt(attribute, formatSearchDate(cal.getTime()));
+        }
+        return dateCondition;
+    }
+
+    protected Condition getRangeCondition(String timeRangeStart, String timeRangeEnd) throws IOException {
+        try {
+            SimpleDateFormat parser = getZuluDateFormat();
+            ExchangeSession.MultiCondition andCondition = and();
+            if (timeRangeStart != null) {
+                andCondition.add(gt("dtend", formatSearchDate(parser.parse(timeRangeStart))));
+            }
+            if (timeRangeEnd != null) {
+                andCondition.add(lt("dtstart", formatSearchDate(parser.parse(timeRangeEnd))));
+            }
+            return andCondition;
+        } catch (ParseException e) {
+            throw new IOException(e+" "+e.getMessage());
+        }
+    }
+
+    /**
+     * Search events between start and end.
+     *
+     * @param folderPath     Exchange folder path
+     * @param timeRangeStart date range start in zulu format
+     * @param timeRangeEnd   date range start in zulu format
+     * @return list of calendar events
+     * @throws IOException on error
+     */
+    public List<Event> searchEvents(String folderPath, String timeRangeStart, String timeRangeEnd) throws IOException {
+        Condition dateCondition = getRangeCondition(timeRangeStart, timeRangeEnd);
+        Condition condition = getCalendarItemCondition(dateCondition);
+
+        return searchEvents(folderPath, condition);
+    }
+
+    /**
+     * Search events between start and end, exclude tasks.
+     *
+     * @param folderPath     Exchange folder path
+     * @param timeRangeStart date range start in zulu format
+     * @param timeRangeEnd   date range start in zulu format
+     * @return list of calendar events
+     * @throws IOException on error
+     */
+    public List<Event> searchEventsOnly(String folderPath, String timeRangeStart, String timeRangeEnd) throws IOException {
+        Condition dateCondition = getRangeCondition(timeRangeStart, timeRangeEnd);
+        return searchEvents(folderPath, getCalendarItemCondition(dateCondition));
+    }
+
+    /**
+     * Search tasks only (VTODO).
+     *
+     * @param folderPath Exchange folder path
+     * @return list of tasks
+     * @throws IOException on error
+     */
+    public List<Event> searchTasksOnly(String folderPath) throws IOException {
+        return searchEvents(folderPath, and(isEqualTo("outlookmessageclass", "IPM.Task"),
+                or(isNull("datecompleted"), getPastDelayCondition("datecompleted"))));
+    }
+
+    /**
+     * Search calendar events in provided folder.
+     *
+     * @param folderPath Exchange folder path
+     * @param filter     search filter
+     * @return list of calendar events
+     * @throws IOException on error
+     */
+    public List<Event> searchEvents(String folderPath, Condition filter) throws IOException {
+
+        Condition privateCondition = null;
+        if (isSharedFolder(folderPath) && Settings.getBooleanProperty("davmail.excludePrivateEvents", true)) {
+            LOGGER.debug("Shared or public calendar: exclude private events");
+            privateCondition = isEqualTo("sensitivity", 0);
+        }
+
+        return searchEvents(folderPath, getItemProperties(),
+                and(filter, privateCondition));
+    }
+
+    /**
+     * Search calendar events or messages in provided folder matching the search query.
+     *
+     * @param folderPath Exchange folder path
+     * @param attributes requested attributes
+     * @param condition  Exchange search query
+     * @return list of calendar messages as Event objects
+     * @throws IOException on error
+     */
+    public abstract List<Event> searchEvents(String folderPath, Set<String> attributes, Condition condition) throws IOException;
+
+    /**
+     * convert vcf extension to EML.
+     *
+     * @param itemName item name
+     * @return EML item name
+     */
+    protected String convertItemNameToEML(String itemName) {
+        if (itemName.endsWith(".vcf")) {
+            return itemName.substring(0, itemName.length() - 3) + "EML";
+        } else {
+            return itemName;
+        }
+    }
+
+    /**
+     * Get item named eventName in folder
+     *
+     * @param folderPath Exchange folder path
+     * @param itemName   event name
+     * @return event object
+     * @throws IOException on error
+     */
+    public abstract Item getItem(String folderPath, String itemName) throws IOException;
+
+    /**
+     * Contact picture
+     */
+    public static class ContactPhoto {
+        /**
+         * Contact picture content type (always image/jpeg on read)
+         */
+        public String contentType;
+        /**
+         * Base64 encoded picture content
+         */
+        public String content;
+    }
+
+    /**
+     * Retrieve contact photo attached to contact
+     *
+     * @param contact address book contact
+     * @return contact photo
+     * @throws IOException on error
+     */
+    public abstract ContactPhoto getContactPhoto(Contact contact) throws IOException;
+
+
+    /**
+     * Delete event named itemName in folder
+     *
+     * @param folderPath Exchange folder path
+     * @param itemName   item name
+     * @throws IOException on error
+     */
+    public abstract void deleteItem(String folderPath, String itemName) throws IOException;
+
+    /**
+     * Mark event processed named eventName in folder
+     *
+     * @param folderPath Exchange folder path
+     * @param itemName   item name
+     * @throws IOException on error
+     */
+    public abstract void processItem(String folderPath, String itemName) throws IOException;
+
+
+    private static int dumpIndex;
+
+    /**
+     * Replace iCal4 (Snow Leopard) principal paths with mailto expression
+     *
+     * @param value attendee value or ics line
+     * @return fixed value
+     */
+    protected String replaceIcal4Principal(String value) {
+        if (value != null && value.contains("/principals/__uuids__/")) {
+            return value.replaceAll("/principals/__uuids__/([^/]*)__AT__([^/]*)/", "mailto:$1@$2");
+        } else {
+            return value;
+        }
+    }
+
+    /**
+     * Event result object to hold HTTP status and event etag from an event creation/update.
+     */
+    public static class ItemResult {
+        /**
+         * HTTP status
+         */
+        public int status;
+        /**
+         * Event etag from response HTTP header
+         */
+        public String etag;
+    }
+
+    /**
+     * Build and send the MIME message for the provided ICS event.
+     *
+     * @param icsBody event in iCalendar format
+     * @return HTTP status
+     * @throws IOException on error
+     */
+    public abstract int sendEvent(String icsBody) throws IOException;
+
+    /**
+     * Create or update item (event or contact) on the Exchange server
+     *
+     * @param folderPath Exchange folder path
+     * @param itemName   event name
+     * @param itemBody   event body in iCalendar format
+     * @param etag       previous event etag to detect concurrent updates
+     * @param noneMatch  if-none-match header value
+     * @return HTTP response event result (status and etag)
+     * @throws IOException on error
+     */
+    public ItemResult createOrUpdateItem(String folderPath, String itemName, String itemBody, String etag, String noneMatch) throws IOException {
+        if (itemBody.startsWith("BEGIN:VCALENDAR")) {
+            return internalCreateOrUpdateEvent(folderPath, itemName, "urn:content-classes:appointment", itemBody, etag, noneMatch);
+        } else if (itemBody.startsWith("BEGIN:VCARD")) {
+            return createOrUpdateContact(folderPath, itemName, itemBody, etag, noneMatch);
+        } else {
+            throw new IOException(BundleMessage.format("EXCEPTION_INVALID_MESSAGE_CONTENT", itemBody));
+        }
+    }
+
+    static final String[] VCARD_N_PROPERTIES = {"sn", "givenName", "middlename", "personaltitle", "namesuffix"};
+    static final String[] VCARD_ADR_HOME_PROPERTIES = {"homepostofficebox", null, "homeStreet", "homeCity", "homeState", "homePostalCode", "homeCountry"};
+    static final String[] VCARD_ADR_WORK_PROPERTIES = {"postofficebox", "roomnumber", "street", "l", "st", "postalcode", "co"};
+    static final String[] VCARD_ADR_OTHER_PROPERTIES = {"otherpostofficebox", null, "otherstreet", "othercity", "otherstate", "otherpostalcode", "othercountry"};
+    static final String[] VCARD_ORG_PROPERTIES = {"o", "department"};
+
+    protected void convertContactProperties(Map<String, String> properties, String[] contactProperties, List<String> values) {
+        for (int i = 0; i < values.size() && i < contactProperties.length; i++) {
+            if (contactProperties[i] != null) {
+                properties.put(contactProperties[i], values.get(i));
+            }
+        }
+    }
+
+    protected ItemResult createOrUpdateContact(String folderPath, String itemName, String itemBody, String etag, String noneMatch) throws IOException {
+        // parse VCARD body to build contact property map
+        Map<String, String> properties = new HashMap<String, String>();
+        properties.put("outlookmessageclass", "IPM.Contact");
+
+        VObject vcard = new VObject(new ICSBufferedReader(new StringReader(itemBody)));
+        for (VProperty property : vcard.getProperties()) {
+            if ("FN".equals(property.getKey())) {
+                properties.put("cn", property.getValue());
+                properties.put("subject", property.getValue());
+                properties.put("fileas", property.getValue());
+
+            } else if ("N".equals(property.getKey())) {
+                convertContactProperties(properties, VCARD_N_PROPERTIES, property.getValues());
+            } else if ("NICKNAME".equals(property.getKey())) {
+                properties.put("nickname", property.getValue());
+            } else if ("TEL".equals(property.getKey())) {
+                if (property.hasParam("TYPE", "cell") || property.hasParam("X-GROUP", "cell")) {
+                    properties.put("mobile", property.getValue());
+                } else if (property.hasParam("TYPE", "work") || property.hasParam("X-GROUP", "work")) {
+                    properties.put("telephoneNumber", property.getValue());
+                } else if (property.hasParam("TYPE", "home") || property.hasParam("X-GROUP", "home")) {
+                    properties.put("homePhone", property.getValue());
+                } else if (property.hasParam("TYPE", "fax")) {
+                    if (property.hasParam("TYPE", "home")) {
+                        properties.put("homefax", property.getValue());
+                    } else {
+                        properties.put("facsimiletelephonenumber", property.getValue());
+                    }
+                } else if (property.hasParam("TYPE", "pager")) {
+                    properties.put("pager", property.getValue());
+                } else if (property.hasParam("TYPE", "car")) {
+                    properties.put("othermobile", property.getValue());
+                } else {
+                    properties.put("otherTelephone", property.getValue());
+                }
+            } else if ("ADR".equals(property.getKey())) {
+                // address
+                if (property.hasParam("TYPE", "home")) {
+                    convertContactProperties(properties, VCARD_ADR_HOME_PROPERTIES, property.getValues());
+                } else if (property.hasParam("TYPE", "work")) {
+                    convertContactProperties(properties, VCARD_ADR_WORK_PROPERTIES, property.getValues());
+                    // any other type goes to other address
+                } else {
+                    convertContactProperties(properties, VCARD_ADR_OTHER_PROPERTIES, property.getValues());
+                }
+            } else if ("EMAIL".equals(property.getKey())) {
+                if (property.hasParam("TYPE", "home")) {
+                    properties.put("email2", property.getValue());
+                    properties.put("smtpemail2", property.getValue());
+                } else if (property.hasParam("TYPE", "other")) {
+                    properties.put("email3", property.getValue());
+                    properties.put("smtpemail3", property.getValue());
+                } else {
+                    properties.put("email1", property.getValue());
+                    properties.put("smtpemail1", property.getValue());
+                }
+            } else if ("ORG".equals(property.getKey())) {
+                convertContactProperties(properties, VCARD_ORG_PROPERTIES, property.getValues());
+            } else if ("URL".equals(property.getKey())) {
+                if (property.hasParam("TYPE", "work")) {
+                    properties.put("businesshomepage", property.getValue());
+                } else if (property.hasParam("TYPE", "home")) {
+                    properties.put("personalHomePage", property.getValue());
+                } else {
+                    // default: set personal home page
+                    properties.put("personalHomePage", property.getValue());
+                }
+            } else if ("TITLE".equals(property.getKey())) {
+                properties.put("title", property.getValue());
+            } else if ("NOTE".equals(property.getKey())) {
+                properties.put("description", property.getValue());
+            } else if ("CUSTOM1".equals(property.getKey())) {
+                properties.put("extensionattribute1", property.getValue());
+            } else if ("CUSTOM2".equals(property.getKey())) {
+                properties.put("extensionattribute2", property.getValue());
+            } else if ("CUSTOM3".equals(property.getKey())) {
+                properties.put("extensionattribute3", property.getValue());
+            } else if ("CUSTOM4".equals(property.getKey())) {
+                properties.put("extensionattribute4", property.getValue());
+            } else if ("ROLE".equals(property.getKey())) {
+                properties.put("profession", property.getValue());
+            } else if ("X-AIM".equals(property.getKey())) {
+                properties.put("im", property.getValue());
+            } else if ("BDAY".equals(property.getKey())) {
+                properties.put("bday", convertBDayToZulu(property.getValue()));
+            } else if ("ANNIVERSARY".equals(property.getKey()) || "X-ANNIVERSARY".equals(property.getKey())) {
+                properties.put("anniversary", convertBDayToZulu(property.getValue()));
+            } else if ("CATEGORIES".equals(property.getKey())) {
+                properties.put("keywords", property.getValue());
+            } else if ("CLASS".equals(property.getKey())) {
+                if ("PUBLIC".equals(property.getValue())) {
+                    properties.put("sensitivity", "0");
+                    properties.put("private", "false");
+                } else {
+                    properties.put("sensitivity", "2");
+                    properties.put("private", "true");
+                }
+            } else if ("SEX".equals(property.getKey())) {
+                String propertyValue = property.getValue();
+                if ("1".equals(propertyValue)) {
+                    properties.put("gender", "2");
+                } else if ("2".equals(propertyValue)) {
+                    properties.put("gender", "1");
+                }
+            } else if ("FBURL".equals(property.getKey())) {
+                properties.put("fburl", property.getValue());
+            } else if ("X-ASSISTANT".equals(property.getKey())) {
+                properties.put("secretarycn", property.getValue());
+            } else if ("X-MANAGER".equals(property.getKey())) {
+                properties.put("manager", property.getValue());
+            } else if ("X-SPOUSE".equals(property.getKey())) {
+                properties.put("spousecn", property.getValue());
+            } else if ("PHOTO".equals(property.getKey())) {
+                properties.put("photo", property.getValue());
+                properties.put("haspicture", "true");
+            }
+        }
+        LOGGER.debug("Create or update contact " + itemName + ": " + properties);
+        // reset missing properties to null
+        for (String key : CONTACT_ATTRIBUTES) {
+            if (!"imapUid".equals(key) && !"etag".equals(key) && !"urlcompname".equals(key)
+                    && !"lastmodified".equals(key) && !"sensitivity".equals(key) &&
+                    !properties.containsKey(key)) {
+                properties.put(key, null);
+            }
+        }
+        return internalCreateOrUpdateContact(folderPath, itemName, properties, etag, noneMatch);
+    }
+
+    protected String convertZuluDateToBday(String value) {
+        String result = null;
+        if (value != null && value.length() > 0) {
+            try {
+                SimpleDateFormat parser = ExchangeSession.getZuluDateFormat();
+                Calendar cal = Calendar.getInstance();
+                cal.setTime(parser.parse(value));
+                cal.add(Calendar.HOUR_OF_DAY, 12);
+                result = ExchangeSession.getVcardBdayFormat().format(cal.getTime());
+            } catch (ParseException e) {
+                LOGGER.warn("Invalid date: " + value);
+            }
+        }
+        return result;
+    }
+
+    protected String convertBDayToZulu(String value) {
+        String result = null;
+        if (value != null && value.length() > 0) {
+            try {
+                SimpleDateFormat parser;
+                if (value.length() == 10) {
+                    parser = ExchangeSession.getVcardBdayFormat();
+                } else if (value.length() == 15) {
+                    parser = new SimpleDateFormat("yyyyMMdd'T'HHmmss", Locale.ENGLISH);
+                    parser.setTimeZone(GMT_TIMEZONE);
+                } else {
+                    parser = ExchangeSession.getExchangeZuluDateFormat();
+                }
+                result = ExchangeSession.getExchangeZuluDateFormatMillisecond().format(parser.parse(value));
+            } catch (ParseException e) {
+                LOGGER.warn("Invalid date: " + value);
+            }
+        }
+
+        return result;
+    }
+
+
+    protected abstract ItemResult internalCreateOrUpdateContact(String folderPath, String itemName, Map<String, String> properties, String etag, String noneMatch) throws IOException;
+
+    protected abstract ItemResult internalCreateOrUpdateEvent(String folderPath, String itemName, String contentClass, String icsBody, String etag, String noneMatch) throws IOException;
+
+    /**
+     * Get current Exchange alias name from login name
+     *
+     * @return user name
+     */
+    public String getAliasFromLogin() {
+        // login is email, not alias
+        if (this.userName.indexOf('@') >= 0) {
+            return null;
+        }
+        String result = this.userName;
+        // remove domain name
+        int index = Math.max(result.indexOf('\\'), result.indexOf('/'));
+        if (index >= 0) {
+            result = result.substring(index + 1);
+        }
+        return result;
+    }
+
+    protected String getEmailSuffixFromHostname() {
+        String domain = httpClient.getHostConfiguration().getHost();
+        int start = domain.lastIndexOf('.', domain.lastIndexOf('.') - 1);
+        if (start >= 0) {
+            return '@' + domain.substring(start + 1);
+        } else {
+            return '@' + domain;
+        }
+    }
+
+    /**
+     * Test if folderPath is inside user mailbox.
+     *
+     * @param folderPath absolute folder path
+     * @return true if folderPath is a public or shared folder
+     */
+    public abstract boolean isSharedFolder(String folderPath);
+
+    /**
+     * Test if folderPath is main calendar.
+     *
+     * @param folderPath absolute folder path
+     * @return true if folderPath is a public or shared folder
+     */
+    public abstract boolean isMainCalendar(String folderPath);
+
+    static final String MAILBOX_BASE = "/cn=";
+
+    protected void getEmailAndAliasFromOptions() {
+        Cookie[] currentCookies = httpClient.getState().getCookies();
+        // get user mail URL from html body
+        BufferedReader optionsPageReader = null;
+        GetMethod optionsMethod = new GetMethod("/owa/?ae=Options&t=About");
+        try {
+            DavGatewayHttpClientFacade.executeGetMethod(httpClient, optionsMethod, false);
+            optionsPageReader = new BufferedReader(new InputStreamReader(optionsMethod.getResponseBodyAsStream()));
+            String line;
+
+            // find email and alias
+            while ((line = optionsPageReader.readLine()) != null
+                    && (line.indexOf('[') == -1
+                    || line.indexOf('@') == -1
+                    || line.indexOf(']') == -1)
+                    && line.toLowerCase().indexOf(MAILBOX_BASE) == -1) {
+            }
+            if (line != null) {
+                int start = line.toLowerCase().lastIndexOf(MAILBOX_BASE) + MAILBOX_BASE.length();
+                int end = line.indexOf('<', start);
+                alias = line.substring(start, end);
+                end = line.lastIndexOf(']');
+                start = line.lastIndexOf('[', end) + 1;
+                email = line.substring(start, end);
+            }
+        } catch (IOException e) {
+            // restore cookies on error
+            httpClient.getState().addCookies(currentCookies);
+            LOGGER.error("Error parsing options page at " + optionsMethod.getPath());
+        } finally {
+            if (optionsPageReader != null) {
+                try {
+                    optionsPageReader.close();
+                } catch (IOException e) {
+                    LOGGER.error("Error parsing options page at " + optionsMethod.getPath());
+                }
+            }
+            optionsMethod.releaseConnection();
+        }
+    }
+
+    /**
+     * Get current user email
+     *
+     * @return user email
+     */
+    public String getEmail() {
+        return email;
+    }
+
+    /**
+     * Get current user alias
+     *
+     * @return user email
+     */
+    public String getAlias() {
+        return alias;
+    }
+
+    /**
+     * Search global address list
+     *
+     * @param condition           search filter
+     * @param returningAttributes returning attributes
+     * @param sizeLimit           size limit
+     * @return matching contacts from gal
+     * @throws IOException on error
+     */
+    public abstract Map<String, Contact> galFind(Condition condition, Set<String> returningAttributes, int sizeLimit) throws IOException;
+
+    /**
+     * Full Contact attribute list
+     */
+    public static final Set<String> CONTACT_ATTRIBUTES = new HashSet<String>();
+
+    static {
+        CONTACT_ATTRIBUTES.add("imapUid");
+        CONTACT_ATTRIBUTES.add("etag");
+        CONTACT_ATTRIBUTES.add("urlcompname");
+
+        CONTACT_ATTRIBUTES.add("extensionattribute1");
+        CONTACT_ATTRIBUTES.add("extensionattribute2");
+        CONTACT_ATTRIBUTES.add("extensionattribute3");
+        CONTACT_ATTRIBUTES.add("extensionattribute4");
+        CONTACT_ATTRIBUTES.add("bday");
+        CONTACT_ATTRIBUTES.add("anniversary");
+        CONTACT_ATTRIBUTES.add("businesshomepage");
+        CONTACT_ATTRIBUTES.add("personalHomePage");
+        CONTACT_ATTRIBUTES.add("cn");
+        CONTACT_ATTRIBUTES.add("co");
+        CONTACT_ATTRIBUTES.add("department");
+        CONTACT_ATTRIBUTES.add("smtpemail1");
+        CONTACT_ATTRIBUTES.add("smtpemail2");
+        CONTACT_ATTRIBUTES.add("smtpemail3");
+        CONTACT_ATTRIBUTES.add("facsimiletelephonenumber");
+        CONTACT_ATTRIBUTES.add("givenName");
+        CONTACT_ATTRIBUTES.add("homeCity");
+        CONTACT_ATTRIBUTES.add("homeCountry");
+        CONTACT_ATTRIBUTES.add("homePhone");
+        CONTACT_ATTRIBUTES.add("homePostalCode");
+        CONTACT_ATTRIBUTES.add("homeState");
+        CONTACT_ATTRIBUTES.add("homeStreet");
+        CONTACT_ATTRIBUTES.add("homepostofficebox");
+        CONTACT_ATTRIBUTES.add("l");
+        CONTACT_ATTRIBUTES.add("manager");
+        CONTACT_ATTRIBUTES.add("mobile");
+        CONTACT_ATTRIBUTES.add("namesuffix");
+        CONTACT_ATTRIBUTES.add("nickname");
+        CONTACT_ATTRIBUTES.add("o");
+        CONTACT_ATTRIBUTES.add("pager");
+        CONTACT_ATTRIBUTES.add("personaltitle");
+        CONTACT_ATTRIBUTES.add("postalcode");
+        CONTACT_ATTRIBUTES.add("postofficebox");
+        CONTACT_ATTRIBUTES.add("profession");
+        CONTACT_ATTRIBUTES.add("roomnumber");
+        CONTACT_ATTRIBUTES.add("secretarycn");
+        CONTACT_ATTRIBUTES.add("sn");
+        CONTACT_ATTRIBUTES.add("spousecn");
+        CONTACT_ATTRIBUTES.add("st");
+        CONTACT_ATTRIBUTES.add("street");
+        CONTACT_ATTRIBUTES.add("telephoneNumber");
+        CONTACT_ATTRIBUTES.add("title");
+        CONTACT_ATTRIBUTES.add("description");
+        CONTACT_ATTRIBUTES.add("im");
+        CONTACT_ATTRIBUTES.add("middlename");
+        CONTACT_ATTRIBUTES.add("lastmodified");
+        CONTACT_ATTRIBUTES.add("otherstreet");
+        CONTACT_ATTRIBUTES.add("otherstate");
+        CONTACT_ATTRIBUTES.add("otherpostofficebox");
+        CONTACT_ATTRIBUTES.add("otherpostalcode");
+        CONTACT_ATTRIBUTES.add("othercountry");
+        CONTACT_ATTRIBUTES.add("othercity");
+        CONTACT_ATTRIBUTES.add("haspicture");
+        CONTACT_ATTRIBUTES.add("keywords");
+        CONTACT_ATTRIBUTES.add("othermobile");
+        CONTACT_ATTRIBUTES.add("otherTelephone");
+        CONTACT_ATTRIBUTES.add("gender");
+        CONTACT_ATTRIBUTES.add("private");
+        CONTACT_ATTRIBUTES.add("sensitivity");
+        CONTACT_ATTRIBUTES.add("fburl");
+    }
+
+    /**
+     * Get freebusy data string from Exchange.
+     *
+     * @param attendee attendee email address
+     * @param start    start date in Exchange zulu format
+     * @param end      end date in Exchange zulu format
+     * @param interval freebusy interval in minutes
+     * @return freebusy data or null
+     * @throws IOException on error
+     */
+    protected abstract String getFreeBusyData(String attendee, String start, String end, int interval) throws IOException;
+
+    /**
+     * Get freebusy info for attendee between start and end date.
+     *
+     * @param attendee       attendee email
+     * @param startDateValue start date
+     * @param endDateValue   end date
+     * @return FreeBusy info
+     * @throws IOException on error
+     */
+    public FreeBusy getFreebusy(String attendee, String startDateValue, String endDateValue) throws IOException {
+        // replace ical encoded attendee name
+        attendee = replaceIcal4Principal(attendee);
+
+        // then check that email address is valid to avoid InvalidSmtpAddress error
+        if (attendee == null || attendee.indexOf('@') < 0 || attendee.charAt(attendee.length() - 1) == '@') {
+            return null;
+        }
+
+        if (attendee.startsWith("mailto:") || attendee.startsWith("MAILTO:")) {
+            attendee = attendee.substring("mailto:".length());
+        }
+
+        SimpleDateFormat exchangeZuluDateFormat = getExchangeZuluDateFormat();
+        SimpleDateFormat icalDateFormat = getZuluDateFormat();
+
+        Date startDate;
+        Date endDate;
+        try {
+            if (startDateValue.length() == 8) {
+                startDate = parseDate(startDateValue);
+            } else {
+                startDate = icalDateFormat.parse(startDateValue);
+            }
+            if (endDateValue.length() == 8) {
+                endDate = parseDate(endDateValue);
+            } else {
+                endDate = icalDateFormat.parse(endDateValue);
+            }
+        } catch (ParseException e) {
+            throw new DavMailException("EXCEPTION_INVALID_DATES", e.getMessage());
+        }
+
+        FreeBusy freeBusy = null;
+        String fbdata = getFreeBusyData(attendee, exchangeZuluDateFormat.format(startDate), exchangeZuluDateFormat.format(endDate), FREE_BUSY_INTERVAL);
+        if (fbdata != null) {
+            freeBusy = new FreeBusy(icalDateFormat, startDate, fbdata);
+        }
+
+        if (freeBusy != null && freeBusy.knownAttendee) {
+            return freeBusy;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Exchange to iCalendar Free/Busy parser.
+     * Free time returns 0, Tentative returns 1, Busy returns 2, and Out of Office (OOF) returns 3
+     */
+    public static final class FreeBusy {
+        final SimpleDateFormat icalParser;
+        boolean knownAttendee = true;
+        static final HashMap<Character, String> FBTYPES = new HashMap<Character, String>();
+
+        static {
+            FBTYPES.put('1', "BUSY-TENTATIVE");
+            FBTYPES.put('2', "BUSY");
+            FBTYPES.put('3', "BUSY-UNAVAILABLE");
+        }
+
+        final HashMap<String, StringBuilder> busyMap = new HashMap<String, StringBuilder>();
+
+        StringBuilder getBusyBuffer(char type) {
+            String fbType = FBTYPES.get(Character.valueOf(type));
+            StringBuilder buffer = busyMap.get(fbType);
+            if (buffer == null) {
+                buffer = new StringBuilder();
+                busyMap.put(fbType, buffer);
+            }
+            return buffer;
+        }
+
+        void startBusy(char type, Calendar currentCal) {
+            if (type == '4') {
+                knownAttendee = false;
+            } else if (type != '0') {
+                StringBuilder busyBuffer = getBusyBuffer(type);
+                if (busyBuffer.length() > 0) {
+                    busyBuffer.append(',');
+                }
+                busyBuffer.append(icalParser.format(currentCal.getTime()));
+            }
+        }
+
+        void endBusy(char type, Calendar currentCal) {
+            if (type != '0' && type != '4') {
+                getBusyBuffer(type).append('/').append(icalParser.format(currentCal.getTime()));
+            }
+        }
+
+        FreeBusy(SimpleDateFormat icalParser, Date startDate, String fbdata) {
+            this.icalParser = icalParser;
+            if (fbdata.length() > 0) {
+                Calendar currentCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+                currentCal.setTime(startDate);
+
+                startBusy(fbdata.charAt(0), currentCal);
+                for (int i = 1; i < fbdata.length() && knownAttendee; i++) {
+                    currentCal.add(Calendar.MINUTE, FREE_BUSY_INTERVAL);
+                    char previousState = fbdata.charAt(i - 1);
+                    char currentState = fbdata.charAt(i);
+                    if (previousState != currentState) {
+                        endBusy(previousState, currentCal);
+                        startBusy(currentState, currentCal);
+                    }
+                }
+                currentCal.add(Calendar.MINUTE, FREE_BUSY_INTERVAL);
+                endBusy(fbdata.charAt(fbdata.length() - 1), currentCal);
+            }
+        }
+
+        /**
+         * Append freebusy information to buffer.
+         *
+         * @param buffer String buffer
+         */
+        public void appendTo(StringBuilder buffer) {
+            for (Map.Entry<String, StringBuilder> entry : busyMap.entrySet()) {
+                buffer.append("FREEBUSY;FBTYPE=").append(entry.getKey())
+                        .append(':').append(entry.getValue()).append((char) 13).append((char) 10);
+            }
+        }
+    }
+
+    protected VObject vTimezone;
+
+    /**
+     * Load and return current user OWA timezone.
+     *
+     * @return current timezone
+     */
+    public VObject getVTimezone() {
+        if (vTimezone == null) {
+            // need to load Timezone info from OWA
+            loadVtimezone();
+        }
+        return vTimezone;
+    }
+
+    protected abstract void loadVtimezone();
+
+    /**
+     * Return internal HttpClient instance
+     *
+     * @return http client
+     */
+    public HttpClient getHttpClient() {
+        return httpClient;
+    }
+}
diff --git a/src/java/davmail/exchange/ExchangeSessionFactory.java b/src/java/davmail/exchange/ExchangeSessionFactory.java
new file mode 100644
index 0000000..6094f71
--- /dev/null
+++ b/src/java/davmail/exchange/ExchangeSessionFactory.java
@@ -0,0 +1,317 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange;
+
+import davmail.BundleMessage;
+import davmail.Settings;
+import davmail.exception.DavMailAuthenticationException;
+import davmail.exception.DavMailException;
+import davmail.exception.WebdavNotAvailableException;
+import davmail.exchange.dav.DavExchangeSession;
+import davmail.exchange.ews.EwsExchangeSession;
+import davmail.http.DavGatewayHttpClientFacade;
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.httpclient.methods.GetMethod;
+
+import java.io.IOException;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Create ExchangeSession instances.
+ */
+public final class ExchangeSessionFactory {
+    private static final Object LOCK = new Object();
+    private static final Map<PoolKey, ExchangeSession> POOL_MAP = new HashMap<PoolKey, ExchangeSession>();
+    private static boolean configChecked;
+    private static boolean errorSent;
+
+    static class PoolKey {
+        final String url;
+        final String userName;
+        final String password;
+
+        PoolKey(String url, String userName, String password) {
+            this.url = url;
+            this.userName = userName;
+            this.password = password;
+        }
+
+        @Override
+        public boolean equals(Object object) {
+            return object == this ||
+                    object instanceof PoolKey &&
+                            ((PoolKey) object).url.equals(this.url) &&
+                            ((PoolKey) object).userName.equals(this.userName) &&
+                            ((PoolKey) object).password.equals(this.password);
+        }
+
+        @Override
+        public int hashCode() {
+            return url.hashCode() + userName.hashCode() + password.hashCode();
+        }
+    }
+
+    private ExchangeSessionFactory() {
+    }
+
+    /**
+     * Create authenticated Exchange session
+     *
+     * @param userName user login
+     * @param password user password
+     * @return authenticated session
+     * @throws IOException on error
+     */
+    public static ExchangeSession getInstance(String userName, String password) throws IOException {
+        String baseUrl = Settings.getProperty("davmail.url");
+        if (Settings.getBooleanProperty("davmail.server")) {
+            return getInstance(baseUrl, userName, password);
+        } else {
+            // serialize session creation in workstation mode to avoid multiple OTP requests
+            synchronized (LOCK) {
+                return getInstance(baseUrl, userName, password);
+            }
+        }
+    }
+
+    private static String convertUserName(String userName) {
+        String result = userName;
+        // prepend default windows domain prefix
+        String defaultDomain = Settings.getProperty("davmail.defaultDomain");
+        if (userName.indexOf('\\') < 0 && defaultDomain != null) {
+            result = defaultDomain + '\\' + userName;
+        }
+        return result;
+    }
+
+    /**
+     * Create authenticated Exchange session
+     *
+     * @param baseUrl  OWA base URL
+     * @param userName user login
+     * @param password user password
+     * @return authenticated session
+     * @throws IOException on error
+     */
+    public static ExchangeSession getInstance(String baseUrl, String userName, String password) throws IOException {
+        ExchangeSession session = null;
+        try {
+
+            PoolKey poolKey = new PoolKey(baseUrl, convertUserName(userName), password);
+
+            synchronized (LOCK) {
+                session = POOL_MAP.get(poolKey);
+            }
+            if (session != null) {
+                ExchangeSession.LOGGER.debug("Got session " + session + " from cache");
+            }
+
+            if (session != null && session.isExpired()) {
+                ExchangeSession.LOGGER.debug("Session " + session + " expired");
+                session = null;
+                // expired session, remove from cache
+                synchronized (LOCK) {
+                    POOL_MAP.remove(poolKey);
+                }
+            }
+
+            if (session == null) {
+                String enableEws = Settings.getProperty("davmail.enableEws", "auto");
+                if ("true".equals(enableEws)) {
+                    session = new EwsExchangeSession(poolKey.url, poolKey.userName, poolKey.password);
+                } else {
+                    try {
+                        session = new DavExchangeSession(poolKey.url, poolKey.userName, poolKey.password);
+                    } catch (WebdavNotAvailableException e) {
+                        if ("auto".equals(enableEws)) {
+                            ExchangeSession.LOGGER.debug(e.getMessage() + ", retry with EWS");
+                            session = new EwsExchangeSession(poolKey.url, poolKey.userName, poolKey.password);
+                        } else {
+                            throw e;
+                        }
+                    }
+                }
+                ExchangeSession.LOGGER.debug("Created new session: " + session);
+            }
+            // successful login, put session in cache
+            synchronized (LOCK) {
+                POOL_MAP.put(poolKey, session);
+            }
+            // session opened, future failure will mean network down
+            configChecked = true;
+            // Reset so next time an problem occurs message will be sent once
+            errorSent = false;
+        } catch (DavMailAuthenticationException exc) {
+            throw exc;
+        } catch (DavMailException exc) {
+            throw exc;
+        } catch (IllegalStateException exc) {
+            throw exc;
+        } catch (Exception exc) {
+            handleNetworkDown(exc);
+        }
+        return session;
+    }
+
+    /**
+     * Get a non expired session.
+     * If the current session is not expired, return current session, else try to create a new session
+     *
+     * @param currentSession current session
+     * @param userName       user login
+     * @param password       user password
+     * @return authenticated session
+     * @throws IOException on error
+     */
+    public static ExchangeSession getInstance(ExchangeSession currentSession, String userName, String password)
+            throws IOException {
+        ExchangeSession session = currentSession;
+        try {
+            if (session.isExpired()) {
+                ExchangeSession.LOGGER.debug("Session " + session + " expired, trying to open a new one");
+                session = null;
+                String baseUrl = Settings.getProperty("davmail.url");
+                PoolKey poolKey = new PoolKey(baseUrl, userName, password);
+                // expired session, remove from cache
+                synchronized (LOCK) {
+                    POOL_MAP.remove(poolKey);
+                }
+                session = getInstance(userName, password);
+            }
+        } catch (DavMailAuthenticationException exc) {
+            ExchangeSession.LOGGER.debug("Unable to reopen session", exc);
+            throw exc;
+        } catch (Exception exc) {
+            ExchangeSession.LOGGER.debug("Unable to reopen session", exc);
+            handleNetworkDown(exc);
+        }
+        return session;
+    }
+
+    /**
+     * Send a request to Exchange server to check current settings.
+     *
+     * @throws IOException if unable to access Exchange server
+     */
+    public static void checkConfig() throws IOException {
+        String url = Settings.getProperty("davmail.url");
+        if (url == null || (!url.startsWith("http://") && !url.startsWith("https://"))) {
+             throw new DavMailException("LOG_INVALID_URL", url);
+        }
+        HttpClient httpClient = DavGatewayHttpClientFacade.getInstance(url);
+        GetMethod testMethod = new GetMethod(url);
+        try {
+            // get webMail root url (will not follow redirects)
+            int status = DavGatewayHttpClientFacade.executeTestMethod(httpClient, testMethod);
+            ExchangeSession.LOGGER.debug("Test configuration status: " + status);
+            if (status != HttpStatus.SC_OK && status != HttpStatus.SC_UNAUTHORIZED
+                    && !DavGatewayHttpClientFacade.isRedirect(status)) {
+                throw new DavMailException("EXCEPTION_CONNECTION_FAILED", url, status);
+            }
+            // session opened, future failure will mean network down
+            configChecked = true;
+            // Reset so next time an problem occurs message will be sent once
+            errorSent = false;
+        } catch (Exception exc) {
+            handleNetworkDown(exc);
+        } finally {
+            testMethod.releaseConnection();
+        }
+
+    }
+
+    private static void handleNetworkDown(Exception exc) throws DavMailException {
+        if (!checkNetwork() || configChecked) {
+            ExchangeSession.LOGGER.warn(BundleMessage.formatLog("EXCEPTION_NETWORK_DOWN"));
+            // log full stack trace for unknown errors
+            if (!((exc instanceof UnknownHostException)||(exc instanceof NetworkDownException))) {
+                ExchangeSession.LOGGER.debug(exc, exc);
+            }
+            throw new NetworkDownException("EXCEPTION_NETWORK_DOWN");
+        } else {
+            BundleMessage message = new BundleMessage("EXCEPTION_CONNECT", exc.getClass().getName(), exc.getMessage());
+            if (errorSent) {
+                ExchangeSession.LOGGER.warn(message);
+                throw new NetworkDownException("EXCEPTION_DAVMAIL_CONFIGURATION", message);
+            } else {
+                // Mark that an error has been sent so you only get one
+                // error in a row (not a repeating string of errors).
+                errorSent = true;
+                ExchangeSession.LOGGER.error(message);
+                throw new DavMailException("EXCEPTION_DAVMAIL_CONFIGURATION", message);
+            }
+        }
+    }
+
+    /**
+     * Get user password from session pool for SASL authentication
+     *
+     * @param userName Exchange user name
+     * @return user password
+     */
+    public static String getUserPassword(String userName) {
+        String fullUserName = convertUserName(userName);
+        for (PoolKey poolKey : POOL_MAP.keySet()) {
+            if (poolKey.userName.equals(fullUserName)) {
+                return poolKey.password;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Check if at least one network interface is up and active (i.e. has an address)
+     *
+     * @return true if network available
+     */
+    static boolean checkNetwork() {
+        boolean up = false;
+        Enumeration<NetworkInterface> enumeration;
+        try {
+            enumeration = NetworkInterface.getNetworkInterfaces();
+            while (!up && enumeration.hasMoreElements()) {
+                NetworkInterface networkInterface = enumeration.nextElement();
+                //noinspection Since15
+                up = networkInterface.isUp() && !networkInterface.isLoopback()
+                        && networkInterface.getInetAddresses().hasMoreElements();
+            }
+        } catch (NoSuchMethodError error) {
+            ExchangeSession.LOGGER.debug("Unable to test network interfaces (not available under Java 1.5)");
+            up = true;
+        } catch (SocketException exc) {
+            ExchangeSession.LOGGER.error("DavMail configuration exception: \n Error listing network interfaces " + exc.getMessage(), exc);
+        }
+        return up;
+    }
+
+    /**
+     * Reset config check status and clear session pool.
+     */
+    public static void reset() {
+        configChecked = false;
+        errorSent = false;
+        POOL_MAP.clear();
+    }
+}
diff --git a/src/java/davmail/exchange/ICSBufferedReader.java b/src/java/davmail/exchange/ICSBufferedReader.java
new file mode 100644
index 0000000..c98e573
--- /dev/null
+++ b/src/java/davmail/exchange/ICSBufferedReader.java
@@ -0,0 +1,72 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Reader;
+
+/**
+ * ICS Buffered Reader.
+ * Read events by line, handle multiple line elements
+ */
+public class ICSBufferedReader extends BufferedReader {
+    protected String nextLine;
+    protected final StringBuilder currentLine = new StringBuilder(75);
+
+    /**
+     * Create an ICS reader on the provided reader
+     *
+     * @param in input reader
+     * @throws IOException on error
+     */
+    public ICSBufferedReader(Reader in) throws IOException {
+        super(in);
+        nextLine = super.readLine();
+    }
+
+    /**
+     * Read a line from input reader, unwrap long lines.
+     */
+    @Override
+    public String readLine() throws IOException {
+        if (nextLine == null) {
+            return null;
+        } else {
+            currentLine.setLength(0);
+            currentLine.append(nextLine);
+            nextLine = super.readLine();
+            while (nextLine != null && !(nextLine.length() == 0) &&
+                    (nextLine.charAt(0) == ' ' || nextLine.charAt(0) == '\t'
+                            // workaround for broken items with \n as first line character
+                            || nextLine.charAt(0) == '\\'
+                            // workaround for Exchange 2010 bug
+                            || nextLine.charAt(0) == ':')) {
+                // Timezone ends with \n => next line starts with :
+                if (nextLine.charAt(0) == ':') {
+                    currentLine.append(nextLine);
+                } else {
+                    currentLine.append(nextLine.substring(1));
+                }
+                nextLine = super.readLine();
+            }
+            return currentLine.toString();
+        }
+    }
+}
diff --git a/src/java/davmail/exchange/ICSBufferedWriter.java b/src/java/davmail/exchange/ICSBufferedWriter.java
new file mode 100644
index 0000000..a6cae56
--- /dev/null
+++ b/src/java/davmail/exchange/ICSBufferedWriter.java
@@ -0,0 +1,122 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange;
+
+/**
+ * ICS String writer.
+ * split lines longer than 75 characters
+ */
+public class ICSBufferedWriter {
+    final StringBuilder buffer = new StringBuilder();
+
+    /**
+     * Write content to buffer, do not split lines.
+     *
+     * @param content ics content
+     */
+    public void write(String content) {
+        if (content != null) {
+            buffer.append(content);
+        }
+    }
+
+    /**
+     * Write line to buffer, split lines at 75 characters.
+     *
+     * @param line ics event line
+     */
+    public void writeLine(String line) {
+        writeLine(line, false);
+    }
+
+    /**
+     * Write line with or without continuation prefix.
+     *
+     * @param line   line content
+     * @param prefix continuation flag
+     */
+    public void writeLine(String line, boolean prefix) {
+        int maxLength = 77;
+        if (prefix) {
+            maxLength--;
+            buffer.append(' ');
+        }
+        if (line.length() > maxLength) {
+            buffer.append(line.substring(0, maxLength));
+            newLine();
+            writeLine(line.substring(maxLength), true);
+        } else {
+            buffer.append(line);
+            newLine();
+        }
+    }
+
+    /**
+     * Append CRLF.
+     */
+    public void newLine() {
+        buffer.append((char) 13).append((char) 10);
+    }
+
+    /**
+     * Get buffer as String
+     *
+     * @return ICS content as String
+     */
+    @Override
+    public String toString() {
+        return buffer.toString();
+    }
+
+    /**
+     * Append single value property
+     *
+     * @param propertyName  property name
+     * @param propertyValue property value
+     */
+    public void appendProperty(String propertyName, String propertyValue) {
+        if ((propertyValue != null) && (propertyValue.length() > 0)) {
+            StringBuilder lineBuffer = new StringBuilder();
+            lineBuffer.append(propertyName);
+            lineBuffer.append(':');
+            appendMultilineEncodedValue(lineBuffer, propertyValue);
+            writeLine(lineBuffer.toString());
+        }
+
+    }
+
+    /**
+     * Append and encode \n to \\n in value.
+     *
+     * @param buffer line buffer
+     * @param value  value
+     */
+    protected void appendMultilineEncodedValue(StringBuilder buffer, String value) {
+        for (int i = 0; i < value.length(); i++) {
+            char c = value.charAt(i);
+            if (c == '\n') {
+                buffer.append("\\n");
+            // skip carriage return
+            } else if (c != '\r') {
+                buffer.append(value.charAt(i));
+            }
+        }
+    }
+
+}
diff --git a/src/java/davmail/exchange/MimeOutputStreamWriter.java b/src/java/davmail/exchange/MimeOutputStreamWriter.java
new file mode 100644
index 0000000..609353d
--- /dev/null
+++ b/src/java/davmail/exchange/MimeOutputStreamWriter.java
@@ -0,0 +1,93 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange;
+
+import javax.mail.internet.MimeUtility;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * Mime OutputStreamWriter to build in memory Mime message.
+ */
+public class MimeOutputStreamWriter extends OutputStreamWriter {
+    /**
+     * Build MIME outputStreamWriter
+     *
+     * @param out outputstream
+     * @throws UnsupportedEncodingException on error
+     */
+    public MimeOutputStreamWriter(OutputStream out) throws UnsupportedEncodingException {
+        super(out, "ASCII");
+    }
+
+    /**
+     * Write MIME header
+     *
+     * @param header header name
+     * @param value  header value
+     * @throws IOException on error
+     */
+    public void writeHeader(String header, String value) throws IOException {
+        // do not write empty headers
+        if (value != null && value.length() > 0) {
+            write(header);
+            write(": ");
+            write(MimeUtility.encodeText(value, "UTF-8", null));
+            writeLn();
+        }
+    }
+
+    /**
+     * Write MIME header
+     *
+     * @param header header name
+     * @param value  header value
+     * @throws IOException on error
+     */
+    public void writeHeader(String header, Date value) throws IOException {
+        SimpleDateFormat formatter = new SimpleDateFormat("EEE, dd MMM yyyy hh:mm:ss Z", Locale.ENGLISH);
+        writeHeader(header, formatter.format(value));
+    }
+
+    /**
+     * Write line.
+     *
+     * @param line line content
+     * @throws IOException on error
+     */
+    public void writeLn(String line) throws IOException {
+        write(line);
+        write("\r\n");
+    }
+
+    /**
+     * Write CRLF.
+     *
+     * @throws IOException on error
+     */
+    public void writeLn() throws IOException {
+        write("\r\n");
+    }
+
+}
diff --git a/src/java/davmail/exchange/NetworkDownException.java b/src/java/davmail/exchange/NetworkDownException.java
new file mode 100644
index 0000000..e5ab7dc
--- /dev/null
+++ b/src/java/davmail/exchange/NetworkDownException.java
@@ -0,0 +1,45 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange;
+
+import davmail.exception.DavMailException;
+
+/**
+ * Custom exception to mark network down case.
+ */
+public class NetworkDownException extends DavMailException {
+    /**
+     * Build a network down exception with the provided BundleMessage key.
+     *
+     * @param key message key
+     */
+    public NetworkDownException(String key) {
+        super(key);
+    }
+
+    /**
+     * Build a network down exception with the provided BundleMessage key.
+     *
+     * @param key     message key
+     * @param message detailed message
+     */
+    public NetworkDownException(String key, Object message) {
+        super(key, message);
+    }
+}
diff --git a/src/java/davmail/exchange/VCalendar.java b/src/java/davmail/exchange/VCalendar.java
new file mode 100644
index 0000000..3dfe1d5
--- /dev/null
+++ b/src/java/davmail/exchange/VCalendar.java
@@ -0,0 +1,741 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange;
+
+import davmail.Settings;
+import davmail.util.StringUtil;
+import org.apache.log4j.Logger;
+
+import java.io.*;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * VCalendar object.
+ */
+public class VCalendar extends VObject {
+    protected static final Logger LOGGER = Logger.getLogger(VCalendar.class);
+    protected VObject firstVevent;
+    protected VObject vTimezone;
+    protected String email;
+
+    /**
+     * Create VCalendar object from reader;
+     *
+     * @param reader    stream reader
+     * @param email     current user email
+     * @param vTimezone user OWA timezone
+     * @throws IOException on error
+     */
+    public VCalendar(BufferedReader reader, String email, VObject vTimezone) throws IOException {
+        super(reader);
+        if (!"VCALENDAR".equals(type)) {
+            throw new IOException("Invalid type: " + type);
+        }
+        this.email = email;
+        // set OWA timezone information
+        if (this.vTimezone == null && vTimezone != null) {
+            setTimezone(vTimezone);
+        }
+    }
+
+    /**
+     * Create VCalendar object from string;
+     *
+     * @param vCalendarBody item body
+     * @param email         current user email
+     * @param vTimezone     user OWA timezone
+     * @throws IOException on error
+     */
+    public VCalendar(String vCalendarBody, String email, VObject vTimezone) throws IOException {
+        this(new ICSBufferedReader(new StringReader(vCalendarBody)), email, vTimezone);
+    }
+
+    /**
+     * Create VCalendar object from string;
+     *
+     * @param vCalendarContent item content
+     * @param email            current user email
+     * @param vTimezone        user OWA timezone
+     * @throws IOException on error
+     */
+    public VCalendar(byte[] vCalendarContent, String email, VObject vTimezone) throws IOException {
+        this(new ICSBufferedReader(new InputStreamReader(new ByteArrayInputStream(vCalendarContent), "UTF-8")), email, vTimezone);
+    }
+
+    /**
+     * Empty constructor
+     */
+    public VCalendar() {
+        type = "VCALENDAR";
+    }
+
+    /**
+     * Set timezone on vObject
+     *
+     * @param vTimezone timezone object
+     */
+    public void setTimezone(VObject vTimezone) {
+        if (vObjects == null) {
+            addVObject(vTimezone);
+        } else {
+            vObjects.add(0, vTimezone);
+        }
+        this.vTimezone = vTimezone;
+    }
+
+    @Override
+    public void addVObject(VObject vObject) {
+        super.addVObject(vObject);
+        if (firstVevent == null && ("VEVENT".equals(vObject.type) || "VTODO".equals(vObject.type))) {
+            firstVevent = vObject;
+        }
+        if ("VTIMEZONE".equals(vObject.type)) {
+            vTimezone = vObject;
+        }
+    }
+
+    protected boolean isAllDay(VObject vObject) {
+        VProperty dtstart = vObject.getProperty("DTSTART");
+        return dtstart != null && dtstart.hasParam("VALUE", "DATE");
+    }
+
+    protected boolean isCdoAllDay(VObject vObject) {
+        return "TRUE".equals(vObject.getPropertyValue("X-MICROSOFT-CDO-ALLDAYEVENT"));
+    }
+
+    /**
+     * Check if vCalendar is CDO allday.
+     *
+     * @return true if vCalendar has X-MICROSOFT-CDO-ALLDAYEVENT property set to TRUE
+     */
+    public boolean isCdoAllDay() {
+        return firstVevent != null && isCdoAllDay(firstVevent);
+    }
+
+    /**
+     * Get email from property value.
+     *
+     * @param property property
+     * @return email value
+     */
+    public String getEmailValue(VProperty property) {
+        if (property == null) {
+            return null;
+        }
+        String propertyValue = property.getValue();
+        if (propertyValue != null && (propertyValue.startsWith("MAILTO:") || propertyValue.startsWith("mailto:"))) {
+            return propertyValue.substring(7);
+        } else {
+            return propertyValue;
+        }
+    }
+
+    protected String getMethod() {
+        return getPropertyValue("METHOD");
+    }
+
+    protected void fixVCalendar(boolean fromServer) {
+        // set iCal 4 global X-CALENDARSERVER-ACCESS from CLASS
+        if (fromServer) {
+            setPropertyValue("X-CALENDARSERVER-ACCESS", getCalendarServerAccess());
+        }
+
+        // iCal 4 global X-CALENDARSERVER-ACCESS
+        String calendarServerAccess = getPropertyValue("X-CALENDARSERVER-ACCESS");
+        String now = ExchangeSession.getZuluDateFormat().format(new Date());
+
+        // fix method from iPhone
+        if (!fromServer && getPropertyValue("METHOD") == null) {
+            setPropertyValue("METHOD", "PUBLISH");
+        }
+
+        // rename TZID for maximum iCal/iPhone compatibility
+        String tzid = null;
+        if (fromServer) {
+            // get current tzid
+            VObject vObject = vTimezone;
+            if (vObject != null) {
+                String currentTzid = vObject.getPropertyValue("TZID");
+                // fix TZID with \n (Exchange 2010 bug)
+                if (currentTzid != null && currentTzid.endsWith("\n")) {
+                    currentTzid = currentTzid.substring(0, currentTzid.length() - 1);
+                    vObject.setPropertyValue("TZID", currentTzid);
+                }
+                if (currentTzid != null && currentTzid.indexOf(' ') >= 0) {
+                    try {
+                        tzid = ResourceBundle.getBundle("timezones").getString(currentTzid);
+                        vObject.setPropertyValue("TZID", tzid);
+                    } catch (MissingResourceException e) {
+                        LOGGER.debug("Timezone " + currentTzid + " not found in rename table");
+                    }
+                }
+            }
+        }
+
+        if (!fromServer) {
+            fixTimezone();
+        }
+
+        // iterate over vObjects
+        for (VObject vObject : vObjects) {
+            if ("VEVENT".equals(vObject.type)) {
+                if (calendarServerAccess != null) {
+                    vObject.setPropertyValue("CLASS", getEventClass(calendarServerAccess));
+                    // iCal 3, get X-CALENDARSERVER-ACCESS from local VEVENT
+                } else if (vObject.getPropertyValue("X-CALENDARSERVER-ACCESS") != null) {
+                    vObject.setPropertyValue("CLASS", getEventClass(vObject.getPropertyValue("X-CALENDARSERVER-ACCESS")));
+                }
+                if (fromServer) {
+                    // remove organizer line for event without attendees for iPhone
+                    if (vObject.getProperty("ATTENDEE") == null) {
+                        vObject.setPropertyValue("ORGANIZER", null);
+                    }
+                    // detect allday and update date properties
+                    if (isCdoAllDay(vObject)) {
+                        setClientAllday(vObject.getProperty("DTSTART"));
+                        setClientAllday(vObject.getProperty("DTEND"));
+                    }
+                    String cdoBusyStatus = vObject.getPropertyValue("X-MICROSOFT-CDO-BUSYSTATUS");
+                    if (cdoBusyStatus != null) {
+                        vObject.setPropertyValue("TRANSP",
+                                !"FREE".equals(cdoBusyStatus) ? "OPAQUE" : "TRANSPARENT");
+                    }
+
+                    // Apple iCal doesn't understand this key, and it's entourage
+                    // specific (i.e. not needed by any caldav client): strip it out
+                    vObject.removeProperty("X-ENTOURAGE_UUID");
+
+                    splitExDate(vObject);
+
+                    // remove empty properties
+                    if ("".equals(vObject.getPropertyValue("LOCATION"))) {
+                        vObject.removeProperty("LOCATION");
+                    }
+                    if ("".equals(vObject.getPropertyValue("DESCRIPTION"))) {
+                        vObject.removeProperty("DESCRIPTION");
+                    }
+                    if ("".equals(vObject.getPropertyValue("CLASS"))) {
+                        vObject.removeProperty("CLASS");
+                    }
+                    // rename TZID
+                    if (tzid != null) {
+                        VProperty dtStart = vObject.getProperty("DTSTART");
+                        if (dtStart != null && dtStart.getParam("TZID") != null) {
+                            dtStart.setParam("TZID", tzid);
+                        }
+                        VProperty dtEnd = vObject.getProperty("DTEND");
+                        if (dtEnd != null && dtStart.getParam("TZID") != null) {
+                            dtEnd.setParam("TZID", tzid);
+                        }
+                    }
+                    // remove unsupported attachment reference
+                    if (vObject.getProperty("ATTACH") != null) {
+                        List<String> toRemoveValues = null;
+                        List<String> values = vObject.getProperty("ATTACH").getValues();
+                        for (String value : values) {
+                            if (value.indexOf("CID:") >= 0) {
+                                if (toRemoveValues == null) {
+                                    toRemoveValues = new ArrayList<String>();
+                                }
+                                toRemoveValues.add(value);
+                            }
+                        }
+                        if (toRemoveValues != null) {
+                            values.removeAll(toRemoveValues);
+                            if (values.size() == 0) {
+                                vObject.removeProperty("ATTACH");
+                            }
+                        }
+                    }
+                } else {
+                    // add organizer line to all events created in Exchange for active sync
+                    String organizer = getEmailValue(vObject.getProperty("ORGANIZER"));
+                    if (organizer == null) {
+                        vObject.setPropertyValue("ORGANIZER", "MAILTO:" + email);
+                    } else if (!email.equalsIgnoreCase(organizer) && vObject.getProperty("X-MICROSOFT-CDO-REPLYTIME") == null) {
+                        vObject.setPropertyValue("X-MICROSOFT-CDO-REPLYTIME", now);
+                    }
+                    // set OWA allday flag
+                    vObject.setPropertyValue("X-MICROSOFT-CDO-ALLDAYEVENT", isAllDay(vObject) ? "TRUE" : "FALSE");
+                    vObject.setPropertyValue("X-MICROSOFT-CDO-BUSYSTATUS",
+                            !"TRANSPARENT".equals(vObject.getPropertyValue("TRANSP")) ? "BUSY" : "FREE");
+
+                    if (isAllDay(vObject)) {
+                        // convert date values to outlook compatible values
+                        setServerAllday(vObject.getProperty("DTSTART"));
+                        setServerAllday(vObject.getProperty("DTEND"));
+                    } else {
+                        fixTzid(vObject.getProperty("DTSTART"));
+                        fixTzid(vObject.getProperty("DTEND"));
+                    }
+                }
+
+                fixAttendees(vObject, fromServer);
+
+                fixAlarm(vObject, fromServer);
+            }
+        }
+
+    }
+
+    private void fixTimezone() {
+        if (vTimezone != null && vTimezone.vObjects != null && vTimezone.vObjects.size() > 2) {
+            VObject standard = null;
+            VObject daylight = null;
+            for (VObject vObject : vTimezone.vObjects) {
+                if ("STANDARD".equals(vObject.type)) {
+                    if (standard == null ||
+                            (vObject.getPropertyValue("DTSTART").compareTo(standard.getPropertyValue("DTSTART")) > 0)) {
+                        standard = vObject;
+                    }
+                }
+                if ("DAYLIGHT".equals(vObject.type)) {
+                    if (daylight == null ||
+                            (vObject.getPropertyValue("DTSTART").compareTo(daylight.getPropertyValue("DTSTART")) > 0)) {
+                        daylight = vObject;
+                    }
+                }
+            }
+            vTimezone.vObjects.clear();
+            vTimezone.vObjects.add(standard);
+            vTimezone.vObjects.add(daylight);
+        }
+    }
+
+    private void fixTzid(VProperty property) {
+        if (property != null && !property.hasParam("TZID")) {
+            property.addParam("TZID", vTimezone.getPropertyValue("TZID"));
+        }
+    }
+
+    protected void splitExDate(VObject vObject) {
+        List<VProperty> exDateProperties = vObject.getProperties("EXDATE");
+        if (exDateProperties != null) {
+            for (VProperty property : exDateProperties) {
+                String value = property.getValue();
+                if (value.indexOf(',') >= 0) {
+                    // split property
+                    vObject.removeProperty(property);
+                    for (String singleValue : value.split(",")) {
+                        VProperty singleProperty = new VProperty("EXDATE", singleValue);
+                        singleProperty.setParams(property.getParams());
+                        vObject.addProperty(singleProperty);
+                    }
+                }
+            }
+        }
+    }
+
+    protected void setServerAllday(VProperty property) {
+        if (vTimezone != null) {
+            // set TZID param
+            if (!property.hasParam("TZID")) {
+                property.addParam("TZID", vTimezone.getPropertyValue("TZID"));
+            }
+            // remove VALUE
+            property.removeParam("VALUE");
+            String value = property.getValue();
+            if (value.length() != 8) {
+                LOGGER.warn("Invalid date value in allday event: " + value);
+            }
+            property.setValue(property.getValue() + "T000000");
+        }
+    }
+
+    protected void setClientAllday(VProperty property) {
+        if (property != null) {
+            // set VALUE=DATE param
+            if (!property.hasParam("VALUE")) {
+                property.addParam("VALUE", "DATE");
+            }
+            // remove TZID
+            property.removeParam("TZID");
+            String value = property.getValue();
+            if (value.length() != 8) {
+                // try to convert datetime value to date value
+                try {
+                    Calendar calendar = Calendar.getInstance();
+                    SimpleDateFormat dateParser = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+                    calendar.setTime(dateParser.parse(value));
+                    calendar.add(Calendar.HOUR_OF_DAY, 12);
+                    SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyyMMdd");
+                    value = dateFormatter.format(calendar.getTime());
+                } catch (ParseException e) {
+                    LOGGER.warn("Invalid date value in allday event: " + value);
+                }
+            }
+            property.setValue(value);
+        }
+    }
+
+    protected void fixAlarm(VObject vObject, boolean fromServer) {
+        if (vObject.vObjects != null) {
+            if (Settings.getBooleanProperty("davmail.caldavDisableReminders", false)) {
+                ArrayList<VObject> vAlarms = null;
+                for (VObject vAlarm : vObject.vObjects) {
+                    if ("VALARM".equals(vAlarm.type)) {
+                        if (vAlarms == null) {
+                            vAlarms = new ArrayList<VObject>();
+                        }
+                        vAlarms.add(vAlarm);
+                    }
+                }
+                // remove all vAlarms
+                if (vAlarms != null) {
+                    for (VObject vAlarm : vAlarms) {
+                        vObject.vObjects.remove(vAlarm);
+                    }
+                }
+
+            } else {
+                for (VObject vAlarm : vObject.vObjects) {
+                    if ("VALARM".equals(vAlarm.type)) {
+                        String action = vAlarm.getPropertyValue("ACTION");
+                        if (fromServer && "DISPLAY".equals(action)
+                                // convert DISPLAY to AUDIO only if user defined an alarm sound
+                                && Settings.getProperty("davmail.caldavAlarmSound") != null) {
+                            // Convert alarm to audio for iCal
+                            vAlarm.setPropertyValue("ACTION", "AUDIO");
+
+                            if (vAlarm.getPropertyValue("ATTACH") == null) {
+                                // Add defined sound into the audio alarm
+                                VProperty vProperty = new VProperty("ATTACH", Settings.getProperty("davmail.caldavAlarmSound"));
+                                vProperty.addParam("VALUE", "URI");
+                                vAlarm.addProperty(vProperty);
+                            }
+
+                        } else if (!fromServer && "AUDIO".equals(action)) {
+                            // Use the alarm action that exchange (and blackberry) understand
+                            // (exchange and blackberry don't understand audio actions)
+                            vAlarm.setPropertyValue("ACTION", "DISPLAY");
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Replace iCal4 (Snow Leopard) principal paths with mailto expression
+     *
+     * @param value attendee value or ics line
+     * @return fixed value
+     */
+    protected String replaceIcal4Principal(String value) {
+        if (value.contains("/principals/__uuids__/")) {
+            return value.replaceAll("/principals/__uuids__/([^/]*)__AT__([^/]*)/", "mailto:$1@$2");
+        } else {
+            return value;
+        }
+    }
+
+    private void fixAttendees(VObject vObject, boolean fromServer) {
+        if (vObject.properties != null) {
+            for (VProperty property : vObject.properties) {
+                if ("ATTENDEE".equalsIgnoreCase(property.getKey())) {
+                    if (fromServer) {
+                        // If this is coming from the server, strip out RSVP for this
+                        // user as an attendee where the partstat is something other
+                        // than PARTSTAT=NEEDS-ACTION since the RSVP confuses iCal4 into
+                        // thinking the attendee has not replied
+                        if (isCurrentUser(property) && property.hasParam("RSVP", "TRUE")) {
+                            VProperty.Param partstat = property.getParam("PARTSTAT");
+                            if (partstat == null || !"NEEDS-ACTION".equals(partstat.getValue())) {
+                                property.removeParam("RSVP");
+                            }
+                        }
+                    } else {
+                        property.setValue(replaceIcal4Principal(property.getValue()));
+                    }
+                }
+
+            }
+        }
+
+    }
+
+    private boolean isCurrentUser(VProperty property) {
+        return property.getValue().equalsIgnoreCase("mailto:" + email);
+    }
+
+    /**
+     * Return VTimezone object
+     *
+     * @return VTimezone
+     */
+    public VObject getVTimezone() {
+        return vTimezone;
+    }
+
+    /**
+     * Convert X-CALENDARSERVER-ACCESS to CLASS.
+     * see http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-privateevents.txt
+     *
+     * @param calendarServerAccess X-CALENDARSERVER-ACCESS value
+     * @return CLASS value
+     */
+    protected String getEventClass(String calendarServerAccess) {
+        if ("PRIVATE".equalsIgnoreCase(calendarServerAccess)) {
+            return "CONFIDENTIAL";
+        } else if ("CONFIDENTIAL".equalsIgnoreCase(calendarServerAccess) || "RESTRICTED".equalsIgnoreCase(calendarServerAccess)) {
+            return "PRIVATE";
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Convert CLASS to X-CALENDARSERVER-ACCESS.
+     * see http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-privateevents.txt     *
+     *
+     * @return X-CALENDARSERVER-ACCESS value
+     */
+    protected String getCalendarServerAccess() {
+        String eventClass = getFirstVeventPropertyValue("CLASS");
+        if ("PRIVATE".equalsIgnoreCase(eventClass)) {
+            return "CONFIDENTIAL";
+        } else if ("CONFIDENTIAL".equalsIgnoreCase(eventClass)) {
+            return "PRIVATE";
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Get property value from first VEVENT in VCALENDAR.
+     *
+     * @param name property name
+     * @return property value
+     */
+    public String getFirstVeventPropertyValue(String name) {
+        if (firstVevent == null) {
+            return null;
+        } else {
+            return firstVevent.getPropertyValue(name);
+        }
+    }
+
+    protected VProperty getFirstVeventProperty(String name) {
+        if (firstVevent == null) {
+            return null;
+        } else {
+            return firstVevent.getProperty(name);
+        }
+    }
+
+
+    /**
+     * Get properties by name from first VEVENT.
+     *
+     * @param name property name
+     * @return properties
+     */
+    public List<VProperty> getFirstVeventProperties(String name) {
+        if (firstVevent == null) {
+            return null;
+        } else {
+            return firstVevent.getProperties(name);
+        }
+    }
+
+    /**
+     * Remove VAlarm from VCalendar.
+     */
+    public void removeVAlarm() {
+        if (vObjects != null) {
+            for (VObject vObject : vObjects) {
+                if ("VEVENT".equals(vObject.type)) {
+                    // As VALARM is the only possible inner object, just drop all objects
+                    if (vObject.vObjects != null) {
+                        vObject.vObjects = null;
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Check if VCalendar has a VALARM item.
+     *
+     * @return true if VCalendar has a VALARM
+     */
+    public boolean hasVAlarm() {
+        if (vObjects != null) {
+            for (VObject vObject : vObjects) {
+                if ("VEVENT".equals(vObject.type)) {
+                    if (vObject.vObjects != null) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Check if this VCalendar is a meeting.
+     *
+     * @return true if this VCalendar has attendees
+     */
+    public boolean isMeeting() {
+        return getFirstVeventProperty("ATTENDEE") != null;
+    }
+
+    /**
+     * Check if current user is meeting organizer.
+     *
+     * @return true it user email matched organizer email
+     */
+    public boolean isMeetingOrganizer() {
+        return email.equalsIgnoreCase(getEmailValue(getFirstVeventProperty("ORGANIZER")));
+    }
+
+    /**
+     * Set property value on first VEVENT.
+     *
+     * @param propertyName  property name
+     * @param propertyValue property value
+     */
+    public void setFirstVeventPropertyValue(String propertyName, String propertyValue) {
+        firstVevent.setPropertyValue(propertyName, propertyValue);
+    }
+
+    /**
+     * Add property on first VEVENT.
+     *
+     * @param vProperty property object
+     */
+    public void addFirstVeventProperty(VProperty vProperty) {
+        firstVevent.addProperty(vProperty);
+    }
+
+    /**
+     * Check if this item is a VTODO item
+     *
+     * @return true with VTODO items
+     */
+    public boolean isTodo() {
+        return "VTODO".equals(firstVevent.type);
+    }
+
+    /**
+     * VCalendar recipients for notifications
+     */
+    public static class Recipients {
+        /**
+         * attendee list
+         */
+        public String attendees;
+
+        /**
+         * optional attendee list
+         */
+        public String optionalAttendees;
+
+        /**
+         * vCalendar organizer
+         */
+        public String organizer;
+    }
+
+    /**
+     * Build recipients value for VCalendar.
+     *
+     * @param isNotification if true, filter recipients that should receive meeting notifications
+     * @return notification/event recipients
+     */
+    public Recipients getRecipients(boolean isNotification) {
+
+        HashSet<String> attendees = new HashSet<String>();
+        HashSet<String> optionalAttendees = new HashSet<String>();
+
+        // get recipients from first VEVENT
+        List<VProperty> attendeeProperties = getFirstVeventProperties("ATTENDEE");
+        if (attendeeProperties != null) {
+            for (VProperty property : attendeeProperties) {
+                // exclude current user and invalid values from recipients
+                // also exclude no action attendees
+                String attendeeEmail = getEmailValue(property);
+                if (!email.equalsIgnoreCase(attendeeEmail) && attendeeEmail != null && attendeeEmail.indexOf('@') >= 0
+                        // return all attendees for user calendar folder, filter for notifications
+                        && (!isNotification
+                        // notify attendee if reply explicitly requested
+                        || (property.hasParam("RSVP", "TRUE"))
+                        || (
+                        // workaround for iCal bug: do not notify if reply explicitly not requested
+                        !(property.hasParam("RSVP", "FALSE")) &&
+                                ((property.hasParam("PARTSTAT", "NEEDS-ACTION")
+                                        // need to include other PARTSTATs participants for CANCEL notifications
+                                        || property.hasParam("PARTSTAT", "ACCEPTED")
+                                        || property.hasParam("PARTSTAT", "DECLINED")
+                                        || property.hasParam("PARTSTAT", "TENTATIVE")))
+                ))) {
+                    if (property.hasParam("ROLE", "OPT-PARTICIPANT")) {
+                        optionalAttendees.add(attendeeEmail);
+                    } else {
+                        attendees.add(attendeeEmail);
+                    }
+                }
+            }
+        }
+        Recipients recipients = new Recipients();
+        recipients.organizer = getEmailValue(getFirstVeventProperty("ORGANIZER"));
+        recipients.attendees = StringUtil.join(attendees, ", ");
+        recipients.optionalAttendees = StringUtil.join(optionalAttendees, ", ");
+        return recipients;
+    }
+
+    protected String getAttendeeStatus() {
+        String status = null;
+        List<VProperty> attendeeProperties = getFirstVeventProperties("ATTENDEE");
+        if (attendeeProperties != null) {
+            for (VProperty property : attendeeProperties) {
+                String attendeeEmail = getEmailValue(property);
+                if (email.equalsIgnoreCase(attendeeEmail) && property.hasParam("PARTSTAT")) {
+                    // found current user attendee line
+                    status = property.getParam("PARTSTAT").getValue();
+                    break;
+                }
+            }
+        }
+        return status;
+    }
+
+    /**
+     * Get recurring VCalendar occurence exceptions.
+     *
+     * @return event occurences
+     */
+    public List<VObject> getModifiedOccurrences() {
+        boolean first = true;
+        ArrayList<VObject> results = new ArrayList<VObject>();
+        for (VObject vObject : vObjects) {
+            if ("VEVENT".equals(vObject.type)) {
+                if (first) {
+                    first = false;
+                } else {
+                    results.add(vObject);
+                }
+            }
+        }
+        return results;
+    }
+}
diff --git a/src/java/davmail/exchange/VCardWriter.java b/src/java/davmail/exchange/VCardWriter.java
new file mode 100644
index 0000000..cd67b0c
--- /dev/null
+++ b/src/java/davmail/exchange/VCardWriter.java
@@ -0,0 +1,92 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange;
+
+/**
+ * VCard Writer
+ */
+public class VCardWriter extends ICSBufferedWriter {
+    /**
+     * Begin VCard and version
+     */
+    public void startCard() {
+        writeLine("BEGIN:VCARD");
+        writeLine("VERSION:3.0");
+    }
+
+    /**
+     * Append compound value
+     *
+     * @param propertyName  property name
+     * @param propertyValue property values
+     */
+    public void appendProperty(String propertyName, String... propertyValue) {
+        boolean hasValue = false;
+        for (String value : propertyValue) {
+            if ((value != null) && (value.length() > 0)) {
+                hasValue = true;
+                break;
+            }
+        }
+        if (hasValue) {
+            boolean first = true;
+            StringBuilder lineBuffer = new StringBuilder();
+            lineBuffer.append(propertyName);
+            lineBuffer.append(':');
+            for (String value : propertyValue) {
+                if (first) {
+                    first = false;
+                } else {
+                    lineBuffer.append(';');
+                }
+                appendEncodedValue(lineBuffer, value);
+            }
+            writeLine(lineBuffer.toString());
+        }
+    }
+
+    /**
+     * Encode and append value to buffer
+     *
+     * @param buffer current buffer
+     * @param value  property value
+     */
+    private void appendEncodedValue(StringBuilder buffer, String value) {
+        if (value != null) {
+            for (int i = 0; i < value.length(); i++) {
+                char c = value.charAt(i);
+                if (c == ',' || c == ';') {
+                    buffer.append('\\');
+                }
+                if (c == '\n') {
+                    buffer.append("\\n");
+                } else {
+                    buffer.append(value.charAt(i));
+                }
+            }
+        }
+    }
+
+    /**
+     * End VCard
+     */
+    public void endCard() {
+        writeLine("END:VCARD");
+    }
+}
diff --git a/src/java/davmail/exchange/VObject.java b/src/java/davmail/exchange/VObject.java
new file mode 100644
index 0000000..f6fb76f
--- /dev/null
+++ b/src/java/davmail/exchange/VObject.java
@@ -0,0 +1,282 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Base class for VCalendar, VTimezone, VEvent.
+ */
+public class VObject {
+    /**
+     * VObject properties
+     */
+    ArrayList<VProperty> properties;
+    /**
+     * Inner VObjects (e.g. VEVENT, VALARM, ...)
+     */
+    ArrayList<VObject> vObjects;
+    /**
+     * Object base name (VCALENDAR, VEVENT, VCARD...).
+     */
+    public String type;
+
+    /**
+     * Create VObject with given type
+     *
+     * @param beginProperty first line property
+     * @param reader        stream reader just after the BEGIN:TYPE line
+     * @throws IOException on error
+     */
+    public VObject(VProperty beginProperty, BufferedReader reader) throws IOException {
+        if (!"BEGIN".equals(beginProperty.getKey())) {
+            throw new IOException("Invalid first line: " + beginProperty);
+        }
+        type = beginProperty.getValue();
+        String endLine = "END:" + type;
+        String line = reader.readLine();
+        while (line != null && !line.startsWith(endLine)) {
+            handleLine(line, reader);
+            line = reader.readLine();
+        }
+        if (line == null) {
+            throw new IOException("Unexpected end of stream");
+        }
+    }
+
+    /**
+     * Create VObject from reader.
+     *
+     * @param reader stream reader just after the BEGIN:TYPE line
+     * @throws IOException on error
+     */
+    public VObject(BufferedReader reader) throws IOException {
+        this(new VProperty(reader.readLine()), reader);
+    }
+
+    /**
+     * Create VCalendar object from string;
+     *
+     * @param itemBody item body
+     * @throws IOException on error
+     */
+    public VObject(String itemBody) throws IOException {
+        this(new ICSBufferedReader(new StringReader(itemBody)));
+    }
+
+    /**
+     * Create empty VCalendar object;
+     */
+    public VObject() {
+    }
+
+
+    protected void handleLine(String line, BufferedReader reader) throws IOException {
+        // skip empty lines
+        if (line.length() > 0) {
+            VProperty property = new VProperty(line);
+            // inner object
+            if ("BEGIN".equals(property.getKey())) {
+                addVObject(new VObject(property, reader));
+            } else if (property.getKey() != null) {
+                addProperty(property);
+            }
+        }
+    }
+
+    /**
+     * Add vObject.
+     *
+     * @param vObject inner object
+     */
+    public void addVObject(VObject vObject) {
+        if (vObjects == null) {
+            vObjects = new ArrayList<VObject>();
+        }
+        vObjects.add(vObject);
+    }
+
+    /**
+     * Add vProperty.
+     *
+     * @param property vProperty
+     */
+    public void addProperty(VProperty property) {
+        if (property.getValue() != null) {
+            if (properties == null) {
+                properties = new ArrayList<VProperty>();
+            }
+            properties.add(property);
+        }
+    }
+
+    /**
+     * Write VObject to writer.
+     *
+     * @param writer buffered writer
+     */
+    public void writeTo(ICSBufferedWriter writer) {
+        writer.write("BEGIN:");
+        writer.writeLine(type);
+        if (properties != null) {
+            for (VProperty property : properties) {
+                writer.writeLine(property.toString());
+            }
+        }
+        if (vObjects != null) {
+            for (VObject object : vObjects) {
+                object.writeTo(writer);
+            }
+        }
+        writer.write("END:");
+        writer.writeLine(type);
+    }
+
+    public String toString() {
+        ICSBufferedWriter writer = new ICSBufferedWriter();
+        writeTo(writer);
+        return writer.toString();
+    }
+
+    /**
+     * Get VObject properties
+     *
+     * @return properties
+     */
+    public List<VProperty> getProperties() {
+        return properties;
+    }
+
+    /**
+     * Get vProperty by name.
+     *
+     * @param name property name
+     * @return property object
+     */
+    public VProperty getProperty(String name) {
+        if (properties != null) {
+            for (VProperty property : properties) {
+                if (property.getKey() != null && property.getKey().equalsIgnoreCase(name)) {
+                    return property;
+                }
+            }
+
+        }
+        return null;
+    }
+
+    /**
+     * Get multivalued vProperty by name.
+     *
+     * @param name property name
+     * @return property list
+     */
+    public List<VProperty> getProperties(String name) {
+        List<VProperty> result = null;
+        if (properties != null) {
+            for (VProperty property : properties) {
+                if (property.getKey() != null && property.getKey().equalsIgnoreCase(name)) {
+                    if (result == null) {
+                        result = new ArrayList<VProperty>();
+                    }
+                    result.add(property);
+                }
+            }
+
+        }
+        return result;
+    }
+
+    /**
+     * Get vProperty value by name.
+     *
+     * @param name property name
+     * @return property value
+     */
+    public String getPropertyValue(String name) {
+        VProperty property = getProperty(name);
+        if (property != null) {
+            return property.getValue();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Set vProperty value on vObject, remove property if value is null.
+     *
+     * @param name  property name
+     * @param value property value
+     */
+    public void setPropertyValue(String name, String value) {
+        if (value == null) {
+            removeProperty(name);
+        } else {
+            VProperty property = getProperty(name);
+            if (property == null) {
+                property = new VProperty(name, value);
+                addProperty(property);
+            } else {
+                property.setValue(value);
+            }
+        }
+    }
+
+    /**
+     * Add vProperty value on vObject.
+     *
+     * @param name  property name
+     * @param value property value
+     */
+    public void addPropertyValue(String name, String value) {
+        if (value != null) {
+            VProperty property = new VProperty(name, value);
+            addProperty(property);
+        }
+    }
+
+    /**
+     * Remove vProperty from vObject.
+     *
+     * @param name property name
+     */
+    public void removeProperty(String name) {
+        if (properties != null) {
+            VProperty property = getProperty(name);
+            if (property != null) {
+                properties.remove(property);
+            }
+        }
+    }
+
+    /**
+     * Remove vProperty object from vObject.
+     *
+     * @param property object
+     */
+    public void removeProperty(VProperty property) {
+        if (properties != null) {
+            properties.remove(property);
+        }
+    }
+}
diff --git a/src/java/davmail/exchange/VProperty.java b/src/java/davmail/exchange/VProperty.java
new file mode 100644
index 0000000..c03cf5b
--- /dev/null
+++ b/src/java/davmail/exchange/VProperty.java
@@ -0,0 +1,452 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * VCard property
+ */
+public class VProperty {
+    protected static enum State {
+        KEY, PARAM_NAME, PARAM_VALUE, QUOTED_PARAM_VALUE, VALUE, BACKSLASH
+    }
+
+    protected static final HashSet<String> MULTIVALUED_PROPERTIES = new HashSet<String>();
+    static {
+        MULTIVALUED_PROPERTIES.add("RESOURCES");
+        MULTIVALUED_PROPERTIES.add("LOCATION");
+    }
+
+    protected static class Param {
+        String name;
+        List<String> values;
+
+        public void addAll(List<String> paramValues) {
+            if (values == null) {
+                values = new ArrayList<String>();
+            }
+            values.addAll(paramValues);
+        }
+
+        public String getValue() {
+            if (values != null && !values.isEmpty()) {
+                return values.get(0);
+            } else {
+                return null;
+            }
+        }
+    }
+
+    protected String key;
+    protected List<Param> params;
+    protected List<String> values;
+
+    /**
+     * Create VProperty for key and value.
+     *
+     * @param name  property name
+     * @param value property value
+     */
+    public VProperty(String name, String value) {
+        setKey(name);
+        setValue(value);
+    }
+
+    /**
+     * Create VProperty from line.
+     *
+     * @param line card line
+     */
+    public VProperty(String line) {
+        if (line != null && !"END:VCARD".equals(line)) {
+            State state = State.KEY;
+            String paramName = null;
+            List<String> paramValues = null;
+            int startIndex = 0;
+            for (int i = 0; i < line.length(); i++) {
+                char currentChar = line.charAt(i);
+                if (state == State.KEY) {
+                    if (currentChar == ':') {
+                        setKey(line.substring(startIndex, i));
+                        state = State.VALUE;
+                        startIndex = i + 1;
+                    } else if (currentChar == ';') {
+                        setKey(line.substring(startIndex, i));
+                        state = State.PARAM_NAME;
+                        startIndex = i + 1;
+                    }
+                } else if (state == State.PARAM_NAME) {
+                    if (currentChar == '=') {
+                        paramName = line.substring(startIndex, i).toUpperCase();
+                        state = State.PARAM_VALUE;
+                        paramValues = new ArrayList<String>();
+                        startIndex = i + 1;
+                    } else if (currentChar == ';') {
+                        // param with no value
+                        paramName = line.substring(startIndex, i).toUpperCase();
+                        addParam(paramName);
+                        state = State.PARAM_NAME;
+                        startIndex = i + 1;
+                    } else if (currentChar == ':') {
+                        // param with no value
+                        paramName = line.substring(startIndex, i).toUpperCase();
+                        addParam(paramName);
+                        state = State.VALUE;
+                        startIndex = i + 1;
+                    }
+                } else if (state == State.PARAM_VALUE) {
+                    if (currentChar == '"') {
+                        state = State.QUOTED_PARAM_VALUE;
+                        startIndex = i + 1;
+                    } else if (currentChar == ':') {
+                        if (startIndex < i) {
+                            paramValues.add(line.substring(startIndex, i));
+                        }
+                        addParam(paramName, paramValues);
+                        state = State.VALUE;
+                        startIndex = i + 1;
+                    } else if (currentChar == ';') {
+                        if (startIndex < i) {
+                            paramValues.add(line.substring(startIndex, i));
+                        }
+                        addParam(paramName, paramValues);
+                        state = State.PARAM_NAME;
+                        startIndex = i + 1;
+                    } else if (currentChar == ',') {
+                        if (startIndex < i) {
+                            paramValues.add(line.substring(startIndex, i));
+                        }
+                        startIndex = i + 1;
+                    }
+                } else if (state == State.QUOTED_PARAM_VALUE) {
+                    if (currentChar == '"') {
+                        state = State.PARAM_VALUE;
+                        paramValues.add(line.substring(startIndex, i));
+                        startIndex = i + 1;
+                    }
+                } else if (state == State.VALUE) {
+                    if (currentChar == '\\') {
+                        state = State.BACKSLASH;
+                    } else if (currentChar == ';' || (MULTIVALUED_PROPERTIES.contains(key) && currentChar == ',')) {
+                        addValue(line.substring(startIndex, i));
+                        startIndex = i + 1;
+                    }
+                } else if (state == State.BACKSLASH) {
+                    state = State.VALUE;
+                }
+            }
+            if (state == State.VALUE) {
+                addValue(line.substring(startIndex));
+            } else {
+                throw new IllegalArgumentException("Invalid property line: " + line);
+            }
+        }
+    }
+
+    /**
+     * Property key, without optional parameters (e.g. TEL).
+     *
+     * @return key
+     */
+    public String getKey() {
+        return key;
+    }
+
+    /**
+     * Property value.
+     *
+     * @return value
+     */
+    public String getValue() {
+        if (values == null || values.isEmpty()) {
+            return null;
+        } else {
+            return values.get(0);
+        }
+    }
+
+    /**
+     * Property values.
+     *
+     * @return values
+     */
+    public List<String> getValues() {
+        return values;
+    }
+
+    /**
+     * Test if the property has a param named paramName with given value.
+     *
+     * @param paramName  param name
+     * @param paramValue param value
+     * @return true if property has param name and value
+     */
+    public boolean hasParam(String paramName, String paramValue) {
+        return params != null && getParam(paramName) != null && containsIgnoreCase(getParam(paramName).values, paramValue);
+    }
+
+    /**
+     * Test if the property has a param named paramName.
+     *
+     * @param paramName param name
+     * @return true if property has param name
+     */
+    public boolean hasParam(String paramName) {
+        return params != null && getParam(paramName) != null;
+    }
+
+    /**
+     * Remove param from property.
+     *
+     * @param paramName param name
+     */
+    public void removeParam(String paramName) {
+        if (params != null) {
+            Param param = getParam(paramName);
+            if (param != null) {
+                params.remove(param);
+            }
+        }
+    }
+
+    protected boolean containsIgnoreCase(List<String> stringCollection, String value) {
+        for (String collectionValue : stringCollection) {
+            if (value.equalsIgnoreCase(collectionValue)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    protected void addParam(String paramName) {
+        addParam(paramName, (String) null);
+    }
+
+    /**
+     * Set param value on property.
+     *
+     * @param paramName param name
+     * @param paramValue param value
+     */
+    public void setParam(String paramName, String paramValue) {
+        Param currentParam = getParam(paramName);
+        if (currentParam != null) {
+            getParams().remove(currentParam);
+        }
+        addParam(paramName, paramValue);
+    }
+
+    /**
+     * Add param value on property.
+     *
+     * @param paramName param name
+     * @param paramValue param value
+     */
+    public void addParam(String paramName, String paramValue) {
+        List<String> paramValues = new ArrayList<String>();
+        paramValues.add(paramValue);
+        addParam(paramName, paramValues);
+    }
+
+    protected void addParam(String paramName, List<String> paramValues) {
+        if (params == null) {
+            params = new ArrayList<Param>();
+        }
+        Param currentParam = getParam(paramName);
+        if (currentParam == null) {
+            currentParam = new Param();
+            currentParam.name = paramName;
+            params.add(currentParam);
+        }
+        currentParam.addAll(paramValues);
+    }
+
+    protected Param getParam(String paramName) {
+        if (params != null) {
+            for (Param param : params) {
+                if (paramName.equals(param.name)) {
+                    return param;
+                }
+            }
+        }
+        return null;
+    }
+
+    public String getParamValue(String paramName) {
+        Param param = getParam(paramName);
+        if (param != null) {
+            return param.getValue();
+        } else {
+            return null;
+        }
+    }
+
+    protected List<Param> getParams() {
+        return params;
+    }
+
+    protected void setParams(List<Param> params) {
+        this.params = params;
+    }
+
+    protected void setValue(String value) {
+        if (value == null) {
+            values = null;
+        } else {
+            if (values == null) {
+                values = new ArrayList<String>();
+            } else {
+                values.clear();
+            }
+            values.add(decodeValue(value));
+        }
+    }
+
+    protected void addValue(String value) {
+        if (values == null) {
+            values = new ArrayList<String>();
+        }
+        values.add(decodeValue(value));
+    }
+
+    protected String decodeValue(String value) {
+        if (value == null || (value.indexOf('\\') < 0 && value.indexOf(',') < 0)) {
+            return value;
+        } else {
+            // decode value
+            StringBuilder decodedValue = new StringBuilder();
+            for (int i = 0; i < value.length(); i++) {
+                char c = value.charAt(i);
+                if (c == '\\') {
+                    //noinspection AssignmentToForLoopParameter
+                    i++;
+                    if (i == value.length()) {
+                        break;
+                    }
+                    c = value.charAt(i);
+                    if (c == 'n' || c == 'N') {
+                        c = '\n';
+                    } else if (c == 'r') {
+                        c = '\r';
+                    }
+                }
+                // iPhone encodes category separator
+                if (c == ',' &&
+                        // multivalued properties
+                        ("N".equals(key) ||
+                                "ADR".equals(key) ||
+                                "CATEGORIES".equals(key) ||
+                                "NICKNAME".equals(key)
+                        )) {
+                    // convert multiple values to multiline values (e.g. street)
+                    c = '\n';
+                }
+                decodedValue.append(c);
+            }
+            return decodedValue.toString();
+        }
+    }
+
+    /**
+     * Set property key.
+     *
+     * @param key property key
+     */
+    public void setKey(String key) {
+        int dotIndex = key.indexOf('.');
+        if (dotIndex < 0) {
+            this.key = key;
+        } else {
+            this.key = key.substring(dotIndex + 1);
+        }
+    }
+
+    public String toString() {
+        StringBuilder buffer = new StringBuilder();
+        buffer.append(key);
+        if (params != null) {
+            for (Param param : params) {
+                buffer.append(';').append(param.name);
+                if (param.values != null) {
+                    buffer.append('=');
+                    appendParamValues(buffer, param);
+                }
+            }
+        }
+        buffer.append(':');
+        if (values != null) {
+            boolean firstValue = true;
+            for (String value : values) {
+                if (firstValue) {
+                    firstValue = false;
+                } else if (MULTIVALUED_PROPERTIES.contains(key)) {
+                    buffer.append(',');
+                } else {
+                    buffer.append(';');
+                }
+                appendMultilineEncodedValue(buffer, value);
+            }
+        }
+        return buffer.toString();
+    }
+
+    protected void appendParamValues(StringBuilder buffer, Param param) {
+        boolean first = true;
+        for (String value : param.values) {
+            if (first) {
+                first = false;
+            } else {
+                buffer.append(',');
+            }
+            // always quote CN param
+            if ("CN".equalsIgnoreCase(param.name)
+                    // quote param values with special characters
+                    || value.indexOf(';') >= 0 || value.indexOf(',') >= 0
+                    || value.indexOf('(') >= 0 || value.indexOf('/') >= 0
+                    || value.indexOf(':') >= 0) {
+                buffer.append('"').append(value).append('"');
+            } else {
+                buffer.append(value);
+            }
+        }
+    }
+
+    /**
+     * Append and encode \n to \\n in value.
+     *
+     * @param buffer line buffer
+     * @param value  value
+     */
+    protected void appendMultilineEncodedValue(StringBuilder buffer, String value) {
+        for (int i = 0; i < value.length(); i++) {
+            char c = value.charAt(i);
+            if (c == '\n') {
+                buffer.append("\\n");
+            } else if (MULTIVALUED_PROPERTIES.contains(key) && c==',') {
+                buffer.append('\\').append(',');
+            } else {
+                buffer.append(value.charAt(i));
+            }
+        }
+    }
+
+}
diff --git a/src/java/davmail/exchange/XMLStreamUtil.java b/src/java/davmail/exchange/XMLStreamUtil.java
new file mode 100644
index 0000000..88cb74b
--- /dev/null
+++ b/src/java/davmail/exchange/XMLStreamUtil.java
@@ -0,0 +1,190 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange;
+
+import org.apache.log4j.Logger;
+
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * XmlStreamReader utility methods
+ */
+ at SuppressWarnings("Since15")
+public final class XMLStreamUtil {
+    private static final Logger LOGGER = Logger.getLogger(XMLStreamUtil.class);
+
+    private XMLStreamUtil() {
+    }
+
+    /**
+     * Build a new XMLInputFactory.
+     *
+     * @return XML input factory
+     */
+    public static XMLInputFactory getXmlInputFactory() {
+        XMLInputFactory inputFactory = XMLInputFactory.newInstance();
+        inputFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
+        inputFactory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, Boolean.TRUE);
+        return inputFactory;
+    }
+
+    /**
+     * Convert the XML stream to a map of entries.
+     * An entry is also a key/value map
+     *
+     * @param inputStream xml input stream
+     * @param rowName     xml tag name of entries
+     * @param idName      xml tag name of entry attribute used as key in the main map
+     * @return map of entries
+     * @throws IOException on error
+     */
+    public static Map<String, Map<String, String>> getElementContentsAsMap(InputStream inputStream, String rowName, String idName) throws IOException {
+        Map<String, Map<String, String>> results = new HashMap<String, Map<String, String>>();
+        Map<String, String> item = null;
+        String currentElement = null;
+        XMLStreamReader reader = null;
+        try {
+            XMLInputFactory inputFactory = getXmlInputFactory();
+            reader = inputFactory.createXMLStreamReader(inputStream);
+            while (reader.hasNext()) {
+                int event = reader.next();
+                if (event == XMLStreamConstants.START_ELEMENT && rowName.equals(reader.getLocalName())) {
+                    item = new HashMap<String, String>();
+                } else if (event == XMLStreamConstants.END_ELEMENT && rowName.equals(reader.getLocalName())) {
+                    if (item.containsKey(idName)) {
+                        results.put(item.get(idName).toLowerCase(), item);
+                    }
+                    item = null;
+                } else if (event == XMLStreamConstants.START_ELEMENT && item != null) {
+                    currentElement = reader.getLocalName();
+                } else if (event == XMLStreamConstants.CHARACTERS && currentElement != null) {
+                    item.put(currentElement, reader.getText());
+                    currentElement = null;
+                }
+            }
+        } catch (XMLStreamException e) {
+            throw new IOException(e.getMessage());
+        } finally {
+            try {
+                if (reader != null) {
+                    reader.close();
+                }
+            } catch (XMLStreamException e) {
+                ExchangeSession.LOGGER.error(e);
+            }
+        }
+        return results;
+    }
+
+    /**
+     * Test if reader is on a start tag named tagLocalName.
+     *
+     * @param reader       xml stream reader
+     * @param tagLocalName tag local name
+     * @return true if reader is on a start tag named tagLocalName
+     */
+    public static boolean isStartTag(XMLStreamReader reader, String tagLocalName) {
+        return (reader.getEventType() == XMLStreamConstants.START_ELEMENT) && (reader.getLocalName().equals(tagLocalName));
+    }
+
+    /**
+     * Test if reader is on a start tag.
+     *
+     * @param reader xml stream reader
+     * @return true if reader is on a start tag
+     */
+    public static boolean isStartTag(XMLStreamReader reader) {
+        return (reader.getEventType() == XMLStreamConstants.START_ELEMENT);
+    }
+
+    /**
+     * Test if reader is on an end tag named tagLocalName.
+     *
+     * @param reader       xml stream reader
+     * @param tagLocalName tag local name
+     * @return true if reader is on an end tag named tagLocalName
+     */
+    public static boolean isEndTag(XMLStreamReader reader, String tagLocalName) {
+        return (reader.getEventType() == XMLStreamConstants.END_ELEMENT) && (reader.getLocalName().equals(tagLocalName));
+    }
+
+    /**
+     * Create XML stream reader for byte array
+     *
+     * @param xmlContent xml content as byte array
+     * @return XML stream reader
+     * @throws XMLStreamException on error
+     */
+    public static XMLStreamReader createXMLStreamReader(byte[] xmlContent) throws XMLStreamException {
+        return createXMLStreamReader(new ByteArrayInputStream(xmlContent));
+    }
+
+    /**
+     * Create XML stream reader for string
+     *
+     * @param xmlContent xml content as string
+     * @return XML stream reader
+     * @throws XMLStreamException on error
+     */
+    public static XMLStreamReader createXMLStreamReader(String xmlContent) throws XMLStreamException {
+        XMLInputFactory xmlInputFactory = XMLStreamUtil.getXmlInputFactory();
+        return xmlInputFactory.createXMLStreamReader(new StringReader(xmlContent));
+    }
+
+    /**
+     * Create XML stream reader for inputStream
+     *
+     * @param inputStream xml content inputStream
+     * @return XML stream reader
+     * @throws XMLStreamException on error
+     */
+    public static XMLStreamReader createXMLStreamReader(InputStream inputStream) throws XMLStreamException {
+        XMLInputFactory xmlInputFactory = XMLStreamUtil.getXmlInputFactory();
+        return xmlInputFactory.createXMLStreamReader(inputStream);
+    }
+
+    /**
+     * Get element text.
+     *
+     * @param reader stream reader
+     * @return element text
+     */
+    public static String getElementText(XMLStreamReader reader) {
+        String value = null;
+        try {
+            value = reader.getElementText();
+        } catch (XMLStreamException e) {
+            LOGGER.warn(e.getMessage());
+        } catch (RuntimeException e) {
+            // probably com.ctc.wstx.exc.WstxLazyException on invalid character sequence
+            LOGGER.warn(e.getMessage());
+        }
+        return value;
+    }
+
+}
diff --git a/src/java/davmail/exchange/dav/DavExchangeSession.java b/src/java/davmail/exchange/dav/DavExchangeSession.java
new file mode 100644
index 0000000..8b97101
--- /dev/null
+++ b/src/java/davmail/exchange/dav/DavExchangeSession.java
@@ -0,0 +1,3122 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.dav;
+
+import davmail.BundleMessage;
+import davmail.Settings;
+import davmail.exception.*;
+import davmail.exchange.*;
+import davmail.http.DavGatewayHttpClientFacade;
+import davmail.ui.tray.DavGatewayTray;
+import davmail.util.IOUtil;
+import davmail.util.StringUtil;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.httpclient.*;
+import org.apache.commons.httpclient.methods.*;
+import org.apache.commons.httpclient.util.URIUtil;
+import org.apache.jackrabbit.webdav.DavConstants;
+import org.apache.jackrabbit.webdav.DavException;
+import org.apache.jackrabbit.webdav.MultiStatus;
+import org.apache.jackrabbit.webdav.MultiStatusResponse;
+import org.apache.jackrabbit.webdav.client.methods.CopyMethod;
+import org.apache.jackrabbit.webdav.client.methods.MoveMethod;
+import org.apache.jackrabbit.webdav.client.methods.PropFindMethod;
+import org.apache.jackrabbit.webdav.client.methods.PropPatchMethod;
+import org.apache.jackrabbit.webdav.property.DavProperty;
+import org.apache.jackrabbit.webdav.property.DavPropertyNameSet;
+import org.apache.jackrabbit.webdav.property.DavPropertySet;
+import org.w3c.dom.Node;
+
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+import javax.mail.internet.MimePart;
+import javax.mail.util.SharedByteArrayInputStream;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+import java.io.*;
+import java.net.NoRouteToHostException;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.zip.GZIPInputStream;
+
+/**
+ * Webdav Exchange adapter.
+ * Compatible with Exchange 2003 and 2007 with webdav available.
+ */
+public class DavExchangeSession extends ExchangeSession {
+    protected static enum FolderQueryTraversal {
+        Shallow, Deep
+    }
+
+    protected static final DavPropertyNameSet WELL_KNOWN_FOLDERS = new DavPropertyNameSet();
+
+    static {
+        WELL_KNOWN_FOLDERS.add(Field.getPropertyName("inbox"));
+        WELL_KNOWN_FOLDERS.add(Field.getPropertyName("deleteditems"));
+        WELL_KNOWN_FOLDERS.add(Field.getPropertyName("sentitems"));
+        WELL_KNOWN_FOLDERS.add(Field.getPropertyName("sendmsg"));
+        WELL_KNOWN_FOLDERS.add(Field.getPropertyName("drafts"));
+        WELL_KNOWN_FOLDERS.add(Field.getPropertyName("calendar"));
+        WELL_KNOWN_FOLDERS.add(Field.getPropertyName("tasks"));
+        WELL_KNOWN_FOLDERS.add(Field.getPropertyName("contacts"));
+        WELL_KNOWN_FOLDERS.add(Field.getPropertyName("outbox"));
+    }
+
+    static final Map<String, String> vTodoToTaskStatusMap = new HashMap<String, String>();
+    static final Map<String, String> taskTovTodoStatusMap = new HashMap<String, String>();
+
+    static {
+        //taskTovTodoStatusMap.put("0", null);
+        taskTovTodoStatusMap.put("1", "IN-PROCESS");
+        taskTovTodoStatusMap.put("2", "COMPLETED");
+        taskTovTodoStatusMap.put("3", "NEEDS-ACTION");
+        taskTovTodoStatusMap.put("4", "CANCELLED");
+
+        //vTodoToTaskStatusMap.put(null, "0");
+        vTodoToTaskStatusMap.put("IN-PROCESS", "1");
+        vTodoToTaskStatusMap.put("COMPLETED", "2");
+        vTodoToTaskStatusMap.put("NEEDS-ACTION", "3");
+        vTodoToTaskStatusMap.put("CANCELLED", "4");
+    }
+
+    /**
+     * Various standard mail boxes Urls
+     */
+    protected String inboxUrl;
+    protected String deleteditemsUrl;
+    protected String sentitemsUrl;
+    protected String sendmsgUrl;
+    protected String draftsUrl;
+    protected String calendarUrl;
+    protected String tasksUrl;
+    protected String contactsUrl;
+    protected String outboxUrl;
+
+    protected String inboxName;
+    protected String deleteditemsName;
+    protected String sentitemsName;
+    protected String sendmsgName;
+    protected String draftsName;
+    protected String calendarName;
+    protected String tasksName;
+    protected String contactsName;
+    protected String outboxName;
+
+    protected static final String USERS = "/users/";
+
+    @Override
+    public boolean isExpired() throws NoRouteToHostException, UnknownHostException {
+        // experimental: try to reset session timeout
+        if ("Exchange2007".equals(serverVersion)) {
+            GetMethod getMethod = null;
+            try {
+                getMethod = new GetMethod("/owa/");
+                getMethod.setFollowRedirects(false);
+                httpClient.executeMethod(getMethod);
+            } catch (IOException e) {
+                LOGGER.warn(e.getMessage());
+            } finally {
+                if (getMethod != null) {
+                    getMethod.releaseConnection();
+                }
+            }
+        }
+
+        return super.isExpired();
+    }
+
+
+    /**
+     * Convert logical or relative folder path to exchange folder path.
+     *
+     * @param folderPath folder name
+     * @return folder path
+     */
+    public String getFolderPath(String folderPath) {
+        String exchangeFolderPath;
+        // IMAP path
+        if (folderPath.startsWith(INBOX)) {
+            exchangeFolderPath = mailPath + inboxName + folderPath.substring(INBOX.length());
+        } else if (folderPath.startsWith(TRASH)) {
+            exchangeFolderPath = mailPath + deleteditemsName + folderPath.substring(TRASH.length());
+        } else if (folderPath.startsWith(DRAFTS)) {
+            exchangeFolderPath = mailPath + draftsName + folderPath.substring(DRAFTS.length());
+        } else if (folderPath.startsWith(SENT)) {
+            exchangeFolderPath = mailPath + sentitemsName + folderPath.substring(SENT.length());
+        } else if (folderPath.startsWith(SENDMSG)) {
+            exchangeFolderPath = mailPath + sendmsgName + folderPath.substring(SENDMSG.length());
+        } else if (folderPath.startsWith(CONTACTS)) {
+            exchangeFolderPath = mailPath + contactsName + folderPath.substring(CONTACTS.length());
+        } else if (folderPath.startsWith(CALENDAR)) {
+            exchangeFolderPath = mailPath + calendarName + folderPath.substring(CALENDAR.length());
+        } else if (folderPath.startsWith(TASKS)) {
+            exchangeFolderPath = mailPath + tasksName + folderPath.substring(TASKS.length());
+        } else if (folderPath.startsWith("public")) {
+            exchangeFolderPath = publicFolderUrl + folderPath.substring("public".length());
+
+            // caldav path
+        } else if (folderPath.startsWith(USERS)) {
+            // get requested principal
+            String principal;
+            String localPath;
+            int principalIndex = folderPath.indexOf('/', USERS.length());
+            if (principalIndex >= 0) {
+                principal = folderPath.substring(USERS.length(), principalIndex);
+                localPath = folderPath.substring(USERS.length() + principal.length() + 1);
+                if (localPath.startsWith(LOWER_CASE_INBOX) || localPath.startsWith(INBOX)) {
+                    localPath = inboxName + localPath.substring(LOWER_CASE_INBOX.length());
+                } else if (localPath.startsWith(CALENDAR)) {
+                    localPath = calendarName + localPath.substring(CALENDAR.length());
+                } else if (localPath.startsWith(TASKS)) {
+                    localPath = tasksName + localPath.substring(TASKS.length());
+                } else if (localPath.startsWith(CONTACTS)) {
+                    localPath = contactsName + localPath.substring(CONTACTS.length());
+                } else if (localPath.startsWith(ADDRESSBOOK)) {
+                    localPath = contactsName + localPath.substring(ADDRESSBOOK.length());
+                }
+            } else {
+                principal = folderPath.substring(USERS.length());
+                localPath = "";
+            }
+            if (principal.length() == 0) {
+                exchangeFolderPath = rootPath;
+            } else if (alias.equalsIgnoreCase(principal) || email.equalsIgnoreCase(principal)) {
+                exchangeFolderPath = mailPath + localPath;
+            } else {
+                LOGGER.debug("Detected shared path for principal " + principal + ", user principal is " + email);
+                exchangeFolderPath = rootPath + principal + '/' + localPath;
+            }
+
+            // absolute folder path
+        } else if (folderPath.startsWith("/")) {
+            exchangeFolderPath = folderPath;
+        } else {
+            exchangeFolderPath = mailPath + folderPath;
+        }
+        return exchangeFolderPath;
+    }
+
+    /**
+     * Test if folderPath is inside user mailbox.
+     *
+     * @param folderPath absolute folder path
+     * @return true if folderPath is a public or shared folder
+     */
+    @Override
+    public boolean isSharedFolder(String folderPath) {
+        return !getFolderPath(folderPath).toLowerCase().startsWith(mailPath.toLowerCase());
+    }
+
+    /**
+     * Test if folderPath is main calendar.
+     *
+     * @param folderPath absolute folder path
+     * @return true if folderPath is a public or shared folder
+     */
+    @Override
+    public boolean isMainCalendar(String folderPath) {
+        return getFolderPath(folderPath).equalsIgnoreCase(getFolderPath("calendar"));
+    }
+
+    /**
+     * Build base path for cmd commands (galfind, gallookup).
+     *
+     * @return cmd base path
+     */
+    public String getCmdBasePath() {
+        if (("Exchange2003".equals(serverVersion) || PUBLIC_ROOT.equals(publicFolderUrl)) && mailPath != null) {
+            // public folder is not available => try to use mailbox path
+            // Note: This does not work with freebusy, which requires /public/
+            return mailPath;
+        } else {
+            // use public folder url
+            return publicFolderUrl;
+        }
+    }
+
+    /**
+     * LDAP to Exchange Criteria Map
+     */
+    static final HashMap<String, String> GALFIND_CRITERIA_MAP = new HashMap<String, String>();
+
+    static {
+        GALFIND_CRITERIA_MAP.put("imapUid", "AN");
+        GALFIND_CRITERIA_MAP.put("smtpemail1", "EM");
+        GALFIND_CRITERIA_MAP.put("cn", "DN");
+        GALFIND_CRITERIA_MAP.put("givenName", "FN");
+        GALFIND_CRITERIA_MAP.put("sn", "LN");
+        GALFIND_CRITERIA_MAP.put("title", "TL");
+        GALFIND_CRITERIA_MAP.put("o", "CP");
+        GALFIND_CRITERIA_MAP.put("l", "OF");
+        GALFIND_CRITERIA_MAP.put("department", "DP");
+    }
+
+    static final HashSet<String> GALLOOKUP_ATTRIBUTES = new HashSet<String>();
+
+    static {
+        GALLOOKUP_ATTRIBUTES.add("givenName");
+        GALLOOKUP_ATTRIBUTES.add("initials");
+        GALLOOKUP_ATTRIBUTES.add("sn");
+        GALLOOKUP_ATTRIBUTES.add("street");
+        GALLOOKUP_ATTRIBUTES.add("st");
+        GALLOOKUP_ATTRIBUTES.add("postalcode");
+        GALLOOKUP_ATTRIBUTES.add("co");
+        GALLOOKUP_ATTRIBUTES.add("departement");
+        GALLOOKUP_ATTRIBUTES.add("mobile");
+    }
+
+    /**
+     * Exchange to LDAP attribute map
+     */
+    static final HashMap<String, String> GALFIND_ATTRIBUTE_MAP = new HashMap<String, String>();
+
+    static {
+        GALFIND_ATTRIBUTE_MAP.put("uid", "AN");
+        GALFIND_ATTRIBUTE_MAP.put("smtpemail1", "EM");
+        GALFIND_ATTRIBUTE_MAP.put("cn", "DN");
+        GALFIND_ATTRIBUTE_MAP.put("displayName", "DN");
+        GALFIND_ATTRIBUTE_MAP.put("telephoneNumber", "PH");
+        GALFIND_ATTRIBUTE_MAP.put("l", "OFFICE");
+        GALFIND_ATTRIBUTE_MAP.put("o", "CP");
+        GALFIND_ATTRIBUTE_MAP.put("title", "TL");
+
+        GALFIND_ATTRIBUTE_MAP.put("givenName", "first");
+        GALFIND_ATTRIBUTE_MAP.put("initials", "initials");
+        GALFIND_ATTRIBUTE_MAP.put("sn", "last");
+        GALFIND_ATTRIBUTE_MAP.put("street", "street");
+        GALFIND_ATTRIBUTE_MAP.put("st", "state");
+        GALFIND_ATTRIBUTE_MAP.put("postalcode", "zip");
+        GALFIND_ATTRIBUTE_MAP.put("co", "country");
+        GALFIND_ATTRIBUTE_MAP.put("department", "department");
+        GALFIND_ATTRIBUTE_MAP.put("mobile", "mobile");
+        GALFIND_ATTRIBUTE_MAP.put("roomnumber", "office");
+    }
+
+    boolean disableGalFind;
+
+    protected Map<String, Map<String, String>> galFind(String query) throws IOException {
+        Map<String, Map<String, String>> results;
+        String path = getCmdBasePath() + "?Cmd=galfind" + query;
+        GetMethod getMethod = new GetMethod(path);
+        try {
+            DavGatewayHttpClientFacade.executeGetMethod(httpClient, getMethod, true);
+            results = XMLStreamUtil.getElementContentsAsMap(getMethod.getResponseBodyAsStream(), "item", "AN");
+            if (LOGGER.isDebugEnabled()) {
+                LOGGER.debug(path + ": " + results.size() + " result(s)");
+            }
+        } catch (IOException e) {
+            LOGGER.debug("GET " + path + " failed: " + e + ' ' + e.getMessage());
+            disableGalFind = true;
+            throw e;
+        } finally {
+            getMethod.releaseConnection();
+        }
+        return results;
+    }
+
+
+    @Override
+    public Map<String, ExchangeSession.Contact> galFind(Condition condition, Set<String> returningAttributes, int sizeLimit) throws IOException {
+        Map<String, ExchangeSession.Contact> contacts = new HashMap<String, ExchangeSession.Contact>();
+        if (disableGalFind) {
+            // do nothing
+        } else if (condition instanceof MultiCondition) {
+            List<Condition> conditions = ((ExchangeSession.MultiCondition) condition).getConditions();
+            Operator operator = ((ExchangeSession.MultiCondition) condition).getOperator();
+            if (operator == Operator.Or) {
+                for (Condition innerCondition : conditions) {
+                    contacts.putAll(galFind(innerCondition, returningAttributes, sizeLimit));
+                }
+            } else if (operator == Operator.And && !conditions.isEmpty()) {
+                Map<String, ExchangeSession.Contact> innerContacts = galFind(conditions.get(0), returningAttributes, sizeLimit);
+                for (ExchangeSession.Contact contact : innerContacts.values()) {
+                    if (condition.isMatch(contact)) {
+                        contacts.put(contact.getName().toLowerCase(), contact);
+                    }
+                }
+            }
+        } else if (condition instanceof AttributeCondition) {
+            String searchAttributeName = ((ExchangeSession.AttributeCondition) condition).getAttributeName();
+            String searchAttribute = GALFIND_CRITERIA_MAP.get(searchAttributeName);
+            if (searchAttribute != null) {
+                String searchValue = ((ExchangeSession.AttributeCondition) condition).getValue();
+                StringBuilder query = new StringBuilder();
+                if ("EM".equals(searchAttribute)) {
+                    // mail search, split
+                    int atIndex = searchValue.indexOf('@');
+                    // remove suffix
+                    if (atIndex >= 0) {
+                        searchValue = searchValue.substring(0, atIndex);
+                    }
+                    // split firstname.lastname
+                    int dotIndex = searchValue.indexOf('.');
+                    if (dotIndex >= 0) {
+                        // assume mail starts with firstname
+                        query.append("&FN=").append(URIUtil.encodeWithinQuery(searchValue.substring(0, dotIndex)));
+                        query.append("&LN=").append(URIUtil.encodeWithinQuery(searchValue.substring(dotIndex + 1)));
+                    } else {
+                        query.append("&FN=").append(URIUtil.encodeWithinQuery(searchValue));
+                    }
+                } else {
+                    query.append('&').append(searchAttribute).append('=').append(URIUtil.encodeWithinQuery(searchValue));
+                }
+                Map<String, Map<String, String>> results = galFind(query.toString());
+                for (Map<String, String> result : results.values()) {
+                    Contact contact = new Contact();
+                    contact.setName(result.get("AN"));
+                    contact.put("imapUid", result.get("AN"));
+                    buildGalfindContact(contact, result);
+                    if (needGalLookup(searchAttributeName, returningAttributes)) {
+                        galLookup(contact);
+                        // iCal fix to suit both iCal 3 and 4:  move cn to sn, remove cn
+                    } else if (returningAttributes.contains("apple-serviceslocator")) {
+                        if (contact.get("cn") != null && returningAttributes.contains("sn")) {
+                            contact.put("sn", contact.get("cn"));
+                            contact.remove("cn");
+                        }
+                    }
+                    if (condition.isMatch(contact)) {
+                        contacts.put(contact.getName().toLowerCase(), contact);
+                    }
+                }
+            }
+
+        }
+        return contacts;
+    }
+
+    protected boolean needGalLookup(String searchAttributeName, Set<String> returningAttributes) {
+        // return all attributes => call gallookup
+        if (returningAttributes == null || returningAttributes.isEmpty()) {
+            return true;
+            // iCal search, do not call gallookup
+        } else if (returningAttributes.contains("apple-serviceslocator")) {
+            return false;
+            // Lightning search, no need to gallookup
+        } else if ("sn".equals(searchAttributeName)) {
+            return returningAttributes.contains("sn");
+            // search attribute is gallookup attribute, need to fetch value for isMatch
+        } else if (GALLOOKUP_ATTRIBUTES.contains(searchAttributeName)) {
+            return true;
+        }
+
+        for (String attributeName : GALLOOKUP_ATTRIBUTES) {
+            if (returningAttributes.contains(attributeName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean disableGalLookup;
+
+    /**
+     * Get extended address book information for person with gallookup.
+     * Does not work with Exchange 2007
+     *
+     * @param contact galfind contact
+     */
+    public void galLookup(Contact contact) {
+        if (!disableGalLookup) {
+            LOGGER.debug("galLookup(" + contact.get("smtpemail1") + ')');
+            GetMethod getMethod = null;
+            try {
+                getMethod = new GetMethod(URIUtil.encodePathQuery(getCmdBasePath() + "?Cmd=gallookup&ADDR=" + contact.get("smtpemail1")));
+                DavGatewayHttpClientFacade.executeGetMethod(httpClient, getMethod, true);
+                Map<String, Map<String, String>> results = XMLStreamUtil.getElementContentsAsMap(getMethod.getResponseBodyAsStream(), "person", "alias");
+                // add detailed information
+                if (!results.isEmpty()) {
+                    Map<String, String> personGalLookupDetails = results.get(contact.get("uid").toLowerCase());
+                    if (personGalLookupDetails != null) {
+                        buildGalfindContact(contact, personGalLookupDetails);
+                    }
+                }
+            } catch (IOException e) {
+                LOGGER.warn("Unable to gallookup person: " + contact + ", disable GalLookup");
+                disableGalLookup = true;
+            } finally {
+                if (getMethod != null) {
+                    getMethod.releaseConnection();
+                }
+            }
+        }
+    }
+
+    protected void buildGalfindContact(Contact contact, Map<String, String> response) {
+        for (Map.Entry<String, String> entry : GALFIND_ATTRIBUTE_MAP.entrySet()) {
+            String attributeValue = response.get(entry.getValue());
+            if (attributeValue != null) {
+                contact.put(entry.getKey(), attributeValue);
+            }
+        }
+    }
+
+    @Override
+    protected String getFreeBusyData(String attendee, String start, String end, int interval) throws IOException {
+        String freebusyUrl = publicFolderUrl + "/?cmd=freebusy" +
+                "&start=" + start +
+                "&end=" + end +
+                "&interval=" + interval +
+                "&u=SMTP:" + attendee;
+        GetMethod getMethod = new GetMethod(freebusyUrl);
+        getMethod.setRequestHeader("Content-Type", "text/xml");
+        String fbdata = null;
+        try {
+            DavGatewayHttpClientFacade.executeGetMethod(httpClient, getMethod, true);
+            fbdata = StringUtil.getLastToken(getMethod.getResponseBodyAsString(), "<a:fbdata>", "</a:fbdata>");
+        } finally {
+            getMethod.releaseConnection();
+        }
+        return fbdata;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public DavExchangeSession(String url, String userName, String password) throws IOException {
+        super(url, userName, password);
+    }
+
+    @Override
+    protected void buildSessionInfo(HttpMethod method) throws DavMailException {
+        buildMailPath(method);
+
+        // get base http mailbox http urls
+        getWellKnownFolders();
+    }
+
+    static final String BASE_HREF = "<base href=\"";
+
+    /**
+     * Exchange 2003: get mailPath from welcome page
+     *
+     * @param method current http method
+     * @return mail path from body
+     */
+    protected String getMailpathFromWelcomePage(HttpMethod method) {
+        String welcomePageMailPath = null;
+        // get user mail URL from html body (multi frame)
+        BufferedReader mainPageReader = null;
+        try {
+            mainPageReader = new BufferedReader(new InputStreamReader(method.getResponseBodyAsStream()));
+            //noinspection StatementWithEmptyBody
+            String line;
+            while ((line = mainPageReader.readLine()) != null && line.toLowerCase().indexOf(BASE_HREF) == -1) {
+            }
+            if (line != null) {
+                // Exchange 2003
+                int start = line.toLowerCase().indexOf(BASE_HREF) + BASE_HREF.length();
+                int end = line.indexOf('\"', start);
+                String mailBoxBaseHref = line.substring(start, end);
+                URL baseURL = new URL(mailBoxBaseHref);
+                welcomePageMailPath = URIUtil.decode(baseURL.getPath());
+                LOGGER.debug("Base href found in body, mailPath is " + welcomePageMailPath);
+            }
+        } catch (IOException e) {
+            LOGGER.error("Error parsing main page at " + method.getPath(), e);
+        } finally {
+            if (mainPageReader != null) {
+                try {
+                    mainPageReader.close();
+                } catch (IOException e) {
+                    LOGGER.error("Error parsing main page at " + method.getPath());
+                }
+            }
+            method.releaseConnection();
+        }
+        return welcomePageMailPath;
+    }
+
+    protected void buildMailPath(HttpMethod method) throws DavMailAuthenticationException {
+        // get mailPath from welcome page on Exchange 2003
+        mailPath = getMailpathFromWelcomePage(method);
+
+        //noinspection VariableNotUsedInsideIf
+        if (mailPath != null) {
+            // Exchange 2003
+            serverVersion = "Exchange2003";
+            fixClientHost(method);
+            checkPublicFolder();
+            try {
+                buildEmail(method.getURI().getHost());
+            } catch (URIException uriException) {
+                LOGGER.warn(uriException);
+            }
+        } else {
+            // Exchange 2007 : get alias and email from options page
+            serverVersion = "Exchange2007";
+
+            // Gallookup is an Exchange 2003 only feature
+            disableGalLookup = true;
+            fixClientHost(method);
+            getEmailAndAliasFromOptions();
+
+            checkPublicFolder();
+
+            // failover: try to get email through Webdav and Galfind
+            if (alias == null || email == null) {
+                try {
+                    buildEmail(method.getURI().getHost());
+                } catch (URIException uriException) {
+                    LOGGER.warn(uriException);
+                }
+            }
+
+            // build standard mailbox link with email
+            mailPath = "/exchange/" + email + '/';
+        }
+
+        if (mailPath == null || email == null) {
+            throw new DavMailAuthenticationException("EXCEPTION_AUTHENTICATION_FAILED_PASSWORD_EXPIRED");
+        }
+        LOGGER.debug("Current user email is " + email + ", alias is " + alias + ", mailPath is " + mailPath + " on " + serverVersion);
+        rootPath = mailPath.substring(0, mailPath.lastIndexOf('/', mailPath.length() - 2) + 1);
+    }
+
+    /**
+     * Determine user email through various means.
+     *
+     * @param hostName Exchange server host name for last failover
+     */
+    public void buildEmail(String hostName) {
+        String mailBoxPath = getMailboxPath();
+        // mailPath contains either alias or email
+        if (mailBoxPath != null && mailBoxPath.indexOf('@') >= 0) {
+            email = mailBoxPath;
+            alias = getAliasFromMailboxDisplayName();
+            if (alias == null) {
+                alias = getAliasFromLogin();
+            }
+        } else {
+            // use mailbox name as alias
+            alias = mailBoxPath;
+            email = getEmail(alias);
+            if (email == null) {
+                // failover: try to get email from login name
+                alias = getAliasFromLogin();
+                email = getEmail(alias);
+            }
+            // another failover : get alias from mailPath display name
+            if (email == null) {
+                alias = getAliasFromMailboxDisplayName();
+                email = getEmail(alias);
+            }
+            if (email == null) {
+                LOGGER.debug("Unable to get user email with alias " + mailBoxPath
+                        + " or " + getAliasFromLogin()
+                        + " or " + alias
+                );
+                // last failover: build email from domain name and mailbox display name
+                StringBuilder buffer = new StringBuilder();
+                // most reliable alias
+                if (mailBoxPath != null) {
+                    alias = mailBoxPath;
+                } else {
+                    alias = getAliasFromLogin();
+                }
+                buffer.append(alias);
+                if (alias.indexOf('@') < 0) {
+                    buffer.append('@');
+                    int dotIndex = hostName.indexOf('.');
+                    if (dotIndex >= 0) {
+                        buffer.append(hostName.substring(dotIndex + 1));
+                    }
+                }
+                email = buffer.toString();
+            }
+        }
+    }
+
+    /**
+     * Get user alias from mailbox display name over Webdav.
+     *
+     * @return user alias
+     */
+    public String getAliasFromMailboxDisplayName() {
+        if (mailPath == null) {
+            return null;
+        }
+        String displayName = null;
+        try {
+            Folder rootFolder = getFolder("");
+            if (rootFolder == null) {
+                LOGGER.warn(new BundleMessage("EXCEPTION_UNABLE_TO_GET_MAIL_FOLDER", mailPath));
+            } else {
+                displayName = rootFolder.displayName;
+            }
+        } catch (IOException e) {
+            LOGGER.warn(new BundleMessage("EXCEPTION_UNABLE_TO_GET_MAIL_FOLDER", mailPath));
+        }
+        return displayName;
+    }
+
+    /**
+     * Get current Exchange alias name from mailbox name
+     *
+     * @return user name
+     */
+    protected String getMailboxPath() {
+        if (mailPath == null) {
+            return null;
+        }
+        int index = mailPath.lastIndexOf('/', mailPath.length() - 2);
+        if (index >= 0 && mailPath.endsWith("/")) {
+            return mailPath.substring(index + 1, mailPath.length() - 1);
+        } else {
+            LOGGER.warn(new BundleMessage("EXCEPTION_INVALID_MAIL_PATH", mailPath));
+            return null;
+        }
+    }
+
+    /**
+     * Get user email from global address list (galfind).
+     *
+     * @param alias user alias
+     * @return user email
+     */
+    public String getEmail(String alias) {
+        String emailResult = null;
+        if (alias != null && !disableGalFind) {
+            try {
+                Map<String, Map<String, String>> results = galFind("&AN=" + URIUtil.encodeWithinQuery(alias));
+                Map<String, String> result = results.get(alias.toLowerCase());
+                if (result != null) {
+                    emailResult = result.get("EM");
+                }
+            } catch (IOException e) {
+                // galfind not available
+                disableGalFind = true;
+                LOGGER.debug("getEmail(" + alias + ") failed");
+            }
+        }
+        return emailResult;
+    }
+
+    protected String getURIPropertyIfExists(DavPropertySet properties, String alias) throws URIException {
+        DavProperty property = properties.get(Field.getPropertyName(alias));
+        if (property == null) {
+            return null;
+        } else {
+            return URIUtil.decode((String) property.getValue());
+        }
+    }
+
+    // return last folder name from url
+
+    protected String getFolderName(String url) {
+        if (url != null) {
+            if (url.endsWith("/")) {
+                return url.substring(url.lastIndexOf('/', url.length() - 2) + 1, url.length() - 1);
+            } else if (url.indexOf('/') > 0) {
+                return url.substring(url.lastIndexOf('/') + 1);
+            } else {
+                return null;
+            }
+        } else {
+            return null;
+        }
+    }
+
+    protected void fixClientHost(HttpMethod method) {
+        try {
+            // update client host, workaround for Exchange 2003 mailbox with an Exchange 2007 frontend
+            URI currentUri = method.getURI();
+            if (currentUri != null && currentUri.getHost() != null && currentUri.getScheme() != null) {
+                httpClient.getHostConfiguration().setHost(currentUri.getHost(), currentUri.getPort(), currentUri.getScheme());
+            }
+        } catch (URIException e) {
+            LOGGER.warn("Unable to update http client host:" + e.getMessage(), e);
+        }
+    }
+
+    protected void checkPublicFolder() {
+
+        Cookie[] currentCookies = httpClient.getState().getCookies();
+        // check public folder access
+        try {
+            publicFolderUrl = httpClient.getHostConfiguration().getHostURL() + PUBLIC_ROOT;
+            DavPropertyNameSet davPropertyNameSet = new DavPropertyNameSet();
+            davPropertyNameSet.add(Field.getPropertyName("displayname"));
+            PropFindMethod propFindMethod = new PropFindMethod(publicFolderUrl, davPropertyNameSet, 0);
+            try {
+                DavGatewayHttpClientFacade.executeMethod(httpClient, propFindMethod);
+            } catch (IOException e) {
+                // workaround for NTLM authentication only on /public
+                if (!DavGatewayHttpClientFacade.hasNTLM(httpClient)) {
+                    DavGatewayHttpClientFacade.addNTLM(httpClient);
+                    DavGatewayHttpClientFacade.executeMethod(httpClient, propFindMethod);
+                }
+            }
+            // update public folder URI
+            publicFolderUrl = propFindMethod.getURI().getURI();
+        } catch (IOException e) {
+            // restore cookies on error
+            httpClient.getState().addCookies(currentCookies);
+            LOGGER.warn("Public folders not available: " + (e.getMessage() == null ? e : e.getMessage()));
+            // default public folder path
+            publicFolderUrl = PUBLIC_ROOT;
+        }
+    }
+
+    protected void getWellKnownFolders() throws DavMailException {
+        // Retrieve well known URLs
+        MultiStatusResponse[] responses;
+        try {
+            responses = DavGatewayHttpClientFacade.executePropFindMethod(
+                    httpClient, URIUtil.encodePath(mailPath), 0, WELL_KNOWN_FOLDERS);
+            if (responses.length == 0) {
+                throw new WebdavNotAvailableException("EXCEPTION_UNABLE_TO_GET_MAIL_FOLDER", mailPath);
+            }
+            DavPropertySet properties = responses[0].getProperties(HttpStatus.SC_OK);
+            inboxUrl = getURIPropertyIfExists(properties, "inbox");
+            inboxName = getFolderName(inboxUrl);
+            deleteditemsUrl = getURIPropertyIfExists(properties, "deleteditems");
+            deleteditemsName = getFolderName(deleteditemsUrl);
+            sentitemsUrl = getURIPropertyIfExists(properties, "sentitems");
+            sentitemsName = getFolderName(sentitemsUrl);
+            sendmsgUrl = getURIPropertyIfExists(properties, "sendmsg");
+            sendmsgName = getFolderName(sendmsgUrl);
+            draftsUrl = getURIPropertyIfExists(properties, "drafts");
+            draftsName = getFolderName(draftsUrl);
+            calendarUrl = getURIPropertyIfExists(properties, "calendar");
+            calendarName = getFolderName(calendarUrl);
+            tasksUrl = getURIPropertyIfExists(properties, "tasks");
+            tasksName = getFolderName(tasksUrl);
+            contactsUrl = getURIPropertyIfExists(properties, "contacts");
+            contactsName = getFolderName(contactsUrl);
+            outboxUrl = getURIPropertyIfExists(properties, "outbox");
+            outboxName = getFolderName(outboxUrl);
+            // junk folder not available over webdav
+
+            LOGGER.debug("Inbox URL: " + inboxUrl +
+                    " Trash URL: " + deleteditemsUrl +
+                    " Sent URL: " + sentitemsUrl +
+                    " Send URL: " + sendmsgUrl +
+                    " Drafts URL: " + draftsUrl +
+                    " Calendar URL: " + calendarUrl +
+                    " Tasks URL: " + tasksUrl +
+                    " Contacts URL: " + contactsUrl +
+                    " Outbox URL: " + outboxUrl +
+                    " Public folder URL: " + publicFolderUrl
+            );
+        } catch (IOException e) {
+            LOGGER.error(e.getMessage());
+            throw new WebdavNotAvailableException("EXCEPTION_UNABLE_TO_GET_MAIL_FOLDER", mailPath);
+        }
+    }
+
+    protected static class MultiCondition extends ExchangeSession.MultiCondition {
+        protected MultiCondition(Operator operator, Condition... condition) {
+            super(operator, condition);
+        }
+
+        public void appendTo(StringBuilder buffer) {
+            boolean first = true;
+
+            for (Condition condition : conditions) {
+                if (condition != null && !condition.isEmpty()) {
+                    if (first) {
+                        buffer.append('(');
+                        first = false;
+                    } else {
+                        buffer.append(' ').append(operator).append(' ');
+                    }
+                    condition.appendTo(buffer);
+                }
+            }
+            // at least one non empty condition
+            if (!first) {
+                buffer.append(')');
+            }
+        }
+    }
+
+    protected static class NotCondition extends ExchangeSession.NotCondition {
+        protected NotCondition(Condition condition) {
+            super(condition);
+        }
+
+        public void appendTo(StringBuilder buffer) {
+            buffer.append("(Not ");
+            condition.appendTo(buffer);
+            buffer.append(')');
+        }
+    }
+
+    static final Map<Operator, String> OPERATOR_MAP = new HashMap<Operator, String>();
+
+    static {
+        OPERATOR_MAP.put(Operator.IsEqualTo, " = ");
+        OPERATOR_MAP.put(Operator.IsGreaterThanOrEqualTo, " >= ");
+        OPERATOR_MAP.put(Operator.IsGreaterThan, " > ");
+        OPERATOR_MAP.put(Operator.IsLessThanOrEqualTo, " <= ");
+        OPERATOR_MAP.put(Operator.IsLessThan, " < ");
+        OPERATOR_MAP.put(Operator.Like, " like ");
+        OPERATOR_MAP.put(Operator.IsNull, " is null");
+        OPERATOR_MAP.put(Operator.IsFalse, " = false");
+        OPERATOR_MAP.put(Operator.IsTrue, " = true");
+        OPERATOR_MAP.put(Operator.StartsWith, " = ");
+        OPERATOR_MAP.put(Operator.Contains, " = ");
+    }
+
+    protected static class AttributeCondition extends ExchangeSession.AttributeCondition {
+        protected boolean isIntValue;
+
+        protected AttributeCondition(String attributeName, Operator operator, String value) {
+            super(attributeName, operator, value);
+        }
+
+        protected AttributeCondition(String attributeName, Operator operator, int value) {
+            super(attributeName, operator, String.valueOf(value));
+            isIntValue = true;
+        }
+
+        public void appendTo(StringBuilder buffer) {
+            Field field = Field.get(attributeName);
+            buffer.append('"').append(field.getUri()).append('"');
+            buffer.append(OPERATOR_MAP.get(operator));
+            //noinspection VariableNotUsedInsideIf
+            if (field.cast != null) {
+                buffer.append("CAST (\"");
+            } else if (!isIntValue && !field.isIntValue()) {
+                buffer.append('\'');
+            }
+            if (Operator.Like == operator) {
+                buffer.append('%');
+            }
+            if ("urlcompname".equals(field.alias)) {
+                buffer.append(StringUtil.encodeUrlcompname(StringUtil.davSearchEncode(value)));
+            } else if (field.isIntValue()) {
+                // check value
+                try {
+                    Integer.parseInt(value);
+                    buffer.append(value);
+                } catch (NumberFormatException e) {
+                    // invalid value, replace with 0
+                    buffer.append('0');
+                }
+            } else {
+                buffer.append(StringUtil.davSearchEncode(value));
+            }
+            if (Operator.Like == operator || Operator.StartsWith == operator) {
+                buffer.append('%');
+            }
+            if (field.cast != null) {
+                buffer.append("\" as '").append(field.cast).append("')");
+            } else if (!isIntValue && !field.isIntValue()) {
+                buffer.append('\'');
+            }
+        }
+
+        public boolean isMatch(ExchangeSession.Contact contact) {
+            String lowerCaseValue = value.toLowerCase();
+            String actualValue = contact.get(attributeName);
+            Operator actualOperator = operator;
+            // patch for iCal or Lightning search without galLookup
+            if (actualValue == null && ("givenName".equals(attributeName) || "sn".equals(attributeName))) {
+                actualValue = contact.get("cn");
+                actualOperator = Operator.Like;
+            }
+            if (actualValue == null) {
+                return false;
+            }
+            actualValue = actualValue.toLowerCase();
+            return (actualOperator == Operator.IsEqualTo && actualValue.equals(lowerCaseValue)) ||
+                    (actualOperator == Operator.Like && actualValue.contains(lowerCaseValue)) ||
+                    (actualOperator == Operator.StartsWith && actualValue.startsWith(lowerCaseValue));
+        }
+    }
+
+    protected static class HeaderCondition extends AttributeCondition {
+
+        protected HeaderCondition(String attributeName, Operator operator, String value) {
+            super(attributeName, operator, value);
+        }
+
+        @Override
+        public void appendTo(StringBuilder buffer) {
+            buffer.append('"').append(Field.getHeader(attributeName).getUri()).append('"');
+            buffer.append(OPERATOR_MAP.get(operator));
+            buffer.append('\'');
+            if (Operator.Like == operator) {
+                buffer.append('%');
+            }
+            buffer.append(value);
+            if (Operator.Like == operator) {
+                buffer.append('%');
+            }
+            buffer.append('\'');
+        }
+    }
+
+    protected static class MonoCondition extends ExchangeSession.MonoCondition {
+        protected MonoCondition(String attributeName, Operator operator) {
+            super(attributeName, operator);
+        }
+
+        public void appendTo(StringBuilder buffer) {
+            buffer.append('"').append(Field.get(attributeName).getUri()).append('"');
+            buffer.append(OPERATOR_MAP.get(operator));
+        }
+    }
+
+    @Override
+    public ExchangeSession.MultiCondition and(Condition... condition) {
+        return new MultiCondition(Operator.And, condition);
+    }
+
+    @Override
+    public ExchangeSession.MultiCondition or(Condition... condition) {
+        return new MultiCondition(Operator.Or, condition);
+    }
+
+    @Override
+    public Condition not(Condition condition) {
+        if (condition == null) {
+            return null;
+        } else {
+            return new NotCondition(condition);
+        }
+    }
+
+    @Override
+    public Condition isEqualTo(String attributeName, String value) {
+        return new AttributeCondition(attributeName, Operator.IsEqualTo, value);
+    }
+
+    @Override
+    public Condition isEqualTo(String attributeName, int value) {
+        return new AttributeCondition(attributeName, Operator.IsEqualTo, value);
+    }
+
+    @Override
+    public Condition headerIsEqualTo(String headerName, String value) {
+        return new HeaderCondition(headerName, Operator.IsEqualTo, value);
+    }
+
+    @Override
+    public Condition gte(String attributeName, String value) {
+        return new AttributeCondition(attributeName, Operator.IsGreaterThanOrEqualTo, value);
+    }
+
+    @Override
+    public Condition lte(String attributeName, String value) {
+        return new AttributeCondition(attributeName, Operator.IsLessThanOrEqualTo, value);
+    }
+
+    @Override
+    public Condition lt(String attributeName, String value) {
+        return new AttributeCondition(attributeName, Operator.IsLessThan, value);
+    }
+
+    @Override
+    public Condition gt(String attributeName, String value) {
+        return new AttributeCondition(attributeName, Operator.IsGreaterThan, value);
+    }
+
+    @Override
+    public Condition contains(String attributeName, String value) {
+        return new AttributeCondition(attributeName, Operator.Like, value);
+    }
+
+    @Override
+    public Condition startsWith(String attributeName, String value) {
+        return new AttributeCondition(attributeName, Operator.StartsWith, value);
+    }
+
+    @Override
+    public Condition isNull(String attributeName) {
+        return new MonoCondition(attributeName, Operator.IsNull);
+    }
+
+    @Override
+    public Condition isTrue(String attributeName) {
+        if ("Exchange2003".equals(this.serverVersion) && "deleted".equals(attributeName)) {
+            return isEqualTo(attributeName, "1");
+        } else {
+            return new MonoCondition(attributeName, Operator.IsTrue);
+        }
+    }
+
+    @Override
+    public Condition isFalse(String attributeName) {
+        if ("Exchange2003".equals(this.serverVersion) && "deleted".equals(attributeName)) {
+            return or(isEqualTo(attributeName, "0"), isNull(attributeName));
+        } else {
+            return new MonoCondition(attributeName, Operator.IsFalse);
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public class Message extends ExchangeSession.Message {
+
+        @Override
+        public String getPermanentId() {
+            return permanentUrl;
+        }
+
+        @Override
+        protected InputStream getMimeHeaders() {
+            InputStream result = null;
+            try {
+                String messageHeaders = getItemProperty(permanentUrl, "messageheaders");
+                if (messageHeaders != null) {
+                    // workaround for messages in Sent folder
+                    if (messageHeaders.indexOf("From:") < 0) {
+                        String from = getItemProperty(permanentUrl, "from");
+                        messageHeaders = "From: "+from+"\n"+messageHeaders;
+                    }
+                    result = new ByteArrayInputStream(messageHeaders.getBytes("UTF-8"));
+                }
+            } catch (Exception e) {
+                LOGGER.warn(e.getMessage());
+            }
+
+            return result;
+        }
+    }
+
+
+    /**
+     * @inheritDoc
+     */
+    public class Contact extends ExchangeSession.Contact {
+        /**
+         * Build Contact instance from multistatusResponse info
+         *
+         * @param multiStatusResponse response
+         * @throws URIException     on error
+         * @throws DavMailException on error
+         */
+        public Contact(MultiStatusResponse multiStatusResponse) throws URIException, DavMailException {
+            setHref(URIUtil.decode(multiStatusResponse.getHref()));
+            DavPropertySet properties = multiStatusResponse.getProperties(HttpStatus.SC_OK);
+            permanentUrl = getURLPropertyIfExists(properties, "permanenturl");
+            etag = getPropertyIfExists(properties, "etag");
+            displayName = getPropertyIfExists(properties, "displayname");
+            for (String attributeName : CONTACT_ATTRIBUTES) {
+                String value = getPropertyIfExists(properties, attributeName);
+                if (value != null) {
+                    if ("bday".equals(attributeName) || "anniversary".equals(attributeName)
+                            || "lastmodified".equals(attributeName) || "datereceived".equals(attributeName)) {
+                        value = convertDateFromExchange(value);
+                    } else if ("haspicture".equals(attributeName) || "private".equals(attributeName)) {
+                        value = "1".equals(value) ? "true" : "false";
+                    }
+                    put(attributeName, value);
+                }
+            }
+        }
+
+        /**
+         * @inheritDoc
+         */
+        public Contact(String folderPath, String itemName, Map<String, String> properties, String etag, String noneMatch) {
+            super(folderPath, itemName, properties, etag, noneMatch);
+        }
+
+        /**
+         * Default constructor for galFind
+         */
+        public Contact() {
+        }
+
+        protected Set<PropertyValue> buildProperties() {
+            Set<PropertyValue> propertyValues = new HashSet<PropertyValue>();
+            for (Map.Entry<String, String> entry : entrySet()) {
+                String key = entry.getKey();
+                if (!"photo".equals(key)) {
+                    propertyValues.add(Field.createPropertyValue(key, entry.getValue()));
+                    if (key.startsWith("email")) {
+                        propertyValues.add(Field.createPropertyValue(key + "type", "SMTP"));
+                    }
+                }
+            }
+
+            return propertyValues;
+        }
+
+        protected ExchangePropPatchMethod internalCreateOrUpdate(String encodedHref) throws IOException {
+            ExchangePropPatchMethod propPatchMethod = new ExchangePropPatchMethod(encodedHref, buildProperties());
+            propPatchMethod.setRequestHeader("Translate", "f");
+            if (etag != null) {
+                propPatchMethod.setRequestHeader("If-Match", etag);
+            }
+            if (noneMatch != null) {
+                propPatchMethod.setRequestHeader("If-None-Match", noneMatch);
+            }
+            try {
+                httpClient.executeMethod(propPatchMethod);
+            } finally {
+                propPatchMethod.releaseConnection();
+            }
+            return propPatchMethod;
+        }
+
+        /**
+         * Create or update contact
+         *
+         * @return action result
+         * @throws IOException on error
+         */
+        public ItemResult createOrUpdate() throws IOException {
+            String encodedHref = URIUtil.encodePath(getHref());
+            ExchangePropPatchMethod propPatchMethod = internalCreateOrUpdate(encodedHref);
+            int status = propPatchMethod.getStatusCode();
+            if (status == HttpStatus.SC_MULTI_STATUS) {
+                status = propPatchMethod.getResponseStatusCode();
+                //noinspection VariableNotUsedInsideIf
+                if (status == HttpStatus.SC_CREATED) {
+                    LOGGER.debug("Created contact " + encodedHref);
+                } else {
+                    LOGGER.debug("Updated contact " + encodedHref);
+                }
+            } else if (status == HttpStatus.SC_NOT_FOUND) {
+                LOGGER.debug("Contact not found at " + encodedHref + ", searching permanenturl by urlcompname");
+                // failover, search item by urlcompname
+                MultiStatusResponse[] responses = searchItems(folderPath, EVENT_REQUEST_PROPERTIES, DavExchangeSession.this.isEqualTo("urlcompname", convertItemNameToEML(itemName)), FolderQueryTraversal.Shallow, 1);
+                if (responses.length == 1) {
+                    encodedHref = getPropertyIfExists(responses[0].getProperties(HttpStatus.SC_OK), "permanenturl");
+                    LOGGER.warn("Contact found, permanenturl is " + encodedHref);
+                    propPatchMethod = internalCreateOrUpdate(encodedHref);
+                    status = propPatchMethod.getStatusCode();
+                    if (status == HttpStatus.SC_MULTI_STATUS) {
+                        status = propPatchMethod.getResponseStatusCode();
+                        LOGGER.debug("Updated contact " + encodedHref);
+                    } else {
+                        LOGGER.warn("Unable to create or update contact " + status + ' ' + propPatchMethod.getStatusLine());
+                    }
+                }
+
+            } else {
+                LOGGER.warn("Unable to create or update contact " + status + ' ' + propPatchMethod.getStatusLine());
+            }
+            ItemResult itemResult = new ItemResult();
+            // 440 means forbidden on Exchange
+            if (status == 440) {
+                status = HttpStatus.SC_FORBIDDEN;
+            }
+            itemResult.status = status;
+
+            if (status == HttpStatus.SC_OK || status == HttpStatus.SC_CREATED) {
+                String contactPictureUrl = URIUtil.encodePath(getHref() + "/ContactPicture.jpg");
+                String photo = get("photo");
+                if (photo != null) {
+                    // need to update photo
+                    byte[] resizedImageBytes = IOUtil.resizeImage(Base64.decodeBase64(photo.getBytes()), 90);
+
+                    final PutMethod putmethod = new PutMethod(contactPictureUrl);
+                    putmethod.setRequestHeader("Overwrite", "t");
+                    putmethod.setRequestHeader("Content-Type", "image/jpeg");
+                    putmethod.setRequestEntity(new ByteArrayRequestEntity(resizedImageBytes, "image/jpeg"));
+                    try {
+                        status = httpClient.executeMethod(putmethod);
+                        if (status != HttpStatus.SC_OK && status != HttpStatus.SC_CREATED) {
+                            throw new IOException("Unable to update contact picture: " + status + ' ' + putmethod.getStatusLine());
+                        }
+                    } catch (IOException e) {
+                        LOGGER.error("Error in contact photo create or update", e);
+                        throw e;
+                    } finally {
+                        putmethod.releaseConnection();
+                    }
+
+                    Set<PropertyValue> picturePropertyValues = new HashSet<PropertyValue>();
+                    picturePropertyValues.add(Field.createPropertyValue("attachmentContactPhoto", "true"));
+                    // picturePropertyValues.add(Field.createPropertyValue("renderingPosition", "-1"));
+                    picturePropertyValues.add(Field.createPropertyValue("attachExtension", ".jpg"));
+
+                    final ExchangePropPatchMethod attachmentPropPatchMethod = new ExchangePropPatchMethod(contactPictureUrl, picturePropertyValues);
+                    try {
+                        status = httpClient.executeMethod(attachmentPropPatchMethod);
+                        if (status != HttpStatus.SC_MULTI_STATUS) {
+                            LOGGER.error("Error in contact photo create or update: " + attachmentPropPatchMethod.getStatusCode());
+                            throw new IOException("Unable to update contact picture");
+                        }
+                    } finally {
+                        attachmentPropPatchMethod.releaseConnection();
+                    }
+
+                } else {
+                    // try to delete picture
+                    DeleteMethod deleteMethod = new DeleteMethod(contactPictureUrl);
+                    try {
+                        status = httpClient.executeMethod(deleteMethod);
+                        if (status != HttpStatus.SC_OK && status != HttpStatus.SC_NOT_FOUND) {
+                            LOGGER.error("Error in contact photo delete: " + status);
+                            throw new IOException("Unable to delete contact picture");
+                        }
+                    } finally {
+                        deleteMethod.releaseConnection();
+                    }
+                }
+                // need to retrieve new etag
+                HeadMethod headMethod = new HeadMethod(URIUtil.encodePath(getHref()));
+                try {
+                    httpClient.executeMethod(headMethod);
+                    if (headMethod.getResponseHeader("ETag") != null) {
+                        itemResult.etag = headMethod.getResponseHeader("ETag").getValue();
+                    }
+                } finally {
+                    headMethod.releaseConnection();
+                }
+            }
+            return itemResult;
+
+        }
+
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public class Event extends ExchangeSession.Event {
+        protected String instancetype;
+
+        /**
+         * Build Event instance from response info.
+         *
+         * @param multiStatusResponse response
+         * @throws URIException on error
+         */
+        public Event(MultiStatusResponse multiStatusResponse) throws URIException {
+            setHref(URIUtil.decode(multiStatusResponse.getHref()));
+            DavPropertySet properties = multiStatusResponse.getProperties(HttpStatus.SC_OK);
+            permanentUrl = getURLPropertyIfExists(properties, "permanenturl");
+            etag = getPropertyIfExists(properties, "etag");
+            displayName = getPropertyIfExists(properties, "displayname");
+            subject = getPropertyIfExists(properties, "subject");
+            instancetype = getPropertyIfExists(properties, "instancetype");
+            contentClass = getPropertyIfExists(properties, "contentclass");
+        }
+
+        protected String getPermanentUrl() {
+            return permanentUrl;
+        }
+
+
+        /**
+         * @inheritDoc
+         */
+        public Event(String folderPath, String itemName, String contentClass, String itemBody, String etag, String noneMatch) throws IOException {
+            super(folderPath, itemName, contentClass, itemBody, etag, noneMatch);
+        }
+
+        protected byte[] getICSFromInternetContentProperty() throws IOException, DavException, MessagingException {
+            byte[] result = null;
+            // PropFind PR_INTERNET_CONTENT
+            String propertyValue = getItemProperty(permanentUrl, "internetContent");
+            if (propertyValue != null) {
+                byte[] byteArray = Base64.decodeBase64(propertyValue.getBytes());
+                result = getICS(new ByteArrayInputStream(byteArray));
+            }
+            return result;
+        }
+
+        /**
+         * Load ICS content from Exchange server.
+         * User Translate: f header to get MIME event content and get ICS attachment from it
+         *
+         * @return ICS (iCalendar) event
+         * @throws HttpException on error
+         */
+        @Override
+        public byte[] getEventContent() throws IOException {
+            byte[] result = null;
+            LOGGER.debug("Get event subject: " + subject + " href: " + getHref() + " permanentUrl: " + permanentUrl);
+            // try to get PR_INTERNET_CONTENT
+            try {
+                result = getICSFromInternetContentProperty();
+                if (result == null) {
+                    GetMethod method = new GetMethod(encodeAndFixUrl(permanentUrl));
+                    method.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
+                    method.setRequestHeader("Translate", "f");
+                    try {
+                        DavGatewayHttpClientFacade.executeGetMethod(httpClient, method, true);
+                        result = getICS(method.getResponseBodyAsStream());
+                    } finally {
+                        method.releaseConnection();
+                    }
+                }
+            } catch (DavException e) {
+                LOGGER.warn(e.getMessage());
+            } catch (IOException e) {
+                LOGGER.warn(e.getMessage());
+            } catch (MessagingException e) {
+                LOGGER.warn(e.getMessage());
+            }
+
+            // failover: rebuild event from MAPI properties
+            if (result == null) {
+                try {
+                    result = getICSFromItemProperties();
+                } catch (HttpException e) {
+                    deleteBroken();
+                    throw e;
+                }
+            }
+            // debug code
+            /*if (new String(result).indexOf("VTODO") < 0) {
+                LOGGER.debug("Original body: " + new String(result));
+                result = getICSFromItemProperties();
+                LOGGER.debug("Rebuilt body: " + new String(result));
+            }*/
+
+            return result;
+        }
+
+        private byte[] getICSFromItemProperties() throws IOException {
+            byte[] result;
+
+            // experimental: build VCALENDAR from properties
+
+            try {
+                //MultiStatusResponse[] responses = DavGatewayHttpClientFacade.executeMethod(httpClient, propFindMethod);
+                Set<String> eventProperties = new HashSet<String>();
+                eventProperties.add("method");
+
+                eventProperties.add("created");
+                eventProperties.add("calendarlastmodified");
+                eventProperties.add("dtstamp");
+                eventProperties.add("calendaruid");
+                eventProperties.add("subject");
+                eventProperties.add("dtstart");
+                eventProperties.add("dtend");
+                eventProperties.add("transparent");
+                eventProperties.add("organizer");
+                eventProperties.add("to");
+                eventProperties.add("description");
+                eventProperties.add("rrule");
+                eventProperties.add("exdate");
+                eventProperties.add("sensitivity");
+                eventProperties.add("alldayevent");
+                eventProperties.add("busystatus");
+                eventProperties.add("reminderset");
+                eventProperties.add("reminderdelta");
+                // task
+                eventProperties.add("importance");
+                eventProperties.add("uid");
+                eventProperties.add("taskstatus");
+                eventProperties.add("percentcomplete");
+                eventProperties.add("keywords");
+                eventProperties.add("startdate");
+                eventProperties.add("duedate");
+                eventProperties.add("datecompleted");
+
+                MultiStatusResponse[] responses = searchItems(folderPath, eventProperties, DavExchangeSession.this.isEqualTo("urlcompname", convertItemNameToEML(itemName)), FolderQueryTraversal.Shallow, 1);
+                if (responses.length == 0) {
+                    throw new HttpNotFoundException(permanentUrl + " not found");
+                }
+                DavPropertySet davPropertySet = responses[0].getProperties(HttpStatus.SC_OK);
+                VCalendar localVCalendar = new VCalendar();
+                localVCalendar.setPropertyValue("PRODID", "-//davmail.sf.net/NONSGML DavMail Calendar V1.1//EN");
+                localVCalendar.setPropertyValue("VERSION", "2.0");
+                localVCalendar.setPropertyValue("METHOD", getPropertyIfExists(davPropertySet, "method"));
+                VObject vEvent = new VObject();
+                vEvent.setPropertyValue("CREATED", convertDateFromExchange(getPropertyIfExists(davPropertySet, "created")));
+                vEvent.setPropertyValue("LAST-MODIFIED", convertDateFromExchange(getPropertyIfExists(davPropertySet, "calendarlastmodified")));
+                vEvent.setPropertyValue("DTSTAMP", convertDateFromExchange(getPropertyIfExists(davPropertySet, "dtstamp")));
+
+                String uid = getPropertyIfExists(davPropertySet, "calendaruid");
+                if (uid == null) {
+                    uid = getPropertyIfExists(davPropertySet, "uid");
+                }
+                vEvent.setPropertyValue("UID", uid);
+                vEvent.setPropertyValue("SUMMARY", getPropertyIfExists(davPropertySet, "subject"));
+                vEvent.setPropertyValue("DESCRIPTION", getPropertyIfExists(davPropertySet, "description"));
+                vEvent.setPropertyValue("PRIORITY", convertPriorityFromExchange(getPropertyIfExists(davPropertySet, "importance")));
+                vEvent.setPropertyValue("CATEGORIES", getPropertyIfExists(davPropertySet, "keywords"));
+
+                if (instancetype == null) {
+                    vEvent.type = "VTODO";
+                    double percentComplete = getDoublePropertyIfExists(davPropertySet, "percentcomplete");
+                    if (percentComplete > 0) {
+                        vEvent.setPropertyValue("PERCENT-COMPLETE", String.valueOf((int) (percentComplete * 100)));
+                    }
+                    vEvent.setPropertyValue("STATUS", taskTovTodoStatusMap.get(getPropertyIfExists(davPropertySet, "taskstatus")));
+                    vEvent.setPropertyValue("DUE;VALUE=DATE", convertDateFromExchangeToTaskDate(getPropertyIfExists(davPropertySet, "duedate")));
+                    vEvent.setPropertyValue("DTSTART;VALUE=DATE", convertDateFromExchangeToTaskDate(getPropertyIfExists(davPropertySet, "startdate")));
+                    vEvent.setPropertyValue("COMPLETED;VALUE=DATE", convertDateFromExchangeToTaskDate(getPropertyIfExists(davPropertySet, "datecompleted")));
+
+                } else {
+                    vEvent.type = "VEVENT";
+                    // check mandatory dtstart value
+                    String dtstart = getPropertyIfExists(davPropertySet, "dtstart");
+                    if (dtstart != null) {
+                        vEvent.setPropertyValue("DTSTART", convertDateFromExchange(dtstart));
+                    } else {
+                        LOGGER.warn("missing dtstart on item, using fake value. Set davmail.deleteBroken=true to delete broken events");
+                        vEvent.setPropertyValue("DTSTART", "20000101T000000Z");
+                        deleteBroken();
+                    }
+                    // same on DTEND
+                    String dtend = getPropertyIfExists(davPropertySet, "dtend");
+                    if (dtend != null) {
+                        vEvent.setPropertyValue("DTEND", convertDateFromExchange(dtend));
+                    } else {
+                        LOGGER.warn("missing dtend on item, using fake value. Set davmail.deleteBroken=true to delete broken events");
+                        vEvent.setPropertyValue("DTEND", "20000101T010000Z");
+                        deleteBroken();
+                    }
+                    vEvent.setPropertyValue("TRANSP", getPropertyIfExists(davPropertySet, "transparent"));
+                    vEvent.setPropertyValue("RRULE", getPropertyIfExists(davPropertySet, "rrule"));
+                    String exdates = getPropertyIfExists(davPropertySet, "exdate");
+                    if (exdates != null) {
+                        String[] exdatearray = exdates.split(",");
+                        for (String exdate : exdatearray) {
+                            vEvent.addPropertyValue("EXDATE",
+                                    StringUtil.convertZuluDateTimeToAllDay(convertDateFromExchange(exdate)));
+                        }
+                    }
+                    String sensitivity = getPropertyIfExists(davPropertySet, "sensitivity");
+                    if ("2".equals(sensitivity)) {
+                        vEvent.setPropertyValue("CLASS", "PRIVATE");
+                    } else if ("3".equals(sensitivity)) {
+                        vEvent.setPropertyValue("CLASS", "CONFIDENTIAL");
+                    } else if ("0".equals(sensitivity)) {
+                        vEvent.setPropertyValue("CLASS", "PUBLIC");
+                    }
+                    String organizer = getPropertyIfExists(davPropertySet, "organizer");
+                    String organizerEmail = null;
+                    if (organizer != null) {
+                        InternetAddress organizerAddress = new InternetAddress(organizer);
+                        organizerEmail = organizerAddress.getAddress();
+                        vEvent.setPropertyValue("ORGANIZER", "MAILTO:" + organizerEmail);
+                    }
+
+                    // Parse attendee list
+                    String toHeader = getPropertyIfExists(davPropertySet, "to");
+                    if (toHeader != null && !toHeader.equals(organizerEmail)) {
+                        InternetAddress[] attendees = InternetAddress.parseHeader(toHeader, false);
+                        for (InternetAddress attendee : attendees) {
+                            if (!attendee.getAddress().equalsIgnoreCase(organizerEmail)) {
+                                VProperty vProperty = new VProperty("ATTENDEE", attendee.getAddress());
+                                if (attendee.getPersonal() != null) {
+                                    vProperty.addParam("CN", attendee.getPersonal());
+                                }
+                                vEvent.addProperty(vProperty);
+                            }
+                        }
+
+                    }
+                    vEvent.setPropertyValue("X-MICROSOFT-CDO-ALLDAYEVENT",
+                            "1".equals(getPropertyIfExists(davPropertySet, "alldayevent")) ? "TRUE" : "FALSE");
+                    vEvent.setPropertyValue("X-MICROSOFT-CDO-BUSYSTATUS", getPropertyIfExists(davPropertySet, "busystatus"));
+
+                    if ("1".equals(getPropertyIfExists(davPropertySet, "reminderset"))) {
+                        VObject vAlarm = new VObject();
+                        vAlarm.type = "VALARM";
+                        vAlarm.setPropertyValue("ACTION", "DISPLAY");
+                        vAlarm.setPropertyValue("DISPLAY", "Reminder");
+                        String reminderdelta = getPropertyIfExists(davPropertySet, "reminderdelta");
+                        VProperty vProperty = new VProperty("TRIGGER", "-PT" + reminderdelta + 'M');
+                        vProperty.addParam("VALUE", "DURATION");
+                        vAlarm.addProperty(vProperty);
+                        vEvent.addVObject(vAlarm);
+                    }
+                }
+
+                localVCalendar.addVObject(vEvent);
+                result = localVCalendar.toString().getBytes("UTF-8");
+            } catch (MessagingException e) {
+                LOGGER.warn("Unable to rebuild event content: " + e.getMessage(), e);
+                throw buildHttpException(e);
+            } catch (IOException e) {
+                LOGGER.warn("Unable to rebuild event content: " + e.getMessage(), e);
+                throw buildHttpException(e);
+            }
+
+            return result;
+        }
+
+        protected void deleteBroken() {
+            // try to delete broken event
+            if (Settings.getBooleanProperty("davmail.deleteBroken")) {
+                LOGGER.warn("Deleting broken event at: " + permanentUrl);
+                try {
+                    DavGatewayHttpClientFacade.executeDeleteMethod(httpClient, encodeAndFixUrl(permanentUrl));
+                } catch (IOException ioe) {
+                    LOGGER.warn("Unable to delete broken event at: " + permanentUrl);
+                }
+            }
+        }
+
+        protected PutMethod internalCreateOrUpdate(String encodedHref, byte[] mimeContent) throws IOException {
+            PutMethod putmethod = new PutMethod(encodedHref);
+            putmethod.setRequestHeader("Translate", "f");
+            putmethod.setRequestHeader("Overwrite", "f");
+            if (etag != null) {
+                putmethod.setRequestHeader("If-Match", etag);
+            }
+            if (noneMatch != null) {
+                putmethod.setRequestHeader("If-None-Match", noneMatch);
+            }
+            putmethod.setRequestHeader("Content-Type", "message/rfc822");
+            putmethod.setRequestEntity(new ByteArrayRequestEntity(mimeContent, "message/rfc822"));
+            try {
+                httpClient.executeMethod(putmethod);
+            } finally {
+                putmethod.releaseConnection();
+            }
+            return putmethod;
+        }
+
+        /**
+         * @inheritDoc
+         */
+        @Override
+        public ItemResult createOrUpdate() throws IOException {
+            ItemResult itemResult = new ItemResult();
+            if (vCalendar.isTodo()) {
+                if ((mailPath + calendarName).equals(folderPath)) {
+                    folderPath = mailPath + tasksName;
+                }
+                String encodedHref = URIUtil.encodePath(getHref());
+                Set<PropertyValue> propertyValues = new HashSet<PropertyValue>();
+                // set contentclass on create
+                if (noneMatch != null) {
+                    propertyValues.add(Field.createPropertyValue("contentclass", "urn:content-classes:task"));
+                    propertyValues.add(Field.createPropertyValue("outlookmessageclass", "IPM.Task"));
+                    propertyValues.add(Field.createPropertyValue("calendaruid", vCalendar.getFirstVeventPropertyValue("UID")));
+                }
+                propertyValues.add(Field.createPropertyValue("subject", vCalendar.getFirstVeventPropertyValue("SUMMARY")));
+                propertyValues.add(Field.createPropertyValue("description", vCalendar.getFirstVeventPropertyValue("DESCRIPTION")));
+                propertyValues.add(Field.createPropertyValue("importance", convertPriorityToExchange(vCalendar.getFirstVeventPropertyValue("PRIORITY"))));
+                String percentComplete = vCalendar.getFirstVeventPropertyValue("PERCENT-COMPLETE");
+                if (percentComplete == null) {
+                    percentComplete = "0";
+                }
+                propertyValues.add(Field.createPropertyValue("percentcomplete", String.valueOf(Double.parseDouble(percentComplete) / 100)));
+                String taskStatus = vTodoToTaskStatusMap.get(vCalendar.getFirstVeventPropertyValue("STATUS"));
+                propertyValues.add(Field.createPropertyValue("taskstatus", taskStatus));
+                propertyValues.add(Field.createPropertyValue("keywords", vCalendar.getFirstVeventPropertyValue("CATEGORIES")));
+                propertyValues.add(Field.createPropertyValue("startdate", convertTaskDateToZulu(vCalendar.getFirstVeventPropertyValue("DTSTART"))));
+                propertyValues.add(Field.createPropertyValue("duedate", convertTaskDateToZulu(vCalendar.getFirstVeventPropertyValue("DUE"))));
+                propertyValues.add(Field.createPropertyValue("datecompleted", convertTaskDateToZulu(vCalendar.getFirstVeventPropertyValue("COMPLETED"))));
+
+                propertyValues.add(Field.createPropertyValue("iscomplete", "2".equals(taskStatus) ? "true" : "false"));
+                propertyValues.add(Field.createPropertyValue("commonstart", convertTaskDateToZulu(vCalendar.getFirstVeventPropertyValue("DTSTART"))));
+                propertyValues.add(Field.createPropertyValue("commonend", convertTaskDateToZulu(vCalendar.getFirstVeventPropertyValue("DUE"))));
+
+                ExchangePropPatchMethod propPatchMethod = new ExchangePropPatchMethod(encodedHref, propertyValues);
+                propPatchMethod.setRequestHeader("Translate", "f");
+                if (etag != null) {
+                    propPatchMethod.setRequestHeader("If-Match", etag);
+                }
+                if (noneMatch != null) {
+                    propPatchMethod.setRequestHeader("If-None-Match", noneMatch);
+                }
+                try {
+                    int status = httpClient.executeMethod(propPatchMethod);
+
+                    if (status == HttpStatus.SC_MULTI_STATUS) {
+                        Item newItem = getItem(folderPath, itemName);
+                        itemResult.status = propPatchMethod.getResponseStatusCode();
+                        itemResult.etag = newItem.etag;
+                    } else {
+                        itemResult.status = status;
+                    }
+                } finally {
+                    propPatchMethod.releaseConnection();
+                }
+
+            } else {
+                String encodedHref = URIUtil.encodePath(getHref());
+                byte[] mimeContent = createMimeContent();
+                PutMethod putMethod = internalCreateOrUpdate(encodedHref, mimeContent);
+                int status = putMethod.getStatusCode();
+
+                if (status == HttpStatus.SC_OK) {
+                    LOGGER.debug("Updated event " + encodedHref);
+                } else if (status == HttpStatus.SC_CREATED) {
+                    LOGGER.debug("Created event " + encodedHref);
+                } else if (status == HttpStatus.SC_NOT_FOUND) {
+                    LOGGER.debug("Event not found at " + encodedHref + ", searching permanenturl by urlcompname");
+                    // failover, search item by urlcompname
+                    MultiStatusResponse[] responses = searchItems(folderPath, EVENT_REQUEST_PROPERTIES, DavExchangeSession.this.isEqualTo("urlcompname", convertItemNameToEML(itemName)), FolderQueryTraversal.Shallow, 1);
+                    if (responses.length == 1) {
+                        encodedHref = getPropertyIfExists(responses[0].getProperties(HttpStatus.SC_OK), "permanenturl");
+                        LOGGER.warn("Event found, permanenturl is " + encodedHref);
+                        putMethod = internalCreateOrUpdate(encodedHref, mimeContent);
+                        status = putMethod.getStatusCode();
+                        if (status == HttpStatus.SC_OK) {
+                            LOGGER.debug("Updated event " + encodedHref);
+                        } else {
+                            LOGGER.warn("Unable to create or update event " + status + ' ' + putMethod.getStatusLine());
+                        }
+                    }
+                } else {
+                    LOGGER.warn("Unable to create or update event " + status + ' ' + putMethod.getStatusLine());
+                }
+
+                // 440 means forbidden on Exchange
+                if (status == 440) {
+                    status = HttpStatus.SC_FORBIDDEN;
+                }
+                itemResult.status = status;
+                if (putMethod.getResponseHeader("GetETag") != null) {
+                    itemResult.etag = putMethod.getResponseHeader("GetETag").getValue();
+                }
+
+                // trigger activeSync push event, only if davmail.forceActiveSyncUpdate setting is true
+                if ((status == HttpStatus.SC_OK || status == HttpStatus.SC_CREATED) &&
+                        (Settings.getBooleanProperty("davmail.forceActiveSyncUpdate"))) {
+                    ArrayList<DavConstants> propertyList = new ArrayList<DavConstants>();
+                    // Set contentclass to make ActiveSync happy
+                    propertyList.add(Field.createDavProperty("contentclass", contentClass));
+                    // ... but also set PR_INTERNET_CONTENT to preserve custom properties
+                    propertyList.add(Field.createDavProperty("internetContent", new String(Base64.encodeBase64(mimeContent))));
+                    PropPatchMethod propPatchMethod = new PropPatchMethod(encodedHref, propertyList);
+                    int patchStatus = DavGatewayHttpClientFacade.executeHttpMethod(httpClient, propPatchMethod);
+                    if (patchStatus != HttpStatus.SC_MULTI_STATUS) {
+                        LOGGER.warn("Unable to patch event to trigger activeSync push");
+                    } else {
+                        // need to retrieve new etag
+                        Item newItem = getItem(folderPath, itemName);
+                        itemResult.etag = newItem.etag;
+                    }
+                }
+            }
+            return itemResult;
+        }
+
+
+    }
+
+    protected Folder buildFolder(MultiStatusResponse entity) throws IOException {
+        String href = URIUtil.decode(entity.getHref());
+        Folder folder = new Folder();
+        DavPropertySet properties = entity.getProperties(HttpStatus.SC_OK);
+        folder.displayName = getPropertyIfExists(properties, "displayname");
+        folder.folderClass = getPropertyIfExists(properties, "folderclass");
+        folder.hasChildren = "1".equals(getPropertyIfExists(properties, "hassubs"));
+        folder.noInferiors = "1".equals(getPropertyIfExists(properties, "nosubs"));
+        folder.unreadCount = getIntPropertyIfExists(properties, "unreadcount");
+        folder.ctag = getPropertyIfExists(properties, "contenttag");
+        folder.etag = getPropertyIfExists(properties, "lastmodified");
+
+        folder.uidNext = getIntPropertyIfExists(properties, "uidNext");
+
+        // replace well known folder names
+        if (inboxUrl != null && href.startsWith(inboxUrl)) {
+            folder.folderPath = href.replaceFirst(inboxUrl, INBOX);
+        } else if (sentitemsUrl != null && href.startsWith(sentitemsUrl)) {
+            folder.folderPath = href.replaceFirst(sentitemsUrl, SENT);
+        } else if (draftsUrl != null && href.startsWith(draftsUrl)) {
+            folder.folderPath = href.replaceFirst(draftsUrl, DRAFTS);
+        } else if (deleteditemsUrl != null && href.startsWith(deleteditemsUrl)) {
+            folder.folderPath = href.replaceFirst(deleteditemsUrl, TRASH);
+        } else if (calendarUrl != null && href.startsWith(calendarUrl)) {
+            folder.folderPath = href.replaceFirst(calendarUrl, CALENDAR);
+        } else if (contactsUrl != null && href.startsWith(contactsUrl)) {
+            folder.folderPath = href.replaceFirst(contactsUrl, CONTACTS);
+        } else {
+            int index = href.indexOf(mailPath.substring(0, mailPath.length() - 1));
+            if (index >= 0) {
+                if (index + mailPath.length() > href.length()) {
+                    folder.folderPath = "";
+                } else {
+                    folder.folderPath = href.substring(index + mailPath.length());
+                }
+            } else {
+                try {
+                    URI folderURI = new URI(href, false);
+                    folder.folderPath = folderURI.getPath();
+                } catch (URIException e) {
+                    throw new DavMailException("EXCEPTION_INVALID_FOLDER_URL", href);
+                }
+            }
+        }
+        if (folder.folderPath.endsWith("/")) {
+            folder.folderPath = folder.folderPath.substring(0, folder.folderPath.length() - 1);
+        }
+        return folder;
+    }
+
+    protected static final Set<String> FOLDER_PROPERTIES = new HashSet<String>();
+
+    static {
+        FOLDER_PROPERTIES.add("displayname");
+        FOLDER_PROPERTIES.add("folderclass");
+        FOLDER_PROPERTIES.add("hassubs");
+        FOLDER_PROPERTIES.add("nosubs");
+        FOLDER_PROPERTIES.add("unreadcount");
+        FOLDER_PROPERTIES.add("contenttag");
+        FOLDER_PROPERTIES.add("lastmodified");
+        FOLDER_PROPERTIES.add("uidNext");
+    }
+
+    protected static final DavPropertyNameSet FOLDER_PROPERTIES_NAME_SET = new DavPropertyNameSet();
+
+    static {
+        for (String attribute : FOLDER_PROPERTIES) {
+            FOLDER_PROPERTIES_NAME_SET.add(Field.getPropertyName(attribute));
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    protected Folder internalGetFolder(String folderPath) throws IOException {
+        MultiStatusResponse[] responses = DavGatewayHttpClientFacade.executePropFindMethod(
+                httpClient, URIUtil.encodePath(getFolderPath(folderPath)), 0, FOLDER_PROPERTIES_NAME_SET);
+        Folder folder = null;
+        if (responses.length > 0) {
+            folder = buildFolder(responses[0]);
+            folder.folderPath = folderPath;
+        }
+        return folder;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    public List<Folder> getSubFolders(String folderPath, Condition condition, boolean recursive) throws IOException {
+        boolean isPublic = folderPath.startsWith("/public");
+        FolderQueryTraversal mode = (!isPublic && recursive) ? FolderQueryTraversal.Deep : FolderQueryTraversal.Shallow;
+        List<Folder> folders = new ArrayList<Folder>();
+
+        MultiStatusResponse[] responses = searchItems(folderPath, FOLDER_PROPERTIES, and(isTrue("isfolder"), isFalse("ishidden"), condition), mode, 0);
+
+        for (MultiStatusResponse response : responses) {
+            Folder folder = buildFolder(response);
+            folders.add(buildFolder(response));
+            if (isPublic && recursive) {
+                getSubFolders(folder.folderPath, condition, recursive);
+            }
+        }
+        return folders;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    public int createFolder(String folderPath, String folderClass, Map<String, String> properties) throws IOException {
+        Set<PropertyValue> propertyValues = new HashSet<PropertyValue>();
+        if (properties != null) {
+            for (Map.Entry<String, String> entry : properties.entrySet()) {
+                propertyValues.add(Field.createPropertyValue(entry.getKey(), entry.getValue()));
+            }
+        }
+        propertyValues.add(Field.createPropertyValue("folderclass", folderClass));
+
+        // standard MkColMethod does not take properties, override PropPatchMethod instead
+        ExchangePropPatchMethod method = new ExchangePropPatchMethod(URIUtil.encodePath(getFolderPath(folderPath)), propertyValues) {
+            @Override
+            public String getName() {
+                return "MKCOL";
+            }
+        };
+        int status = DavGatewayHttpClientFacade.executeHttpMethod(httpClient, method);
+        if (status == HttpStatus.SC_MULTI_STATUS) {
+            status = method.getResponseStatusCode();
+        }
+        return status;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    public int updateFolder(String folderPath, Map<String, String> properties) throws IOException {
+        Set<PropertyValue> propertyValues = new HashSet<PropertyValue>();
+        if (properties != null) {
+            for (Map.Entry<String, String> entry : properties.entrySet()) {
+                propertyValues.add(Field.createPropertyValue(entry.getKey(), entry.getValue()));
+            }
+        }
+
+        // standard MkColMethod does not take properties, override PropPatchMethod instead
+        ExchangePropPatchMethod method = new ExchangePropPatchMethod(URIUtil.encodePath(getFolderPath(folderPath)), propertyValues);
+        int status = DavGatewayHttpClientFacade.executeHttpMethod(httpClient, method);
+        if (status == HttpStatus.SC_MULTI_STATUS) {
+            status = method.getResponseStatusCode();
+        }
+        return status;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    public void deleteFolder(String folderPath) throws IOException {
+        DavGatewayHttpClientFacade.executeDeleteMethod(httpClient, URIUtil.encodePath(getFolderPath(folderPath)));
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    public void moveFolder(String folderPath, String targetPath) throws IOException {
+        MoveMethod method = new MoveMethod(URIUtil.encodePath(getFolderPath(folderPath)),
+                URIUtil.encodePath(getFolderPath(targetPath)), false);
+        try {
+            int statusCode = httpClient.executeMethod(method);
+            if (statusCode == HttpStatus.SC_PRECONDITION_FAILED) {
+                throw new HttpPreconditionFailedException(BundleMessage.format("EXCEPTION_UNABLE_TO_MOVE_FOLDER"));
+            } else if (statusCode != HttpStatus.SC_CREATED) {
+                throw DavGatewayHttpClientFacade.buildHttpException(method);
+            } else if (folderPath.equalsIgnoreCase("/users/" + getEmail() + "/calendar")) {
+                // calendar renamed, need to reload well known folders 
+                getWellKnownFolders();
+            }
+        } finally {
+            method.releaseConnection();
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    public void moveItem(String sourcePath, String targetPath) throws IOException {
+        MoveMethod method = new MoveMethod(URIUtil.encodePath(getFolderPath(sourcePath)),
+                URIUtil.encodePath(getFolderPath(targetPath)), false);
+        moveItem(method);
+    }
+
+    protected void moveItem(MoveMethod method) throws IOException {
+        try {
+            int statusCode = httpClient.executeMethod(method);
+            if (statusCode == HttpStatus.SC_PRECONDITION_FAILED) {
+                throw new DavMailException("EXCEPTION_UNABLE_TO_MOVE_ITEM");
+            } else if (statusCode != HttpStatus.SC_CREATED && statusCode != HttpStatus.SC_OK) {
+                throw DavGatewayHttpClientFacade.buildHttpException(method);
+            }
+        } finally {
+            method.releaseConnection();
+        }
+    }
+
+    protected String getPropertyIfExists(DavPropertySet properties, String alias) {
+        DavProperty property = properties.get(Field.getResponsePropertyName(alias));
+        if (property == null) {
+            return null;
+        } else {
+            Object value = property.getValue();
+            if (value instanceof Node) {
+                return ((Node) value).getTextContent();
+            } else if (value instanceof List) {
+                StringBuilder buffer = new StringBuilder();
+                for (Object node : (List) value) {
+                    if (buffer.length() > 0) {
+                        buffer.append(',');
+                    }
+                    buffer.append(((Node) node).getTextContent());
+                }
+                return buffer.toString();
+            } else {
+                return (String) value;
+            }
+        }
+    }
+
+    protected String getURLPropertyIfExists(DavPropertySet properties, String alias) throws URIException {
+        String result = getPropertyIfExists(properties, alias);
+        if (result != null) {
+            result = URIUtil.decode(result);
+        }
+        return result;
+    }
+
+    protected int getIntPropertyIfExists(DavPropertySet properties, String alias) {
+        DavProperty property = properties.get(Field.getPropertyName(alias));
+        if (property == null) {
+            return 0;
+        } else {
+            return Integer.parseInt((String) property.getValue());
+        }
+    }
+
+    protected long getLongPropertyIfExists(DavPropertySet properties, String alias) {
+        DavProperty property = properties.get(Field.getPropertyName(alias));
+        if (property == null) {
+            return 0;
+        } else {
+            return Long.parseLong((String) property.getValue());
+        }
+    }
+
+    protected double getDoublePropertyIfExists(DavPropertySet properties, String alias) {
+        DavProperty property = properties.get(Field.getResponsePropertyName(alias));
+        if (property == null) {
+            return 0;
+        } else {
+            return Double.parseDouble((String) property.getValue());
+        }
+    }
+
+    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, DavMailException {
+        Message message = new Message();
+        message.messageUrl = URIUtil.decode(responseEntity.getHref());
+        DavPropertySet properties = responseEntity.getProperties(HttpStatus.SC_OK);
+
+        message.permanentUrl = getURLPropertyIfExists(properties, "permanenturl");
+        message.size = getIntPropertyIfExists(properties, "messageSize");
+        message.uid = getPropertyIfExists(properties, "uid");
+        message.contentClass = getPropertyIfExists(properties, "contentclass");
+        message.imapUid = getLongPropertyIfExists(properties, "imapUid");
+        message.read = "1".equals(getPropertyIfExists(properties, "read"));
+        message.junk = "1".equals(getPropertyIfExists(properties, "junk"));
+        message.flagged = "2".equals(getPropertyIfExists(properties, "flagStatus"));
+        message.draft = (getIntPropertyIfExists(properties, "messageFlags") & 8) != 0;
+        String lastVerbExecuted = getPropertyIfExists(properties, "lastVerbExecuted");
+        message.answered = "102".equals(lastVerbExecuted) || "103".equals(lastVerbExecuted);
+        message.forwarded = "104".equals(lastVerbExecuted);
+        message.date = convertDateFromExchange(getPropertyIfExists(properties, "date"));
+        message.deleted = "1".equals(getPropertyIfExists(properties, "deleted"));
+
+        String lastmodified = convertDateFromExchange(getPropertyIfExists(properties, "lastmodified"));
+        message.recent = !message.read && lastmodified != null && lastmodified.equals(message.date);
+
+        if (LOGGER.isDebugEnabled()) {
+            StringBuilder buffer = new StringBuilder();
+            buffer.append("Message");
+            if (message.imapUid != 0) {
+                buffer.append(" IMAP uid: ").append(message.imapUid);
+            }
+            if (message.uid != null) {
+                buffer.append(" uid: ").append(message.uid);
+            }
+            buffer.append(" href: ").append(responseEntity.getHref()).append(" permanenturl:").append(message.permanentUrl);
+            LOGGER.debug(buffer.toString());
+        }
+        return message;
+    }
+
+    @Override
+    public MessageList searchMessages(String folderPath, Set<String> attributes, Condition condition) throws IOException {
+        MessageList messages = new MessageList();
+        MultiStatusResponse[] responses = searchItems(folderPath, attributes, and(isFalse("isfolder"), isFalse("ishidden"), condition), FolderQueryTraversal.Shallow, 0);
+
+        for (MultiStatusResponse response : responses) {
+            Message message = buildMessage(response);
+            message.messageList = messages;
+            messages.add(message);
+        }
+        Collections.sort(messages);
+        return messages;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    public List<ExchangeSession.Contact> searchContacts(String folderPath, Set<String> attributes, Condition condition, int maxCount) throws IOException {
+        List<ExchangeSession.Contact> contacts = new ArrayList<ExchangeSession.Contact>();
+        MultiStatusResponse[] responses = searchItems(folderPath, attributes,
+                and(isEqualTo("outlookmessageclass", "IPM.Contact"), isFalse("isfolder"), isFalse("ishidden"), condition),
+                FolderQueryTraversal.Shallow, maxCount);
+        for (MultiStatusResponse response : responses) {
+            contacts.add(new Contact(response));
+        }
+        return contacts;
+    }
+
+    /**
+     * Common item properties
+     */
+    protected static final Set<String> ITEM_PROPERTIES = new HashSet<String>();
+
+    static {
+        ITEM_PROPERTIES.add("etag");
+        ITEM_PROPERTIES.add("displayname");
+        // calendar CdoInstanceType
+        ITEM_PROPERTIES.add("instancetype");
+        ITEM_PROPERTIES.add("urlcompname");
+        ITEM_PROPERTIES.add("subject");
+    }
+
+    protected Set<String> getItemProperties() {
+        return ITEM_PROPERTIES;
+    }
+
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    public List<ExchangeSession.Event> getEventMessages(String folderPath) throws IOException {
+        return searchEvents(folderPath, ITEM_PROPERTIES,
+                and(isEqualTo("contentclass", "urn:content-classes:calendarmessage"),
+                        or(isNull("processed"), isFalse("processed"))));
+    }
+
+
+    @Override
+    public List<ExchangeSession.Event> searchEvents(String folderPath, Set<String> attributes, Condition condition) throws IOException {
+        List<ExchangeSession.Event> events = new ArrayList<ExchangeSession.Event>();
+        MultiStatusResponse[] responses = searchItems(folderPath, attributes, and(isFalse("isfolder"), isFalse("ishidden"), condition), FolderQueryTraversal.Shallow, 0);
+        for (MultiStatusResponse response : responses) {
+            String instancetype = getPropertyIfExists(response.getProperties(HttpStatus.SC_OK), "instancetype");
+            Event event = new Event(response);
+            //noinspection VariableNotUsedInsideIf
+            if (instancetype == null) {
+                // check ics content
+                try {
+                    event.getBody();
+                    // getBody success => add event or task
+                    events.add(event);
+                } catch (IOException e) {
+                    // invalid event: exclude from list
+                    LOGGER.warn("Invalid event " + event.displayName + " found at " + response.getHref(), e);
+                }
+            } else {
+                events.add(event);
+            }
+        }
+        return events;
+    }
+
+    @Override
+    protected Condition getCalendarItemCondition(Condition dateCondition) {
+        boolean caldavEnableLegacyTasks = Settings.getBooleanProperty("davmail.caldavEnableLegacyTasks", false);
+        if (caldavEnableLegacyTasks) {
+            // return tasks created in calendar folder
+            return or(isNull("instancetype"),
+                    isEqualTo("instancetype", 1),
+                    and(isEqualTo("instancetype", 0), dateCondition));
+        } else {
+            // instancetype 0 single appointment / 1 master recurring appointment
+            return and(isEqualTo("outlookmessageclass", "IPM.Appointment"),
+                    or(isEqualTo("instancetype", 1),
+                            and(isEqualTo("instancetype", 0), dateCondition)));
+        }
+    }
+
+    protected MultiStatusResponse[] searchItems(String folderPath, Set<String> attributes, Condition condition,
+                                                FolderQueryTraversal folderQueryTraversal, int maxCount) throws IOException {
+        String folderUrl;
+        if (folderPath.startsWith("http")) {
+            folderUrl = folderPath;
+        } else {
+            folderUrl = getFolderPath(folderPath);
+        }
+        StringBuilder searchRequest = new StringBuilder();
+        searchRequest.append("SELECT ")
+                .append(Field.getRequestPropertyString("permanenturl"));
+        if (attributes != null) {
+            for (String attribute : attributes) {
+                searchRequest.append(',').append(Field.getRequestPropertyString(attribute));
+            }
+        }
+        searchRequest.append(" FROM SCOPE('").append(folderQueryTraversal).append(" TRAVERSAL OF \"").append(folderUrl).append("\"')");
+        if (condition != null) {
+            searchRequest.append(" WHERE ");
+            condition.appendTo(searchRequest);
+        }
+        DavGatewayTray.debug(new BundleMessage("LOG_SEARCH_QUERY", searchRequest));
+        MultiStatusResponse[] responses = DavGatewayHttpClientFacade.executeSearchMethod(
+                httpClient, encodeAndFixUrl(folderUrl), searchRequest.toString(), maxCount);
+        DavGatewayTray.debug(new BundleMessage("LOG_SEARCH_RESULT", responses.length));
+        return responses;
+    }
+
+    protected static final Set<String> EVENT_REQUEST_PROPERTIES = new HashSet<String>();
+
+    static {
+        EVENT_REQUEST_PROPERTIES.add("permanenturl");
+        EVENT_REQUEST_PROPERTIES.add("urlcompname");
+        EVENT_REQUEST_PROPERTIES.add("etag");
+        EVENT_REQUEST_PROPERTIES.add("contentclass");
+        EVENT_REQUEST_PROPERTIES.add("displayname");
+        EVENT_REQUEST_PROPERTIES.add("subject");
+    }
+
+    protected static final DavPropertyNameSet EVENT_REQUEST_PROPERTIES_NAME_SET = new DavPropertyNameSet();
+
+    static {
+        for (String attribute : EVENT_REQUEST_PROPERTIES) {
+            EVENT_REQUEST_PROPERTIES_NAME_SET.add(Field.getPropertyName(attribute));
+        }
+
+    }
+
+    @Override
+    public Item getItem(String folderPath, String itemName) throws IOException {
+        String emlItemName = convertItemNameToEML(itemName);
+        String itemPath = getFolderPath(folderPath) + '/' + emlItemName;
+        MultiStatusResponse[] responses = null;
+        try {
+            try {
+                responses = DavGatewayHttpClientFacade.executePropFindMethod(httpClient, URIUtil.encodePath(itemPath), 0, EVENT_REQUEST_PROPERTIES_NAME_SET);
+            } catch (HttpNotFoundException e) {
+                // ignore
+            }
+            if (responses == null || responses.length == 0 && isMainCalendar(folderPath)) {
+                if (itemName.endsWith(".ics")) {
+                    itemName = itemName.substring(0, itemName.length() - 3) + "EML";
+                }
+                // look for item in tasks folder
+                responses = DavGatewayHttpClientFacade.executePropFindMethod(httpClient, URIUtil.encodePath(getFolderPath(TASKS) + '/' + emlItemName), 0, EVENT_REQUEST_PROPERTIES_NAME_SET);
+            }
+            if (responses == null || responses.length == 0) {
+                throw new HttpNotFoundException(itemPath + " not found");
+            }
+        } catch (HttpNotFoundException e) {
+            try {
+                LOGGER.debug(itemPath + " not found, searching by urlcompname");
+                // failover: try to get event by displayname
+                responses = searchItems(folderPath, EVENT_REQUEST_PROPERTIES, isEqualTo("urlcompname", emlItemName), FolderQueryTraversal.Shallow, 1);
+                if (responses.length == 0 && isMainCalendar(folderPath)) {
+                    responses = searchItems(TASKS, EVENT_REQUEST_PROPERTIES, isEqualTo("urlcompname", emlItemName), FolderQueryTraversal.Shallow, 1);
+                }
+                if (responses.length == 0) {
+                    throw new HttpNotFoundException(itemPath + " not found");
+                }
+            } catch (HttpNotFoundException e2) {
+                LOGGER.debug("last failover: search all items");
+                List<ExchangeSession.Event> events = getAllEvents(folderPath);
+                for (ExchangeSession.Event event : events) {
+                    if (itemName.equals(event.getName())) {
+                        responses = DavGatewayHttpClientFacade.executePropFindMethod(httpClient, encodeAndFixUrl(((DavExchangeSession.Event) event).getPermanentUrl()), 0, EVENT_REQUEST_PROPERTIES_NAME_SET);
+                        break;
+                    }
+                }
+                if (responses == null || responses.length == 0) {
+                    throw new HttpNotFoundException(itemPath + " not found");
+                }
+                LOGGER.warn("search by urlcompname failed, actual value is " + getPropertyIfExists(responses[0].getProperties(HttpStatus.SC_OK), "urlcompname"));
+            }
+        }
+        // build item
+        String contentClass = getPropertyIfExists(responses[0].getProperties(HttpStatus.SC_OK), "contentclass");
+        String urlcompname = getPropertyIfExists(responses[0].getProperties(HttpStatus.SC_OK), "urlcompname");
+        if ("urn:content-classes:person".equals(contentClass)) {
+            // retrieve Contact properties
+            List<ExchangeSession.Contact> contacts = searchContacts(folderPath, CONTACT_ATTRIBUTES,
+                    isEqualTo("urlcompname", StringUtil.decodeUrlcompname(urlcompname)), 1);
+            if (contacts.isEmpty()) {
+                LOGGER.warn("Item found, but unable to build contact");
+                throw new HttpNotFoundException(itemPath + " not found");
+            }
+            return contacts.get(0);
+        } else if ("urn:content-classes:appointment".equals(contentClass)
+                || "urn:content-classes:calendarmessage".equals(contentClass)
+                || "urn:content-classes:task".equals(contentClass)) {
+            return new Event(responses[0]);
+        } else {
+            LOGGER.warn("wrong contentclass on item " + itemPath + ": " + contentClass);
+            // return item anyway
+            return new Event(responses[0]);
+        }
+
+    }
+
+    @Override
+    public ExchangeSession.ContactPhoto getContactPhoto(ExchangeSession.Contact contact) throws IOException {
+        ContactPhoto contactPhoto = null;
+        if ("true".equals(contact.get("haspicture"))) {
+            final GetMethod method = new GetMethod(URIUtil.encodePath(contact.getHref()) + "/ContactPicture.jpg");
+            method.setRequestHeader("Translate", "f");
+            method.setRequestHeader("Accept-Encoding", "gzip");
+
+            InputStream inputStream = null;
+            try {
+                DavGatewayHttpClientFacade.executeGetMethod(httpClient, method, true);
+                if (DavGatewayHttpClientFacade.isGzipEncoded(method)) {
+                    inputStream = (new GZIPInputStream(method.getResponseBodyAsStream()));
+                } else {
+                    inputStream = method.getResponseBodyAsStream();
+                }
+
+                contactPhoto = new ContactPhoto();
+                contactPhoto.contentType = "image/jpeg";
+
+                ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                InputStream partInputStream = inputStream;
+                byte[] bytes = new byte[8192];
+                int length;
+                while ((length = partInputStream.read(bytes)) > 0) {
+                    baos.write(bytes, 0, length);
+                }
+                contactPhoto.content = new String(Base64.encodeBase64(baos.toByteArray()));
+            } finally {
+                if (inputStream != null) {
+                    try {
+                        inputStream.close();
+                    } catch (IOException e) {
+                        LOGGER.debug(e);
+                    }
+                }
+                method.releaseConnection();
+            }
+        }
+        return contactPhoto;
+    }
+
+    @Override
+    public int sendEvent(String icsBody) throws IOException {
+        String itemName = UUID.randomUUID().toString() + ".EML";
+        byte[] mimeContent = (new Event(getFolderPath(DRAFTS), itemName, "urn:content-classes:calendarmessage", icsBody, null, null)).createMimeContent();
+        if (mimeContent == null) {
+            // no recipients, cancel
+            return HttpStatus.SC_NO_CONTENT;
+        } else {
+            sendMessage(mimeContent);
+            return HttpStatus.SC_OK;
+        }
+    }
+
+    @Override
+    public void deleteItem(String folderPath, String itemName) throws IOException {
+        String eventPath = URIUtil.encodePath(getFolderPath(folderPath) + '/' + convertItemNameToEML(itemName));
+        int status = DavGatewayHttpClientFacade.executeDeleteMethod(httpClient, eventPath);
+        if (status == HttpStatus.SC_NOT_FOUND && isMainCalendar(folderPath)) {
+            // retry in tasks folder
+            eventPath = URIUtil.encodePath(getFolderPath(TASKS) + '/' + convertItemNameToEML(itemName));
+            status = DavGatewayHttpClientFacade.executeDeleteMethod(httpClient, eventPath);
+        }
+        if (status == HttpStatus.SC_NOT_FOUND) {
+            LOGGER.debug("Unable to delete " + itemName + ": item not found");
+        }
+    }
+
+    @Override
+    public void processItem(String folderPath, String itemName) throws IOException {
+        String eventPath = URIUtil.encodePath(getFolderPath(folderPath) + '/' + convertItemNameToEML(itemName));
+        // do not delete calendar messages, mark read and processed
+        ArrayList<DavConstants> list = new ArrayList<DavConstants>();
+        list.add(Field.createDavProperty("processed", "true"));
+        list.add(Field.createDavProperty("read", "1"));
+        PropPatchMethod patchMethod = new PropPatchMethod(eventPath, list);
+        DavGatewayHttpClientFacade.executeMethod(httpClient, patchMethod);
+    }
+
+    @Override
+    public ItemResult internalCreateOrUpdateEvent(String folderPath, String itemName, String contentClass, String icsBody, String etag, String noneMatch) throws IOException {
+        return new Event(getFolderPath(folderPath), itemName, contentClass, icsBody, etag, noneMatch).createOrUpdate();
+    }
+
+    /**
+     * create a fake event to get VTIMEZONE body
+     */
+    @Override
+    protected void loadVtimezone() {
+        try {
+            // create temporary folder
+            String folderPath = getFolderPath("davmailtemp");
+            createCalendarFolder(folderPath, null);
+
+            String fakeEventUrl = null;
+            if ("Exchange2003".equals(serverVersion)) {
+                PostMethod postMethod = new PostMethod(URIUtil.encodePath(folderPath));
+                postMethod.addParameter("Cmd", "saveappt");
+                postMethod.addParameter("FORMTYPE", "appointment");
+                try {
+                    // create fake event
+                    int statusCode = httpClient.executeMethod(postMethod);
+                    if (statusCode == HttpStatus.SC_OK) {
+                        fakeEventUrl = StringUtil.getToken(postMethod.getResponseBodyAsString(), "<span id=\"itemHREF\">", "</span>");
+                        if (fakeEventUrl != null) {
+                            fakeEventUrl = URIUtil.decode(fakeEventUrl);
+                        }
+                    }
+                } finally {
+                    postMethod.releaseConnection();
+                }
+            }
+            // failover for Exchange 2007, use PROPPATCH with forced timezone
+            if (fakeEventUrl == null) {
+                ArrayList<DavConstants> propertyList = new ArrayList<DavConstants>();
+                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
+                String timezoneId = Settings.getProperty("davmail.timezoneId");
+                if (timezoneId == null) {
+                    // get timezoneid from OWA settings
+                    timezoneId = getTimezoneIdFromExchange();
+                }
+                // without a timezoneId, use Exchange timezone
+                if (timezoneId != null) {
+                    propertyList.add(Field.createDavProperty("timezoneid", timezoneId));
+                }
+                String patchMethodUrl = folderPath + '/' + UUID.randomUUID().toString() + ".EML";
+                PropPatchMethod patchMethod = new PropPatchMethod(URIUtil.encodePath(patchMethodUrl), propertyList);
+                try {
+                    int statusCode = httpClient.executeMethod(patchMethod);
+                    if (statusCode == HttpStatus.SC_MULTI_STATUS) {
+                        fakeEventUrl = patchMethodUrl;
+                    }
+                } finally {
+                    patchMethod.releaseConnection();
+                }
+            }
+            if (fakeEventUrl != null) {
+                // get fake event body
+                GetMethod getMethod = new GetMethod(URIUtil.encodePath(fakeEventUrl));
+                getMethod.setRequestHeader("Translate", "f");
+                try {
+                    httpClient.executeMethod(getMethod);
+                    this.vTimezone = new VObject("BEGIN:VTIMEZONE" +
+                            StringUtil.getToken(getMethod.getResponseBodyAsString(), "BEGIN:VTIMEZONE", "END:VTIMEZONE") +
+                            "END:VTIMEZONE\r\n");
+                } finally {
+                    getMethod.releaseConnection();
+                }
+            }
+
+            // delete temporary folder
+            deleteFolder("davmailtemp");
+        } catch (IOException e) {
+            LOGGER.warn("Unable to get VTIMEZONE info: " + e, e);
+        }
+    }
+
+    protected String getTimezoneIdFromExchange() {
+        String timezoneId = null;
+        String timezoneName = null;
+        try {
+            Set<String> attributes = new HashSet<String>();
+            attributes.add("roamingdictionary");
+
+            MultiStatusResponse[] responses = searchItems("/users/" + getEmail() + "/NON_IPM_SUBTREE", attributes, isEqualTo("messageclass", "IPM.Configuration.OWA.UserOptions"), DavExchangeSession.FolderQueryTraversal.Deep, 1);
+            if (responses.length == 1) {
+                byte[] roamingdictionary = getBinaryPropertyIfExists(responses[0].getProperties(HttpStatus.SC_OK), "roamingdictionary");
+                if (roamingdictionary != null) {
+                    timezoneName = getTimezoneNameFromRoamingDictionary(roamingdictionary);
+                    if (timezoneName != null) {
+                        timezoneId = ResourceBundle.getBundle("timezoneids").getString(timezoneName);
+                    }
+                }
+            }
+        } catch (MissingResourceException e) {
+            LOGGER.warn("Unable to retrieve Exchange timezone id for 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;
+    }
+
+    protected String getTimezoneNameFromRoamingDictionary(byte[] roamingdictionary) {
+        String timezoneName = null;
+        XMLStreamReader reader;
+        try {
+            reader = XMLStreamUtil.createXMLStreamReader(roamingdictionary);
+            while (reader.hasNext()) {
+                reader.next();
+                if (XMLStreamUtil.isStartTag(reader, "e")
+                        && "18-timezone".equals(reader.getAttributeValue(null, "k"))) {
+                    String value = reader.getAttributeValue(null, "v");
+                    if (value != null && value.startsWith("18-")) {
+                        timezoneName = value.substring(3);
+                    }
+                }
+            }
+
+        } catch (XMLStreamException e) {
+            LOGGER.error("Error while parsing RoamingDictionary: " + e, e);
+        }
+        return timezoneName;
+    }
+
+    @Override
+    protected ItemResult internalCreateOrUpdateContact(String folderPath, String itemName, Map<String, String> properties, String etag, String noneMatch) throws IOException {
+        return new Contact(getFolderPath(folderPath), itemName, properties, etag, noneMatch).createOrUpdate();
+    }
+
+    protected List<DavConstants> buildProperties(Map<String, String> properties) {
+        ArrayList<DavConstants> list = new ArrayList<DavConstants>();
+        if (properties != null) {
+            for (Map.Entry<String, String> entry : properties.entrySet()) {
+                if ("read".equals(entry.getKey())) {
+                    list.add(Field.createDavProperty("read", entry.getValue()));
+                } else if ("junk".equals(entry.getKey())) {
+                    list.add(Field.createDavProperty("junk", entry.getValue()));
+                } else if ("flagged".equals(entry.getKey())) {
+                    list.add(Field.createDavProperty("flagStatus", entry.getValue()));
+                } else if ("answered".equals(entry.getKey())) {
+                    list.add(Field.createDavProperty("lastVerbExecuted", entry.getValue()));
+                    if ("102".equals(entry.getValue())) {
+                        list.add(Field.createDavProperty("iconIndex", "261"));
+                    }
+                } else if ("forwarded".equals(entry.getKey())) {
+                    list.add(Field.createDavProperty("lastVerbExecuted", entry.getValue()));
+                    if ("104".equals(entry.getValue())) {
+                        list.add(Field.createDavProperty("iconIndex", "262"));
+                    }
+                } else if ("bcc".equals(entry.getKey())) {
+                    list.add(Field.createDavProperty("bcc", entry.getValue()));
+                } else if ("deleted".equals(entry.getKey())) {
+                    list.add(Field.createDavProperty("deleted", entry.getValue()));
+                } else if ("datereceived".equals(entry.getKey())) {
+                    list.add(Field.createDavProperty("datereceived", entry.getValue()));
+                }
+            }
+        }
+        return list;
+    }
+
+    /**
+     * Create message in specified folder.
+     * Will overwrite an existing message with same messageName in the same folder
+     *
+     * @param folderPath  Exchange folder path
+     * @param messageName message name
+     * @param properties  message properties (flags)
+     * @param mimeMessage MIME message
+     * @throws IOException when unable to create message
+     */
+    @Override
+    public void createMessage(String folderPath, String messageName, HashMap<String, String> properties, MimeMessage mimeMessage) throws IOException {
+        String messageUrl = URIUtil.encodePathQuery(getFolderPath(folderPath) + '/' + messageName);
+        PropPatchMethod patchMethod;
+        List<DavConstants> davProperties = buildProperties(properties);
+
+        if (properties != null && properties.containsKey("draft")) {
+            // note: draft is readonly after create, create the message first with requested messageFlags
+            davProperties.add(Field.createDavProperty("messageFlags", properties.get("draft")));
+        }
+        if (properties != null && properties.containsKey("mailOverrideFormat")) {
+            davProperties.add(Field.createDavProperty("mailOverrideFormat", properties.get("mailOverrideFormat")));
+        }
+        if (properties != null && properties.containsKey("messageFormat")) {
+            davProperties.add(Field.createDavProperty("messageFormat", properties.get("messageFormat")));
+        }
+        if (!davProperties.isEmpty()) {
+            patchMethod = new PropPatchMethod(messageUrl, davProperties);
+            try {
+                // update message with blind carbon copy and other flags
+                int statusCode = httpClient.executeMethod(patchMethod);
+                if (statusCode != HttpStatus.SC_MULTI_STATUS) {
+                    throw new DavMailException("EXCEPTION_UNABLE_TO_CREATE_MESSAGE", messageUrl, statusCode, ' ', patchMethod.getStatusLine());
+                }
+
+            } finally {
+                patchMethod.releaseConnection();
+            }
+        }
+
+        // update message body
+        PutMethod putmethod = new PutMethod(messageUrl);
+        putmethod.setRequestHeader("Translate", "f");
+        putmethod.setRequestHeader("Content-Type", "message/rfc822");
+
+        try {
+            // use same encoding as client socket reader
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            mimeMessage.writeTo(baos);
+            baos.close();
+            putmethod.setRequestEntity(new ByteArrayRequestEntity(baos.toByteArray()));
+            int code = httpClient.executeMethod(putmethod);
+
+            // workaround for misconfigured Exchange server
+            if (code == HttpStatus.SC_NOT_ACCEPTABLE) {
+                LOGGER.warn("Draft message creation failed, failover to property update. Note: attachments are lost");
+
+                ArrayList<DavConstants> propertyList = new ArrayList<DavConstants>();
+                propertyList.add(Field.createDavProperty("to", mimeMessage.getHeader("to", ",")));
+                propertyList.add(Field.createDavProperty("cc", mimeMessage.getHeader("cc", ",")));
+                propertyList.add(Field.createDavProperty("message-id", mimeMessage.getHeader("message-id", ",")));
+
+                MimePart mimePart = mimeMessage;
+                if (mimeMessage.getContent() instanceof MimeMultipart) {
+                    MimeMultipart multiPart = (MimeMultipart) mimeMessage.getContent();
+                    for (int i = 0; i < multiPart.getCount(); i++) {
+                        String contentType = multiPart.getBodyPart(i).getContentType();
+                        if (contentType.startsWith("text/")) {
+                            mimePart = (MimePart) multiPart.getBodyPart(i);
+                            break;
+                        }
+                    }
+                }
+
+                String contentType = mimePart.getContentType();
+
+                if (contentType.startsWith("text/plain")) {
+                    propertyList.add(Field.createDavProperty("description", (String) mimePart.getContent()));
+                } else if (contentType.startsWith("text/html")) {
+                    propertyList.add(Field.createDavProperty("htmldescription", (String) mimePart.getContent()));
+                } else {
+                    LOGGER.warn("Unsupported content type: " + contentType + " message body will be empty");
+                }
+
+                propertyList.add(Field.createDavProperty("subject", mimeMessage.getHeader("subject", ",")));
+                PropPatchMethod propPatchMethod = new PropPatchMethod(messageUrl, propertyList);
+                try {
+                    int patchStatus = DavGatewayHttpClientFacade.executeHttpMethod(httpClient, propPatchMethod);
+                    if (patchStatus == HttpStatus.SC_MULTI_STATUS) {
+                        code = HttpStatus.SC_OK;
+                    }
+                } finally {
+                    propPatchMethod.releaseConnection();
+                }
+            }
+
+            if (code != HttpStatus.SC_OK && code != HttpStatus.SC_CREATED) {
+
+                // first delete draft message
+                if (!davProperties.isEmpty()) {
+                    try {
+                        DavGatewayHttpClientFacade.executeDeleteMethod(httpClient, messageUrl);
+                    } catch (IOException e) {
+                        LOGGER.warn("Unable to delete draft message");
+                    }
+                }
+                if (code == HttpStatus.SC_INSUFFICIENT_STORAGE) {
+                    throw new InsufficientStorageException(putmethod.getStatusText());
+                } else {
+                    throw new DavMailException("EXCEPTION_UNABLE_TO_CREATE_MESSAGE", messageUrl, code, ' ', putmethod.getStatusLine());
+                }
+            }
+        } catch (MessagingException e) {
+            throw new IOException(e.getMessage());
+        } finally {
+            putmethod.releaseConnection();
+        }
+
+        try {
+            // need to update bcc after put
+            if (mimeMessage.getHeader("Bcc") != null) {
+                davProperties = new ArrayList<DavConstants>();
+                davProperties.add(Field.createDavProperty("bcc", mimeMessage.getHeader("Bcc", ",")));
+                patchMethod = new PropPatchMethod(messageUrl, davProperties);
+                try {
+                    // update message with blind carbon copy
+                    int statusCode = httpClient.executeMethod(patchMethod);
+                    if (statusCode != HttpStatus.SC_MULTI_STATUS) {
+                        throw new DavMailException("EXCEPTION_UNABLE_TO_CREATE_MESSAGE", messageUrl, statusCode, ' ', patchMethod.getStatusLine());
+                    }
+
+                } finally {
+                    patchMethod.releaseConnection();
+                }
+            }
+        } catch (MessagingException e) {
+            throw new IOException(e.getMessage());
+        }
+
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    public void updateMessage(ExchangeSession.Message message, Map<String, String> properties) throws IOException {
+        PropPatchMethod patchMethod = new PropPatchMethod(encodeAndFixUrl(message.permanentUrl), buildProperties(properties)) {
+            @Override
+            protected void processResponseBody(HttpState httpState, HttpConnection httpConnection) {
+                // ignore response body, sometimes invalid with exchange mapi properties
+            }
+        };
+        try {
+            int statusCode = httpClient.executeMethod(patchMethod);
+            if (statusCode != HttpStatus.SC_MULTI_STATUS) {
+                throw new DavMailException("EXCEPTION_UNABLE_TO_UPDATE_MESSAGE");
+            }
+
+        } finally {
+            patchMethod.releaseConnection();
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    public void deleteMessage(ExchangeSession.Message message) throws IOException {
+        LOGGER.debug("Delete " + message.permanentUrl + " (" + message.messageUrl + ')');
+        DavGatewayHttpClientFacade.executeDeleteMethod(httpClient, encodeAndFixUrl(message.permanentUrl));
+    }
+
+    /**
+     * Send message.
+     *
+     * @param messageBody MIME message body
+     * @throws IOException on error
+     */
+    public void sendMessage(byte[] messageBody) throws IOException {
+        try {
+            sendMessage(new MimeMessage(null, new SharedByteArrayInputStream(messageBody)));
+        } catch (MessagingException e) {
+            throw new IOException(e.getMessage());
+        }
+    }
+
+    //protected static final long MAPI_SEND_NO_RICH_INFO = 0x00010000L;
+    protected static final long ENCODING_PREFERENCE = 0x00020000L;
+    protected static final long ENCODING_MIME = 0x00040000L;
+    //protected static final long BODY_ENCODING_HTML = 0x00080000L;
+    protected static final long BODY_ENCODING_TEXT_AND_HTML = 0x00100000L;
+    //protected static final long MAC_ATTACH_ENCODING_UUENCODE = 0x00200000L;
+    //protected static final long MAC_ATTACH_ENCODING_APPLESINGLE = 0x00400000L;
+    //protected static final long MAC_ATTACH_ENCODING_APPLEDOUBLE = 0x00600000L;
+    //protected static final long OOP_DONT_LOOKUP = 0x10000000L;
+
+    @Override
+    public void sendMessage(MimeMessage mimeMessage) throws IOException {
+        try {
+            // need to create draft first
+            String itemName = UUID.randomUUID().toString() + ".EML";
+            HashMap<String, String> properties = new HashMap<String, String>();
+            properties.put("draft", "9");
+            String contentType = mimeMessage.getContentType();
+            if (contentType != null && contentType.startsWith("text/plain")) {
+                properties.put("messageFormat", "1");
+            } else {
+                properties.put("mailOverrideFormat", String.valueOf(ENCODING_PREFERENCE | ENCODING_MIME | BODY_ENCODING_TEXT_AND_HTML));
+                properties.put("messageFormat", "2");
+            }
+            createMessage(DRAFTS, itemName, properties, mimeMessage);
+            MoveMethod method = new MoveMethod(URIUtil.encodePath(getFolderPath(DRAFTS + '/' + itemName)),
+                    URIUtil.encodePath(getFolderPath(SENDMSG)), false);
+            // set header if saveInSent is disabled 
+            if (!Settings.getBooleanProperty("davmail.smtpSaveInSent", true)) {
+                method.setRequestHeader("Saveinsent", "f");
+            }
+            moveItem(method);
+        } catch (MessagingException e) {
+            throw new IOException(e.getMessage());
+        }
+    }
+
+    // wrong hostname fix flag
+    protected boolean restoreHostName;
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    protected byte[] getContent(ExchangeSession.Message message) throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        InputStream contentInputStream;
+        try {
+            try {
+                try {
+                    contentInputStream = getContentInputStream(message.messageUrl);
+                } catch (UnknownHostException e) {
+                    // failover for misconfigured Exchange server, replace host name in url
+                    restoreHostName = true;
+                    contentInputStream = getContentInputStream(message.messageUrl);
+                }
+            } catch (HttpNotFoundException e) {
+                LOGGER.debug("Message not found at: " + message.messageUrl + ", retrying with permanenturl");
+                contentInputStream = getContentInputStream(message.permanentUrl);
+            }
+
+            try {
+                IOUtil.write(contentInputStream, baos);
+            } finally {
+                contentInputStream.close();
+            }
+
+        } catch (LoginTimeoutException e) {
+            // throw error on expired session
+            LOGGER.warn(e.getMessage());
+            throw e;
+        } catch (IOException e) {
+            LOGGER.warn("Broken message at: " + message.messageUrl + " permanentUrl: " + message.permanentUrl + ", trying to rebuild from properties");
+
+            try {
+                DavPropertyNameSet messageProperties = new DavPropertyNameSet();
+                messageProperties.add(Field.getPropertyName("contentclass"));
+                messageProperties.add(Field.getPropertyName("message-id"));
+                messageProperties.add(Field.getPropertyName("from"));
+                messageProperties.add(Field.getPropertyName("to"));
+                messageProperties.add(Field.getPropertyName("cc"));
+                messageProperties.add(Field.getPropertyName("subject"));
+                messageProperties.add(Field.getPropertyName("date"));
+                messageProperties.add(Field.getPropertyName("htmldescription"));
+                messageProperties.add(Field.getPropertyName("body"));
+                PropFindMethod propFindMethod = new PropFindMethod(encodeAndFixUrl(message.permanentUrl), messageProperties, 0);
+                DavGatewayHttpClientFacade.executeMethod(httpClient, propFindMethod);
+                MultiStatus responses = propFindMethod.getResponseBodyAsMultiStatus();
+                if (responses.getResponses().length > 0) {
+                    MimeMessage mimeMessage = new MimeMessage((Session) null);
+
+                    DavPropertySet properties = responses.getResponses()[0].getProperties(HttpStatus.SC_OK);
+                    String propertyValue = getPropertyIfExists(properties, "contentclass");
+                    if (propertyValue != null) {
+                        mimeMessage.addHeader("Content-class", propertyValue);
+                    }
+                    propertyValue = getPropertyIfExists(properties, "date");
+                    if (propertyValue != null) {
+                        mimeMessage.setSentDate(parseDateFromExchange(propertyValue));
+                    }
+                    propertyValue = getPropertyIfExists(properties, "from");
+                    if (propertyValue != null) {
+                        mimeMessage.addHeader("From", propertyValue);
+                    }
+                    propertyValue = getPropertyIfExists(properties, "to");
+                    if (propertyValue != null) {
+                        mimeMessage.addHeader("To", propertyValue);
+                    }
+                    propertyValue = getPropertyIfExists(properties, "cc");
+                    if (propertyValue != null) {
+                        mimeMessage.addHeader("Cc", propertyValue);
+                    }
+                    propertyValue = getPropertyIfExists(properties, "subject");
+                    if (propertyValue != null) {
+                        mimeMessage.setSubject(propertyValue);
+                    }
+                    propertyValue = getPropertyIfExists(properties, "htmldescription");
+                    if (propertyValue != null) {
+                        mimeMessage.setContent(propertyValue, "text/html; charset=UTF-8");
+                    } else {
+                        propertyValue = getPropertyIfExists(properties, "body");
+                        if (propertyValue != null) {
+                            mimeMessage.setText(propertyValue);
+                        }
+                    }
+                    mimeMessage.writeTo(baos);
+                }
+                if (LOGGER.isDebugEnabled()) {
+                    LOGGER.debug("Rebuilt message content: " + new String(baos.toByteArray()));
+                }
+            } catch (IOException e2) {
+                LOGGER.warn(e2);
+            } catch (DavException e2) {
+                LOGGER.warn(e2);
+            } catch (MessagingException e2) {
+                LOGGER.warn(e2);
+            }
+            // other exception
+            if (baos.size() == 0 && Settings.getBooleanProperty("davmail.deleteBroken")) {
+                LOGGER.warn("Deleting broken message at: " + message.messageUrl + " permanentUrl: " + message.permanentUrl);
+                try {
+                    message.delete();
+                } catch (IOException ioe) {
+                    LOGGER.warn("Unable to delete broken message at: " + message.permanentUrl);
+                }
+                throw e;
+            }
+        }
+
+        return baos.toByteArray();
+    }
+
+    protected String getEscapedUrlFromPath(String escapedPath) throws URIException {
+        URI uri = new URI(httpClient.getHostConfiguration().getHostURL(), true);
+        uri.setEscapedPath(escapedPath);
+        return uri.getEscapedURI();
+    }
+
+    public String encodeAndFixUrl(String url) throws URIException {
+        String originalUrl = URIUtil.encodePath(url);
+        if (restoreHostName && originalUrl.startsWith("http")) {
+            String targetPath = new URI(originalUrl, true).getEscapedPath();
+            originalUrl = getEscapedUrlFromPath(targetPath);
+        }
+        return originalUrl;
+    }
+
+    protected InputStream getContentInputStream(String url) throws IOException {
+        String encodedUrl = encodeAndFixUrl(url);
+
+        final GetMethod method = new GetMethod(encodedUrl);
+        method.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
+        method.setRequestHeader("Translate", "f");
+        method.setRequestHeader("Accept-Encoding", "gzip");
+
+        InputStream inputStream;
+        try {
+            DavGatewayHttpClientFacade.executeGetMethod(httpClient, method, true);
+            if (DavGatewayHttpClientFacade.isGzipEncoded(method)) {
+                inputStream = new GZIPInputStream(method.getResponseBodyAsStream());
+            } else {
+                inputStream = method.getResponseBodyAsStream();
+            }
+            inputStream = new FilterInputStream(inputStream) {
+                int totalCount;
+                int lastLogCount;
+
+                @Override
+                public int read(byte[] buffer, int offset, int length) throws IOException {
+                    int count = super.read(buffer, offset, length);
+                    totalCount += count;
+                    if (totalCount - lastLogCount > 1024 * 128) {
+                        DavGatewayTray.debug(new BundleMessage("LOG_DOWNLOAD_PROGRESS", String.valueOf(totalCount / 1024), method.getURI()));
+                        DavGatewayTray.switchIcon();
+                        lastLogCount = totalCount;
+                    }
+                    return count;
+                }
+
+                @Override
+                public void close() throws IOException {
+                    try {
+                        super.close();
+                    } finally {
+                        method.releaseConnection();
+                    }
+                }
+            };
+
+        } catch (HttpException e) {
+            method.releaseConnection();
+            LOGGER.warn("Unable to retrieve message at: " + url);
+            throw e;
+        }
+        return inputStream;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    public void moveMessage(ExchangeSession.Message message, String targetFolder) throws IOException {
+        try {
+            moveMessage(message.permanentUrl, targetFolder);
+        } catch (HttpNotFoundException e) {
+            LOGGER.debug("404 not found at permanenturl: " + message.permanentUrl + ", retry with messageurl");
+            moveMessage(message.messageUrl, targetFolder);
+        }
+    }
+
+    protected void moveMessage(String sourceUrl, String targetFolder) throws IOException {
+        String targetPath = URIUtil.encodePath(getFolderPath(targetFolder)) + '/' + UUID.randomUUID().toString();
+        MoveMethod method = new MoveMethod(URIUtil.encodePath(sourceUrl), targetPath, false);
+        // allow rename if a message with the same name exists
+        method.addRequestHeader("Allow-Rename", "t");
+        try {
+            int statusCode = httpClient.executeMethod(method);
+            if (statusCode == HttpStatus.SC_PRECONDITION_FAILED) {
+                throw new DavMailException("EXCEPTION_UNABLE_TO_MOVE_MESSAGE");
+            } else if (statusCode != HttpStatus.SC_CREATED) {
+                throw DavGatewayHttpClientFacade.buildHttpException(method);
+            }
+        } finally {
+            method.releaseConnection();
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    public void copyMessage(ExchangeSession.Message message, String targetFolder) throws IOException {
+        try {
+            copyMessage(message.permanentUrl, targetFolder);
+        } catch (HttpNotFoundException e) {
+            LOGGER.debug("404 not found at permanenturl: " + message.permanentUrl + ", retry with messageurl");
+            copyMessage(message.messageUrl, targetFolder);
+        }
+    }
+
+    protected void copyMessage(String sourceUrl, String targetFolder) throws IOException {
+        String targetPath = URIUtil.encodePath(getFolderPath(targetFolder)) + '/' + UUID.randomUUID().toString();
+        CopyMethod method = new CopyMethod(URIUtil.encodePath(sourceUrl), targetPath, false);
+        // allow rename if a message with the same name exists
+        method.addRequestHeader("Allow-Rename", "t");
+        try {
+            int statusCode = httpClient.executeMethod(method);
+            if (statusCode == HttpStatus.SC_PRECONDITION_FAILED) {
+                throw new DavMailException("EXCEPTION_UNABLE_TO_COPY_MESSAGE");
+            } else if (statusCode != HttpStatus.SC_CREATED) {
+                throw DavGatewayHttpClientFacade.buildHttpException(method);
+            }
+        } finally {
+            method.releaseConnection();
+        }
+    }
+
+    @Override
+    protected void moveToTrash(ExchangeSession.Message message) throws IOException {
+        String destination = URIUtil.encodePath(deleteditemsUrl) + '/' + UUID.randomUUID().toString();
+        LOGGER.debug("Deleting : " + message.permanentUrl + " to " + destination);
+        MoveMethod method = new MoveMethod(encodeAndFixUrl(message.permanentUrl), destination, false);
+        method.addRequestHeader("Allow-rename", "t");
+
+        int status = DavGatewayHttpClientFacade.executeHttpMethod(httpClient, method);
+        // do not throw error if already deleted
+        if (status != HttpStatus.SC_CREATED && status != HttpStatus.SC_NOT_FOUND) {
+            throw DavGatewayHttpClientFacade.buildHttpException(method);
+        }
+        if (method.getResponseHeader("Location") != null) {
+            destination = method.getResponseHeader("Location").getValue();
+        }
+
+        LOGGER.debug("Deleted to :" + destination);
+    }
+
+    protected String getItemProperty(String permanentUrl, String propertyName) throws IOException, DavException {
+        String result = null;
+        DavPropertyNameSet davPropertyNameSet = new DavPropertyNameSet();
+        davPropertyNameSet.add(Field.getPropertyName(propertyName));
+        PropFindMethod propFindMethod = new PropFindMethod(encodeAndFixUrl(permanentUrl), davPropertyNameSet, 0);
+        try {
+            try {
+                DavGatewayHttpClientFacade.executeHttpMethod(httpClient, propFindMethod);
+            } catch (UnknownHostException e) {
+                propFindMethod.releaseConnection();
+                // failover for misconfigured Exchange server, replace host name in url
+                restoreHostName = true;
+                propFindMethod = new PropFindMethod(encodeAndFixUrl(permanentUrl), davPropertyNameSet, 0);
+                DavGatewayHttpClientFacade.executeHttpMethod(httpClient, propFindMethod);
+            }
+
+            MultiStatus responses = propFindMethod.getResponseBodyAsMultiStatus();
+            if (responses.getResponses().length > 0) {
+                DavPropertySet properties = responses.getResponses()[0].getProperties(HttpStatus.SC_OK);
+                result = getPropertyIfExists(properties, propertyName);
+            }
+        } finally {
+            propFindMethod.releaseConnection();
+        }
+        return result;
+    }
+
+    protected String convertDateFromExchange(String exchangeDateValue) throws DavMailException {
+        String zuluDateValue = null;
+        if (exchangeDateValue != null) {
+            try {
+                zuluDateValue = getZuluDateFormat().format(getExchangeZuluDateFormatMillisecond().parse(exchangeDateValue));
+            } catch (ParseException e) {
+                throw new DavMailException("EXCEPTION_INVALID_DATE", exchangeDateValue);
+            }
+        }
+        return zuluDateValue;
+    }
+
+    protected static final Map<String, String> importanceToPriorityMap = new HashMap<String, String>();
+
+    static {
+        importanceToPriorityMap.put("high", "1");
+        importanceToPriorityMap.put("normal", "5");
+        importanceToPriorityMap.put("low", "9");
+    }
+
+    protected static final Map<String, String> priorityToImportanceMap = new HashMap<String, String>();
+
+    static {
+        priorityToImportanceMap.put("1", "high");
+        priorityToImportanceMap.put("5", "normal");
+        priorityToImportanceMap.put("9", "low");
+    }
+
+    protected String convertPriorityFromExchange(String exchangeImportanceValue) {
+        String value = null;
+        if (exchangeImportanceValue != null) {
+            value = importanceToPriorityMap.get(exchangeImportanceValue);
+        }
+        return value;
+    }
+
+    protected String convertPriorityToExchange(String vTodoPriorityValue) {
+        String value = null;
+        if (vTodoPriorityValue != null) {
+            value = priorityToImportanceMap.get(vTodoPriorityValue);
+        }
+        return value;
+    }
+
+
+    /**
+     * Format date to exchange search format.
+     *
+     * @param date date object
+     * @return formatted search date
+     */
+    @Override
+    public String formatSearchDate(Date date) {
+        SimpleDateFormat dateFormatter = new SimpleDateFormat(YYYY_MM_DD_HH_MM_SS, Locale.ENGLISH);
+        dateFormatter.setTimeZone(GMT_TIMEZONE);
+        return dateFormatter.format(date);
+    }
+
+    protected String convertTaskDateToZulu(String value) {
+        String result = null;
+        if (value != null && value.length() > 0) {
+            try {
+                SimpleDateFormat parser;
+                if (value.length() == 8) {
+                    parser = new SimpleDateFormat("yyyyMMdd", Locale.ENGLISH);
+                    parser.setTimeZone(GMT_TIMEZONE);
+                } else if (value.length() == 15) {
+                    parser = new SimpleDateFormat("yyyyMMdd'T'HHmmss", Locale.ENGLISH);
+                    parser.setTimeZone(GMT_TIMEZONE);
+                } else if (value.length() == 16) {
+                    parser = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.ENGLISH);
+                    parser.setTimeZone(GMT_TIMEZONE);
+                } else {
+                    parser = ExchangeSession.getExchangeZuluDateFormat();
+                }
+                Calendar calendarValue = Calendar.getInstance(GMT_TIMEZONE);
+                calendarValue.setTime(parser.parse(value));
+                // zulu time: add 12 hours
+                if (value.length() == 16) {
+                    calendarValue.add(Calendar.HOUR, 12);
+                }
+                calendarValue.set(Calendar.HOUR, 0);
+                calendarValue.set(Calendar.MINUTE, 0);
+                calendarValue.set(Calendar.SECOND, 0);
+                result = ExchangeSession.getExchangeZuluDateFormatMillisecond().format(calendarValue.getTime());
+            } catch (ParseException e) {
+                LOGGER.warn("Invalid date: " + value);
+            }
+        }
+
+        return result;
+    }
+
+    protected String convertDateFromExchangeToTaskDate(String exchangeDateValue) throws DavMailException {
+        String result = null;
+        if (exchangeDateValue != null) {
+            try {
+                SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd", Locale.ENGLISH);
+                dateFormat.setTimeZone(GMT_TIMEZONE);
+                result = dateFormat.format(getExchangeZuluDateFormatMillisecond().parse(exchangeDateValue));
+            } catch (ParseException e) {
+                throw new DavMailException("EXCEPTION_INVALID_DATE", exchangeDateValue);
+            }
+        }
+        return result;
+    }
+
+    protected Date parseDateFromExchange(String exchangeDateValue) throws DavMailException {
+        Date result = null;
+        if (exchangeDateValue != null) {
+            try {
+                result = getExchangeZuluDateFormatMillisecond().parse(exchangeDateValue);
+            } catch (ParseException e) {
+                throw new DavMailException("EXCEPTION_INVALID_DATE", exchangeDateValue);
+            }
+        }
+        return result;
+    }
+}
diff --git a/src/java/davmail/exchange/dav/ExchangeDavMethod.java b/src/java/davmail/exchange/dav/ExchangeDavMethod.java
new file mode 100644
index 0000000..757a703
--- /dev/null
+++ b/src/java/davmail/exchange/dav/ExchangeDavMethod.java
@@ -0,0 +1,246 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2012  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.dav;
+
+import davmail.exchange.XMLStreamUtil;
+import org.apache.commons.httpclient.*;
+import org.apache.commons.httpclient.methods.PostMethod;
+import org.apache.commons.httpclient.methods.RequestEntity;
+import org.apache.jackrabbit.webdav.MultiStatusResponse;
+import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
+import org.apache.jackrabbit.webdav.xml.Namespace;
+import org.apache.log4j.Logger;
+
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * New stax based implementation to replace DOM based jackrabbit version an support Exchange only extensions.
+ */
+public abstract class ExchangeDavMethod extends PostMethod {
+    protected static final Logger LOGGER = Logger.getLogger(ExchangeDavMethod.class);
+    List<MultiStatusResponse> responses;
+
+    /**
+     * Create PROPPATCH method.
+     *
+     * @param path           path
+     */
+    public ExchangeDavMethod(String path) {
+        super(path);
+        setRequestEntity(new RequestEntity() {
+            byte[] content;
+
+            public boolean isRepeatable() {
+                return true;
+            }
+
+            public void writeRequest(OutputStream outputStream) throws IOException {
+                if (content == null) {
+                    content = generateRequestContent();
+                }
+                outputStream.write(content);
+            }
+
+            public long getContentLength() {
+                if (content == null) {
+                    content = generateRequestContent();
+                }
+                return content.length;
+            }
+
+            public String getContentType() {
+                return "text/xml;charset=UTF-8";
+            }
+        });
+    }
+
+    /**
+     * Generate request content from property values.
+     *
+     * @return request content as byte array
+     */
+    protected abstract byte[] generateRequestContent();
+
+    @Override
+    protected void processResponseBody(HttpState httpState, HttpConnection httpConnection) {
+        Header contentTypeHeader = getResponseHeader("Content-Type");
+        if (contentTypeHeader != null && "text/xml".equals(contentTypeHeader.getValue())) {
+            responses = new ArrayList<MultiStatusResponse>();
+            XMLStreamReader reader;
+            try {
+                reader = XMLStreamUtil.createXMLStreamReader(new FilterInputStream(getResponseBodyAsStream()) {
+                    final byte[] lastbytes = new byte[3];
+
+                    @Override
+                    public int read(byte[] bytes, int off, int len) throws IOException {
+                        int count = in.read(bytes, off, len);
+                        // patch invalid element name
+                        for (int i = 0; i < count; i++) {
+                            byte currentByte = bytes[off + i];
+                            if ((lastbytes[0] == '<') && (currentByte >= '0' && currentByte <= '9')) {
+                                // move invalid first tag char to valid range
+                                bytes[off + i] = (byte) (currentByte + 49);
+                            }
+                            lastbytes[0] = lastbytes[1];
+                            lastbytes[1] = lastbytes[2];
+                            lastbytes[2] = currentByte;
+                        }
+                        return count;
+                    }
+
+                });
+                while (reader.hasNext()) {
+                    reader.next();
+                    if (XMLStreamUtil.isStartTag(reader, "response")) {
+                        handleResponse(reader);
+                    }
+                }
+
+            } catch (IOException e) {
+                LOGGER.error("Error while parsing soap response: " + e, e);
+            } catch (XMLStreamException e) {
+                LOGGER.error("Error while parsing soap response: " + e, e);
+            }
+        }
+    }
+
+    protected void handleResponse(XMLStreamReader reader) throws XMLStreamException {
+        MultiStatusResponse multiStatusResponse = null;
+        String href = null;
+        String responseStatus = "";
+        while (reader.hasNext() && !XMLStreamUtil.isEndTag(reader, "response")) {
+            reader.next();
+            if (XMLStreamUtil.isStartTag(reader)) {
+                String tagLocalName = reader.getLocalName();
+                if ("href".equals(tagLocalName)) {
+                    href = reader.getElementText();
+                } else if ("status".equals(tagLocalName)) {
+                    responseStatus = reader.getElementText();
+                } else if ("propstat".equals(tagLocalName)) {
+                     if (multiStatusResponse == null) {
+                         multiStatusResponse = new MultiStatusResponse(href, responseStatus);
+                     }
+                     handlePropstat(reader, multiStatusResponse);
+                }
+            }
+        }
+        if (multiStatusResponse != null) {
+            responses.add(multiStatusResponse);
+        }
+    }
+
+    protected void handlePropstat(XMLStreamReader reader, MultiStatusResponse multiStatusResponse) throws XMLStreamException {
+        int propstatStatus = 0;
+        while (reader.hasNext() && !XMLStreamUtil.isEndTag(reader, "propstat")) {
+            reader.next();
+            if (XMLStreamUtil.isStartTag(reader)) {
+                String tagLocalName = reader.getLocalName();
+                if ("status".equals(tagLocalName)) {
+                    if ("HTTP/1.1 200 OK".equals(reader.getElementText())) {
+                        propstatStatus = HttpStatus.SC_OK;
+                    } else {
+                        propstatStatus = 0;
+                    }
+                } else if ("prop".equals(tagLocalName) && propstatStatus == HttpStatus.SC_OK) {
+                    handleProperty(reader, multiStatusResponse);
+                }
+            }
+        }
+
+    }
+
+    protected void handleProperty(XMLStreamReader reader, MultiStatusResponse multiStatusResponse) throws XMLStreamException {
+        while (reader.hasNext() && !XMLStreamUtil.isEndTag(reader, "prop")) {
+            reader.next();
+            if (XMLStreamUtil.isStartTag(reader)) {
+                Namespace namespace = Namespace.getNamespace(reader.getNamespaceURI());
+                String tagLocalName = reader.getLocalName();
+                String tagContent = getTagContent(reader);
+                if (tagContent != null) {
+                    multiStatusResponse.add(new DefaultDavProperty(tagLocalName, tagContent, namespace));
+                }
+            }
+        }
+    }
+
+    protected String getTagContent(XMLStreamReader reader) throws XMLStreamException {
+        String value = null;
+        String tagLocalName = reader.getLocalName();
+        while (reader.hasNext() &&
+                !((reader.getEventType() == XMLStreamConstants.END_ELEMENT) && tagLocalName.equals(reader.getLocalName()))) {
+            reader.next();
+            if (reader.getEventType() == XMLStreamConstants.CHARACTERS) {
+                value = reader.getText();
+            }
+        }
+        // empty tag
+        if (!reader.hasNext()) {
+            throw new XMLStreamException("End element for " + tagLocalName + " not found");
+        }
+        return value;
+    }
+
+    /**
+     * Get Multistatus responses.
+     *
+     * @return responses
+     * @throws HttpException on error
+     */
+    public MultiStatusResponse[] getResponses() throws HttpException {
+        if (responses == null) {
+            throw new HttpException(getStatusLine().toString());
+        }
+        return responses.toArray(new MultiStatusResponse[responses.size()]);
+    }
+
+    /**
+     * Get single Multistatus response.
+     *
+     * @return response
+     * @throws HttpException on error
+     */
+    public MultiStatusResponse getResponse() throws HttpException {
+        if (responses == null || responses.size() != 1) {
+            throw new HttpException(getStatusLine().toString());
+        }
+        return responses.get(0);
+    }
+
+    /**
+     * Return method http status code.
+     *
+     * @return http status code
+     * @throws HttpException on error
+     */
+    public int getResponseStatusCode() throws HttpException {
+        String responseDescription = getResponse().getResponseDescription();
+        if ("HTTP/1.1 201 Created".equals(responseDescription)) {
+            return HttpStatus.SC_CREATED;
+        } else {
+            return HttpStatus.SC_OK;
+        }
+    }
+}
diff --git a/src/java/davmail/exchange/dav/ExchangePropFindMethod.java b/src/java/davmail/exchange/dav/ExchangePropFindMethod.java
new file mode 100644
index 0000000..9526d1e
--- /dev/null
+++ b/src/java/davmail/exchange/dav/ExchangePropFindMethod.java
@@ -0,0 +1,114 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2011  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.dav;
+
+import org.apache.jackrabbit.webdav.header.DepthHeader;
+import org.apache.jackrabbit.webdav.property.DavPropertyName;
+import org.apache.jackrabbit.webdav.property.DavPropertyNameIterator;
+import org.apache.jackrabbit.webdav.property.DavPropertyNameSet;
+import org.apache.log4j.Logger;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Custom Exchange PROPFIND method.
+ * Does not load full DOM in memory.
+ */
+public class ExchangePropFindMethod extends ExchangeDavMethod {
+    protected static final Logger LOGGER = Logger.getLogger(ExchangePropFindMethod.class);
+
+    protected final DavPropertyNameSet propertyNameSet;
+
+    public ExchangePropFindMethod(String uri) {
+        this(uri, null, DepthHeader.DEPTH_INFINITY);
+    }
+
+    public ExchangePropFindMethod(String uri, DavPropertyNameSet propertyNameSet, int depth) {
+        super(uri);
+        this.propertyNameSet = propertyNameSet;
+        DepthHeader dh = new DepthHeader(depth);
+        setRequestHeader(dh.getHeaderName(), dh.getHeaderValue());
+    }
+
+    protected byte[] generateRequestContent() {
+        try {
+            // build namespace map
+            int currentChar = 'e';
+            final Map<String, Integer> nameSpaceMap = new HashMap<String, Integer>();
+            nameSpaceMap.put("DAV:", (int) 'D');
+            if (propertyNameSet != null) {
+                DavPropertyNameIterator propertyNameIterator = propertyNameSet.iterator();
+                while (propertyNameIterator.hasNext()) {
+                    DavPropertyName davPropertyName = propertyNameIterator.nextPropertyName();
+
+                    davPropertyName.getName();
+                    // property namespace
+                    String namespaceUri = davPropertyName.getNamespace().getURI();
+                    if (!nameSpaceMap.containsKey(namespaceUri)) {
+                        nameSpaceMap.put(namespaceUri, currentChar++);
+                    }
+                }
+            }
+            // <D:propfind xmlns:D="DAV:"><D:prop><D:displayname/></D:prop></D:propfind>
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            OutputStreamWriter writer = new OutputStreamWriter(baos, "UTF-8");
+            writer.write("<D:propfind ");
+            for (Map.Entry<String, Integer> mapEntry : nameSpaceMap.entrySet()) {
+                writer.write(" xmlns:");
+                writer.write((char) mapEntry.getValue().intValue());
+                writer.write("=\"");
+                writer.write(mapEntry.getKey());
+                writer.write("\"");
+            }
+            writer.write(">");
+            if (propertyNameSet == null || propertyNameSet.isEmpty()) {
+                writer.write("<D:allprop/>");
+            } else {
+                writer.write("<D:prop>");
+                DavPropertyNameIterator propertyNameIterator = propertyNameSet.iterator();
+                while (propertyNameIterator.hasNext()) {
+                    DavPropertyName davPropertyName = propertyNameIterator.nextPropertyName();
+                    char nameSpaceChar = (char) nameSpaceMap.get(davPropertyName.getNamespace().getURI()).intValue();
+                    writer.write('<');
+                    writer.write(nameSpaceChar);
+                    writer.write(':');
+                    writer.write(davPropertyName.getName());
+                    writer.write("/>");
+                }
+                writer.write("</D:prop>");
+            }
+            writer.write("</D:propfind>");
+            writer.close();
+            return baos.toByteArray();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+
+    }
+
+    @Override
+    public String getName() {
+        return "PROPFIND";
+    }
+
+}
diff --git a/src/java/davmail/exchange/dav/ExchangePropPatchMethod.java b/src/java/davmail/exchange/dav/ExchangePropPatchMethod.java
new file mode 100644
index 0000000..6f749e8
--- /dev/null
+++ b/src/java/davmail/exchange/dav/ExchangePropPatchMethod.java
@@ -0,0 +1,137 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.dav;
+
+import org.apache.log4j.Logger;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Custom Exchange PROPPATCH method.
+ * Supports extended property update with type.
+ */
+public class ExchangePropPatchMethod extends ExchangeDavMethod {
+    protected static final Logger LOGGER = Logger.getLogger(ExchangePropPatchMethod.class);
+    static final String TYPE_NAMESPACE = "urn:schemas-microsoft-com:datatypes";
+    final Set<PropertyValue> propertyValues;
+
+    /**
+     * Create PROPPATCH method.
+     *
+     * @param path           path
+     * @param propertyValues property values
+     */
+    public ExchangePropPatchMethod(String path, Set<PropertyValue> propertyValues) {
+        super(path);
+        this.propertyValues = propertyValues;
+    }
+
+    @Override
+    protected byte[] generateRequestContent() {
+        try {
+            // build namespace map
+            int currentChar = 'e';
+            final Map<String, Integer> nameSpaceMap = new HashMap<String, Integer>();
+            final Set<PropertyValue> setPropertyValues = new HashSet<PropertyValue>();
+            final Set<PropertyValue> deletePropertyValues = new HashSet<PropertyValue>();
+            for (PropertyValue propertyValue : propertyValues) {
+                // data type namespace
+                if (!nameSpaceMap.containsKey(TYPE_NAMESPACE) && propertyValue.getTypeString() != null) {
+                    nameSpaceMap.put(TYPE_NAMESPACE, currentChar++);
+                }
+                // property namespace
+                String namespaceUri = propertyValue.getNamespaceUri();
+                if (!nameSpaceMap.containsKey(namespaceUri)) {
+                    nameSpaceMap.put(namespaceUri, currentChar++);
+                }
+                if (propertyValue.getXmlEncodedValue() == null) {
+                    deletePropertyValues.add(propertyValue);
+                } else {
+                    setPropertyValues.add(propertyValue);
+                }
+            }
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            OutputStreamWriter writer = new OutputStreamWriter(baos, "UTF-8");
+            writer.write("<D:propertyupdate xmlns:D=\"DAV:\"");
+            for (Map.Entry<String, Integer> mapEntry : nameSpaceMap.entrySet()) {
+                writer.write(" xmlns:");
+                writer.write((char) mapEntry.getValue().intValue());
+                writer.write("=\"");
+                writer.write(mapEntry.getKey());
+                writer.write("\"");
+            }
+            writer.write(">");
+            if (!setPropertyValues.isEmpty()) {
+                writer.write("<D:set><D:prop>");
+                for (PropertyValue propertyValue : setPropertyValues) {
+                    String typeString = propertyValue.getTypeString();
+                    char nameSpaceChar = (char) nameSpaceMap.get(propertyValue.getNamespaceUri()).intValue();
+                    writer.write('<');
+                    writer.write(nameSpaceChar);
+                    writer.write(':');
+                    writer.write(propertyValue.getName());
+                    if (typeString != null) {
+                        writer.write(' ');
+                        writer.write(nameSpaceMap.get(TYPE_NAMESPACE));
+                        writer.write(":dt=\"");
+                        writer.write(typeString);
+                        writer.write("\"");
+                    }
+                    writer.write('>');
+                    writer.write(propertyValue.getXmlEncodedValue());
+                    writer.write("</");
+                    writer.write(nameSpaceChar);
+                    writer.write(':');
+                    writer.write(propertyValue.getName());
+                    writer.write('>');
+                }
+                writer.write("</D:prop></D:set>");
+            }
+            if (!deletePropertyValues.isEmpty()) {
+                writer.write("<D:remove><D:prop>");
+                for (PropertyValue propertyValue : deletePropertyValues) {
+                    char nameSpaceChar = (char) nameSpaceMap.get(propertyValue.getNamespaceUri()).intValue();
+                    writer.write('<');
+                    writer.write(nameSpaceChar);
+                    writer.write(':');
+                    writer.write(propertyValue.getName());
+                    writer.write("/>");
+                }
+                writer.write("</D:prop></D:remove>");
+            }
+            writer.write("</D:propertyupdate>");
+            writer.close();
+            return baos.toByteArray();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public String getName() {
+        return "PROPPATCH";
+    }
+
+}
diff --git a/src/java/davmail/exchange/dav/ExchangeSearchMethod.java b/src/java/davmail/exchange/dav/ExchangeSearchMethod.java
new file mode 100644
index 0000000..eeb2ee7
--- /dev/null
+++ b/src/java/davmail/exchange/dav/ExchangeSearchMethod.java
@@ -0,0 +1,72 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2012  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.dav;
+
+import davmail.util.StringUtil;
+import org.apache.log4j.Logger;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+
+/**
+ * Custom Exchange PROPFIND method.
+ * Does not load full DOM in memory.
+ */
+public class ExchangeSearchMethod extends ExchangeDavMethod {
+    protected static final Logger LOGGER = Logger.getLogger(ExchangeSearchMethod.class);
+
+    protected final String searchRequest;
+
+    /**
+     * Create search method.
+     *
+     * @param uri method uri
+     * @param searchRequest Exchange search request
+     */
+    public ExchangeSearchMethod(String uri, String searchRequest) {
+        super(uri);
+        this.searchRequest = searchRequest;
+    }
+
+    protected byte[] generateRequestContent() {
+        try {
+
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            OutputStreamWriter writer = new OutputStreamWriter(baos, "UTF-8");
+            writer.write("<?xml version=\"1.0\"?>\n");
+            writer.write("<d:searchrequest xmlns:d=\"DAV:\">\n");
+            writer.write("        <d:sql>");
+            writer.write(StringUtil.xmlEncode(searchRequest));
+            writer.write("</d:sql>\n");
+            writer.write("</d:searchrequest>");
+            writer.close();
+            return baos.toByteArray();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+
+    }
+
+    @Override
+    public String getName() {
+        return "SEARCH";
+    }
+
+}
diff --git a/src/java/davmail/exchange/dav/Field.java b/src/java/davmail/exchange/dav/Field.java
new file mode 100644
index 0000000..fabfe59
--- /dev/null
+++ b/src/java/davmail/exchange/dav/Field.java
@@ -0,0 +1,635 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.dav;
+
+import davmail.util.StringUtil;
+import org.apache.jackrabbit.webdav.DavConstants;
+import org.apache.jackrabbit.webdav.property.DavPropertyName;
+import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
+import org.apache.jackrabbit.webdav.xml.DomUtil;
+import org.apache.jackrabbit.webdav.xml.Namespace;
+import org.apache.jackrabbit.webdav.xml.XmlSerializable;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * WebDav Field
+ */
+public class Field {
+
+    protected static final Map<DistinguishedPropertySetType, String> distinguishedPropertySetMap = new HashMap<DistinguishedPropertySetType, String>();
+
+    static {
+        distinguishedPropertySetMap.put(DistinguishedPropertySetType.Meeting, "6ed8da90-450b-101b-98da-00aa003f1305");
+        distinguishedPropertySetMap.put(DistinguishedPropertySetType.Appointment, "00062002-0000-0000-c000-000000000046");
+        distinguishedPropertySetMap.put(DistinguishedPropertySetType.Common, "00062008-0000-0000-c000-000000000046");
+        distinguishedPropertySetMap.put(DistinguishedPropertySetType.PublicStrings, "00020329-0000-0000-c000-000000000046");
+        distinguishedPropertySetMap.put(DistinguishedPropertySetType.Address, "00062004-0000-0000-c000-000000000046");
+        distinguishedPropertySetMap.put(DistinguishedPropertySetType.InternetHeaders, "00020386-0000-0000-c000-000000000046");
+        distinguishedPropertySetMap.put(DistinguishedPropertySetType.UnifiedMessaging, "4442858e-a9e3-4e80-b900-317a210cc15b");
+        distinguishedPropertySetMap.put(DistinguishedPropertySetType.Task, "00062003-0000-0000-c000-000000000046");
+    }
+
+    protected static final Namespace EMPTY = Namespace.getNamespace("");
+    protected static final Namespace XML = Namespace.getNamespace("xml:");
+    protected static final Namespace DAV = Namespace.getNamespace("DAV:");
+    protected static final Namespace URN_SCHEMAS_HTTPMAIL = Namespace.getNamespace("urn:schemas:httpmail:");
+    protected static final Namespace URN_SCHEMAS_MAILHEADER = Namespace.getNamespace("urn:schemas:mailheader:");
+
+    protected static final Namespace SCHEMAS_EXCHANGE = Namespace.getNamespace("http://schemas.microsoft.com/exchange/");
+    protected static final Namespace SCHEMAS_MAPI = Namespace.getNamespace("http://schemas.microsoft.com/mapi/");
+    protected static final Namespace SCHEMAS_MAPI_PROPTAG = Namespace.getNamespace("http://schemas.microsoft.com/mapi/proptag/");
+    protected static final Namespace SCHEMAS_MAPI_ID = Namespace.getNamespace("http://schemas.microsoft.com/mapi/id/");
+    protected static final Namespace SCHEMAS_MAPI_STRING = Namespace.getNamespace("http://schemas.microsoft.com/mapi/string/");
+    protected static final Namespace SCHEMAS_REPL = Namespace.getNamespace("http://schemas.microsoft.com/repl/");
+    protected static final Namespace URN_SCHEMAS_CONTACTS = Namespace.getNamespace("urn:schemas:contacts:");
+    protected static final Namespace URN_SCHEMAS_CALENDAR = Namespace.getNamespace("urn:schemas:calendar:");
+
+    protected static final Namespace SCHEMAS_MAPI_STRING_INTERNET_HEADERS =
+            Namespace.getNamespace(SCHEMAS_MAPI_STRING.getURI() +
+                    '{' + distinguishedPropertySetMap.get(DistinguishedPropertySetType.InternetHeaders) + "}/");
+
+    protected static final Map<PropertyType, String> propertyTypeMap = new HashMap<PropertyType, String>();
+
+    static {
+        propertyTypeMap.put(PropertyType.Integer, "0003"); // PT_INT
+        propertyTypeMap.put(PropertyType.Boolean, "000b"); // PT_BOOLEAN
+        propertyTypeMap.put(PropertyType.SystemTime, "0040"); // PT_SYSTIME
+        propertyTypeMap.put(PropertyType.String, "001f"); // 001f is PT_UNICODE_STRING, 001E is PT_STRING
+        propertyTypeMap.put(PropertyType.Binary, "0102"); // PT_BINARY
+        propertyTypeMap.put(PropertyType.Double, "0005"); // PT_DOUBLE
+    }
+
+    @SuppressWarnings({"UnusedDeclaration"})
+    protected static enum DistinguishedPropertySetType {
+        Meeting, Appointment, Common, PublicStrings, Address, InternetHeaders, CalendarAssistant, UnifiedMessaging, Task
+    }
+
+
+    protected static final Map<String, Field> fieldMap = new HashMap<String, Field>();
+
+    static {
+        // well known folders
+        createField(URN_SCHEMAS_HTTPMAIL, "inbox");
+        createField(URN_SCHEMAS_HTTPMAIL, "deleteditems");
+        createField(URN_SCHEMAS_HTTPMAIL, "sentitems");
+        createField(URN_SCHEMAS_HTTPMAIL, "sendmsg");
+        createField(URN_SCHEMAS_HTTPMAIL, "drafts");
+        createField(URN_SCHEMAS_HTTPMAIL, "calendar");
+        createField(URN_SCHEMAS_HTTPMAIL, "tasks");
+        createField(URN_SCHEMAS_HTTPMAIL, "contacts");
+        createField(URN_SCHEMAS_HTTPMAIL, "outbox");
+
+
+        // folder
+        createField("folderclass", SCHEMAS_EXCHANGE, "outlookfolderclass");
+        createField(DAV, "hassubs");
+        createField(DAV, "nosubs");
+        createField(URN_SCHEMAS_HTTPMAIL, "unreadcount");
+        createField(SCHEMAS_REPL, "contenttag");
+
+        createField("uidNext", 0x6751, PropertyType.Integer);// PR_ARTICLE_NUM_NEXT
+        createField("highestUid", 0x6752, PropertyType.Integer);// PR_IMAP_LAST_ARTICLE_ID
+
+        createField(DAV, "isfolder");
+
+        // item uid, do not use as search parameter, see http://support.microsoft.com/kb/320749
+        createField(DAV, "uid"); // based on PR_RECORD_KEY
+
+        // POP and IMAP message
+        createField("messageSize", 0x0e08, PropertyType.Integer);//PR_MESSAGE_SIZE
+        createField("imapUid", 0x0e23, PropertyType.Integer);//PR_INTERNET_ARTICLE_NUMBER
+        createField("junk", 0x1083, PropertyType.Integer);
+        createField("flagStatus", 0x1090, PropertyType.Integer);//PR_FLAG_STATUS
+        createField("messageFlags", 0x0e07, PropertyType.Integer);//PR_MESSAGE_FLAGS
+        createField("lastVerbExecuted", 0x1081, PropertyType.Integer);//PR_LAST_VERB_EXECUTED
+        createField("iconIndex", 0x1080, PropertyType.Integer);//PR_ICON_INDEX
+        createField(URN_SCHEMAS_HTTPMAIL, "read");
+        //createField("read", 0x0e69, PropertyType.Boolean);//PR_READ
+        createField("deleted", DistinguishedPropertySetType.Common, 0x8570, "deleted", PropertyType.String);
+
+        //createField(URN_SCHEMAS_HTTPMAIL, "date");//PR_CLIENT_SUBMIT_TIME, 0x0039
+        createField("date", 0x0e06, PropertyType.SystemTime);//PR_MESSAGE_DELIVERY_TIME
+        createField(URN_SCHEMAS_MAILHEADER, "bcc");//PS_INTERNET_HEADERS/bcc
+        createField(URN_SCHEMAS_HTTPMAIL, "datereceived");//PR_MESSAGE_DELIVERY_TIME, 0x0E06
+
+        // unused: force message encoding
+        createField("messageFormat", 0x5909, PropertyType.Integer);//PR_MSG_EDITOR_FORMAT EDITOR_FORMAT_PLAINTEXT = 1 EDITOR_FORMAT_HTML = 2
+        createField("mailOverrideFormat", 0x5902, PropertyType.Integer);//PR_INETMAIL_OVERRIDE_FORMAT ENCODING_PREFERENCE = 2 BODY_ENCODING_TEXT_AND_HTML = 1 ENCODING_MIME = 4
+
+        // IMAP search
+
+        createField(URN_SCHEMAS_HTTPMAIL, "subject"); // DistinguishedPropertySetType.InternetHeaders/Subject/String
+        //createField("subject", 0x0037, PropertyType.String);//PR_SUBJECT
+        createField("body", 0x1000, PropertyType.String);//PR_BODY
+        createField("messageheaders", 0x007D, PropertyType.String);// PR_TRANSPORT_MESSAGE_HEADERS
+        createField(URN_SCHEMAS_HTTPMAIL, "from");
+        //createField("from", DistinguishedPropertySetType.PublicStrings, 0x001f);//urn:schemas:httpmail:from
+        createField(URN_SCHEMAS_MAILHEADER, "to"); // DistinguishedPropertySetType.InternetHeaders/To/String
+        createField(URN_SCHEMAS_MAILHEADER, "cc"); // DistinguishedPropertySetType.InternetHeaders/To/String
+        createField(URN_SCHEMAS_MAILHEADER, "message-id"); // DistinguishedPropertySetType.InternetHeaders/message-id/String
+        createField(URN_SCHEMAS_MAILHEADER, "htmldescription"); // DistinguishedPropertySetType.InternetHeaders/htmldescription/String
+
+        createField("lastmodified", DAV, "getlastmodified"); // PR_LAST_MODIFICATION_TIME 0x3008 SystemTime
+
+        // failover search
+        createField(DAV, "displayname");
+        createField("urlcompname", 0x10f3, PropertyType.String); //PR_URL_COMP_NAME
+
+        // items
+        createField("etag", DAV, "getetag");
+
+        // calendar
+        createField(SCHEMAS_EXCHANGE, "permanenturl");
+        createField(URN_SCHEMAS_CALENDAR, "instancetype"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:instancetype/Integer
+        createField(URN_SCHEMAS_CALENDAR, "dtstart"); // 0x10C3 SystemTime
+        createField(URN_SCHEMAS_CALENDAR, "dtend"); // 0x10C4 SystemTime
+
+        //createField(URN_SCHEMAS_CALENDAR, "prodid"); //  // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:prodid/String
+        createField("calendarversion", URN_SCHEMAS_CALENDAR, "version"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:version/String
+        createField(URN_SCHEMAS_CALENDAR, "method"); //  // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:method/String
+
+        createField("calendarlastmodified", URN_SCHEMAS_CALENDAR, "lastmodified"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:isorganizer/Boolean
+        createField(URN_SCHEMAS_CALENDAR, "dtstamp"); // PidLidOwnerCriticalChange
+        createField("calendaruid", URN_SCHEMAS_CALENDAR, "uid"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:uid/String
+        createField(URN_SCHEMAS_CALENDAR, "transparent"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:transparent/String
+
+        createField(URN_SCHEMAS_CALENDAR, "organizer");
+        createField(URN_SCHEMAS_CALENDAR, "created"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:created/SystemTime
+        createField(URN_SCHEMAS_CALENDAR, "alldayevent"); // DistinguishedPropertySetType.Appointment/0x8215 Boolean
+
+        createField(URN_SCHEMAS_CALENDAR, "rrule"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:rrule/PtypMultipleString
+        createField(URN_SCHEMAS_CALENDAR, "exdate"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:exdate/PtypMultipleTime
+
+        createField(SCHEMAS_MAPI, "reminderset"); // PidLidReminderSet
+        createField(SCHEMAS_MAPI, "reminderdelta"); // PidLidReminderDelta         
+
+        // TODO
+        createField(SCHEMAS_MAPI, "allattendeesstring"); // PidLidAllAttendeesString
+        createField(SCHEMAS_MAPI, "required_attendees"); // PidLidRequiredAttendees
+        createField(SCHEMAS_MAPI, "apptendtime"); // PidLidAppointmentEndTime
+        createField(SCHEMAS_MAPI, "apptstateflags"); // PidLidAppointmentStateFlags 1: Meeting, 2: Received, 4: Cancelled
+
+        createField(URN_SCHEMAS_CALENDAR, "isorganizer"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:isorganizer/Boolean
+        createField(URN_SCHEMAS_CALENDAR, "location"); // DistinguishedPropertySetType.Appointment/0x8208 String
+        createField(URN_SCHEMAS_CALENDAR, "attendeerole"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:attendeerole/Integer
+        createField(URN_SCHEMAS_CALENDAR, "busystatus"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:busystatus/String
+        createField(URN_SCHEMAS_CALENDAR, "exrule"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:exrule/PtypMultipleString
+        createField(URN_SCHEMAS_CALENDAR, "recurrenceidrange"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:recurrenceidrange/String
+        createField(URN_SCHEMAS_CALENDAR, "rdate"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:rdate/PtypMultipleTime        
+        createField(URN_SCHEMAS_CALENDAR, "reminderoffset"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:reminderoffset/Integer
+        createField(URN_SCHEMAS_CALENDAR, "timezone"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:timezone/String
+
+
+        createField(SCHEMAS_EXCHANGE, "sensitivity"); // PR_SENSITIVITY 0x0036 Integer
+        createField(URN_SCHEMAS_CALENDAR, "timezoneid"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:timezoneid/Integer
+        // should use PidLidServerProcessed ?
+        createField("processed", 0x65e8, PropertyType.Boolean);// PR_MESSAGE_PROCESSED
+
+        createField(DAV, "contentclass");
+        createField("internetContent", 0x6659, PropertyType.Binary);
+
+        // contact
+
+        createField(SCHEMAS_EXCHANGE, "outlookmessageclass");
+        createField(URN_SCHEMAS_HTTPMAIL, "subject");
+
+        createField(URN_SCHEMAS_CONTACTS, "middlename"); // PR_MIDDLE_NAME 0x3A44
+        createField(URN_SCHEMAS_CONTACTS, "fileas"); // urn:schemas:contacts:fileas PS_PUBLIC_STRINGS
+
+        //createField("id", 0x0ff6, PropertyType.Binary); // PR_INSTANCE_KEY http://support.microsoft.com/kb/320749
+
+        createField(URN_SCHEMAS_CONTACTS, "homepostaladdress"); // homeAddress DistinguishedPropertySetType.Address/0x0000801A/String
+        createField(URN_SCHEMAS_CONTACTS, "otherpostaladdress"); // otherAddress DistinguishedPropertySetType.Address/0x0000801C/String
+        createField(URN_SCHEMAS_CONTACTS, "mailingaddressid"); // postalAddressId DistinguishedPropertySetType.Address/0x00008022/String
+        createField(URN_SCHEMAS_CONTACTS, "workaddress"); // workAddress DistinguishedPropertySetType.Address/0x0000801B/String
+
+        createField(URN_SCHEMAS_CONTACTS, "alternaterecipient"); // alternaterecipient DistinguishedPropertySetType.PublicStrings/urn:schemas:contacts:alternaterecipient/String
+
+        createField(SCHEMAS_EXCHANGE, "extensionattribute1"); // DistinguishedPropertySetType.Address/0x0000804F/String
+        createField(SCHEMAS_EXCHANGE, "extensionattribute2"); // DistinguishedPropertySetType.Address/0x00008050/String
+        createField(SCHEMAS_EXCHANGE, "extensionattribute3"); // DistinguishedPropertySetType.Address/0x00008051/String
+        createField(SCHEMAS_EXCHANGE, "extensionattribute4"); // DistinguishedPropertySetType.Address/0x00008052/String
+
+        createField(URN_SCHEMAS_CONTACTS, "bday"); // PR_BIRTHDAY 0x3A42 SystemTime
+        createField("anniversary", URN_SCHEMAS_CONTACTS, "weddinganniversary"); // WeddingAnniversary
+        createField(URN_SCHEMAS_CONTACTS, "businesshomepage"); // PR_BUSINESS_HOME_PAGE 0x3A51 String
+        createField(URN_SCHEMAS_CONTACTS, "personalHomePage"); // PR_PERSONAL_HOME_PAGE 0x3A50 String
+        createField(URN_SCHEMAS_CONTACTS, "cn"); // PR_DISPLAY_NAME 0x3001 String
+        createField(URN_SCHEMAS_CONTACTS, "co"); // workAddressCountry DistinguishedPropertySetType.Address/0x00008049/String
+        createField(URN_SCHEMAS_CONTACTS, "department"); // PR_DEPARTMENT_NAME 0x3A18 String
+
+        // smtp email
+        createField("smtpemail1", DistinguishedPropertySetType.Address, 0x8084, "smtpemail1"); // Email1OriginalDisplayName
+        createField("smtpemail2", DistinguishedPropertySetType.Address, 0x8094, "smtpemail2"); // Email2OriginalDisplayName
+        createField("smtpemail3", DistinguishedPropertySetType.Address, 0x80A4, "smtpemail3"); // Email3OriginalDisplayName
+
+        // native email
+        createField("email1", DistinguishedPropertySetType.Address, 0x8083, "email1"); // Email1EmailAddress
+        createField("email2", DistinguishedPropertySetType.Address, 0x8093, "email2"); // Email2EmailAddress
+        createField("email3", DistinguishedPropertySetType.Address, 0x80A3, "email3"); // Email3EmailAddress
+
+        // email type
+        createField("email1type", DistinguishedPropertySetType.Address, 0x8082, "email1type"); // Email1AddressType
+        createField("email2type", DistinguishedPropertySetType.Address, 0x8092, "email2type"); // Email2AddressType
+        createField("email3type", DistinguishedPropertySetType.Address, 0x80A2, "email3type"); // Email3AddressType
+
+        createField(URN_SCHEMAS_CONTACTS, "facsimiletelephonenumber"); // PR_BUSINESS_FAX_NUMBER 0x3A24 String
+        createField(URN_SCHEMAS_CONTACTS, "givenName"); // PR_GIVEN_NAME 0x3A06 String
+        createField(URN_SCHEMAS_CONTACTS, "homepostofficebox"); // PR_HOME_ADDRESS_POST_OFFICE_BOX 0x3A5E String
+        createField(URN_SCHEMAS_CONTACTS, "homeCity"); // PR_HOME_ADDRESS_CITY 0x3A59 String
+        createField(URN_SCHEMAS_CONTACTS, "homeCountry"); // PR_HOME_ADDRESS_COUNTRY 0x3A5A String
+        createField(URN_SCHEMAS_CONTACTS, "homePhone"); // PR_HOME_TELEPHONE_NUMBER 0x3A09 String
+        createField(URN_SCHEMAS_CONTACTS, "homePostalCode"); // PR_HOME_ADDRESS_POSTAL_CODE 0x3A5B String
+        createField(URN_SCHEMAS_CONTACTS, "homeState"); // PR_HOME_ADDRESS_STATE_OR_PROVINCE 0x3A5C String
+        createField(URN_SCHEMAS_CONTACTS, "homeStreet"); // PR_HOME_ADDRESS_STREET 0x3A5D String
+        createField(URN_SCHEMAS_CONTACTS, "l"); // workAddressCity DistinguishedPropertySetType.Address/0x00008046/String
+        createField(URN_SCHEMAS_CONTACTS, "manager"); // PR_MANAGER_NAME 0x3A4E String
+        createField(URN_SCHEMAS_CONTACTS, "mobile"); // PR_MOBILE_TELEPHONE_NUMBER 0x3A1C String
+        createField(URN_SCHEMAS_CONTACTS, "namesuffix"); // PR_GENERATION 0x3A05 String
+        createField(URN_SCHEMAS_CONTACTS, "nickname"); // PR_NICKNAME 0x3A4F String
+        createField(URN_SCHEMAS_CONTACTS, "o"); // PR_COMPANY_NAME 0x3A16 String
+        createField(URN_SCHEMAS_CONTACTS, "pager"); // PR_PAGER_TELEPHONE_NUMBER 0x3A21 String
+        createField(URN_SCHEMAS_CONTACTS, "personaltitle"); // PR_DISPLAY_NAME_PREFIX 0x3A45 String
+        createField(URN_SCHEMAS_CONTACTS, "postalcode"); // workAddressPostalCode DistinguishedPropertySetType.Address/0x00008048/String
+        createField(URN_SCHEMAS_CONTACTS, "postofficebox"); // workAddressPostOfficeBox DistinguishedPropertySetType.Address/0x0000804A/String
+        createField(URN_SCHEMAS_CONTACTS, "profession"); // PR_PROFESSION 0x3A46 String
+        createField(URN_SCHEMAS_CONTACTS, "roomnumber"); // PR_OFFICE_LOCATION 0x3A19 String
+        createField(URN_SCHEMAS_CONTACTS, "secretarycn"); // PR_ASSISTANT 0x3A30 String
+        createField(URN_SCHEMAS_CONTACTS, "sn"); // PR_SURNAME 0x3A11 String
+        createField(URN_SCHEMAS_CONTACTS, "spousecn"); // PR_SPOUSE_NAME 0x3A48 String
+        createField(URN_SCHEMAS_CONTACTS, "st"); // workAddressState DistinguishedPropertySetType.Address/0x00008047/String
+        createField(URN_SCHEMAS_CONTACTS, "street"); // workAddressStreet DistinguishedPropertySetType.Address/0x00008045/String
+        createField(URN_SCHEMAS_CONTACTS, "telephoneNumber"); // PR_BUSINESS_TELEPHONE_NUMBER 0x3A08 String
+        createField(URN_SCHEMAS_CONTACTS, "title"); // PR_TITLE 0x3A17 String
+        createField("description", URN_SCHEMAS_HTTPMAIL, "textdescription"); // PR_BODY 0x1000 String
+        createField("im", SCHEMAS_MAPI, "InstMsg"); // InstantMessagingAddress DistinguishedPropertySetType.Address/0x00008062/String
+        createField(URN_SCHEMAS_CONTACTS, "othermobile"); // PR_CAR_TELEPHONE_NUMBER 0x3A1E String
+        createField(URN_SCHEMAS_CONTACTS, "internationalisdnnumber"); // PR_ISDN_NUMBER 0x3A2D String        
+
+        createField(URN_SCHEMAS_CONTACTS, "otherTelephone"); // PR_OTHER_TELEPHONE_NUMBER 0x3A21 String
+        createField(URN_SCHEMAS_CONTACTS, "homefax"); // PR_HOME_FAX_NUMBER 0x3A25 String
+
+        createField(URN_SCHEMAS_CONTACTS, "otherstreet"); // PR_OTHER_ADDRESS_STREET 0x3A63 String
+        createField(URN_SCHEMAS_CONTACTS, "otherstate"); // PR_OTHER_ADDRESS_STATE_OR_PROVINCE 0x3A62 String
+        createField(URN_SCHEMAS_CONTACTS, "otherpostofficebox"); // PR_OTHER_ADDRESS_POST_OFFICE_BOX 0x3A64 String
+        createField(URN_SCHEMAS_CONTACTS, "otherpostalcode"); // PR_OTHER_ADDRESS_POSTAL_CODE 0x3A61 String
+        createField(URN_SCHEMAS_CONTACTS, "othercountry"); // PR_OTHER_ADDRESS_COUNTRY 0x3A60 String
+        createField(URN_SCHEMAS_CONTACTS, "othercity"); // PR_OTHER_ADDRESS_CITY 0x3A5F String
+
+        createField(URN_SCHEMAS_CONTACTS, "gender"); // PR_GENDER 0x3A4D Integer16
+
+        createField("keywords", SCHEMAS_EXCHANGE, "keywords-utf8", PropertyType.StringArray); // PS_PUBLIC_STRINGS Keywords String
+        //createField("keywords", DistinguishedPropertySetType.PublicStrings, "Keywords", ); // PS_PUBLIC_STRINGS Keywords String
+
+        // contact private flags
+        createField("private", DistinguishedPropertySetType.Common, 0x8506, "private", PropertyType.Boolean); // True/False
+        createField("sensitivity", 0x0036, PropertyType.Integer); // PR_SENSITIVITY SENSITIVITY_PRIVATE = 2, SENSITIVITY_PERSONAL = 1, SENSITIVITY_NONE = 0
+
+        createField("haspicture", DistinguishedPropertySetType.Address, 0x8015, "haspicture", PropertyType.Boolean); // True/False
+
+        createField(URN_SCHEMAS_CALENDAR, "fburl"); // freeBusyLocation
+
+        // OWA settings
+        createField("messageclass", 0x001a, PropertyType.String);
+        createField("roamingxmlstream", 0x7c08, PropertyType.Binary);
+        createField("roamingdictionary", 0x7c07, PropertyType.Binary);
+
+        createField(DAV, "ishidden");
+
+        // attachment content
+        createField("attachDataBinary", 0x3701, PropertyType.Binary);
+
+        createField("attachmentContactPhoto", 0x7FFF, PropertyType.Boolean); // PR_ATTACHMENT_CONTACTPHOTO
+        createField("renderingPosition", 0x370B, PropertyType.Integer);// PR_RENDERING_POSITION
+        //createField("attachFilename", 0x3704, PropertyType.String); // PR_ATTACH_FILENAME
+        createField("attachExtension", 0x3703, PropertyType.String); // PR_ATTACH_EXTENSION
+
+        createField("xmozlastack", DistinguishedPropertySetType.PublicStrings);
+        createField("xmozsnoozetime", DistinguishedPropertySetType.PublicStrings);
+        createField("xmozsendinvitations", DistinguishedPropertySetType.PublicStrings);
+
+        // task
+        createField(URN_SCHEMAS_MAILHEADER, "importance");//PS_INTERNET_HEADERS/importance
+        createField("percentcomplete", DistinguishedPropertySetType.Task, 0x8102, "percentcomplete", PropertyType.Double);
+        createField("taskstatus", DistinguishedPropertySetType.Task, 0x8101, "taskstatus", PropertyType.Integer);
+        createField("startdate", DistinguishedPropertySetType.Task, 0x8104, "startdate", PropertyType.SystemTime);
+        createField("duedate", DistinguishedPropertySetType.Task, 0x8105, "duedate", PropertyType.SystemTime);
+        createField("datecompleted", DistinguishedPropertySetType.Task, 0x810F, "datecompleted", PropertyType.SystemTime);
+        createField("iscomplete", DistinguishedPropertySetType.Task, 0x811C, "iscomplete", PropertyType.Boolean);
+
+
+        createField("commonstart", DistinguishedPropertySetType.Common, 0x8516, "commonstart", PropertyType.SystemTime);
+        createField("commonend", DistinguishedPropertySetType.Common, 0x8517, "commonend", PropertyType.SystemTime);
+    }
+
+    protected static String toHexString(int propertyTag) {
+        StringBuilder hexValue = new StringBuilder(Integer.toHexString(propertyTag));
+        while (hexValue.length() < 4) {
+            hexValue.insert(0, '0');
+        }
+        return hexValue.toString();
+    }
+
+    protected static void createField(String alias, int propertyTag, PropertyType propertyType) {
+        String name = 'x' + toHexString(propertyTag) + propertyTypeMap.get(propertyType);
+        Field field;
+        if (propertyType == PropertyType.Binary) {
+            field = new Field(alias, SCHEMAS_MAPI_PROPTAG, name, propertyType, null, "bin.base64", name);
+        } else {
+            field = new Field(alias, SCHEMAS_MAPI_PROPTAG, name, propertyType);
+        }
+        fieldMap.put(field.alias, field);
+    }
+
+    protected static void createField(String alias, DistinguishedPropertySetType propertySetType) {
+        Field field = new Field(Namespace.getNamespace(SCHEMAS_MAPI_STRING.getURI() +
+                '{' + distinguishedPropertySetMap.get(propertySetType) + "}/"), alias);
+        fieldMap.put(field.alias, field);
+    }
+
+    protected static void createField(String alias, DistinguishedPropertySetType propertySetType, int propertyTag, String responseAlias) {
+        createField(alias, propertySetType, propertyTag, responseAlias, null);
+    }
+
+    protected static void createField(String alias, DistinguishedPropertySetType propertySetType, int propertyTag, String responseAlias, PropertyType propertyType) {
+        String name;
+        String updateAlias;
+        if (propertySetType == DistinguishedPropertySetType.Address) {
+            // Address namespace expects integer names
+            name = String.valueOf(propertyTag);
+            updateAlias = "_x0030_x" + toHexString(propertyTag);
+        } else if (propertySetType == DistinguishedPropertySetType.Task) {
+            name = "0x" + toHexString(propertyTag);
+            updateAlias = "0x0000" + toHexString(propertyTag);
+        } else {
+            // Common namespace expects hex names
+            name = "0x" + toHexString(propertyTag);
+            updateAlias = "_x0030_x" + toHexString(propertyTag);
+        }
+        Field field = new Field(alias, Namespace.getNamespace(SCHEMAS_MAPI_ID.getURI() +
+                '{' + distinguishedPropertySetMap.get(propertySetType) + "}/"), name, propertyType, responseAlias, null, updateAlias);
+        fieldMap.put(field.alias, field);
+    }
+
+    protected static void createField(Namespace namespace, String name) {
+        Field field = new Field(namespace, name);
+        fieldMap.put(field.alias, field);
+    }
+
+    protected static void createField(String alias, Namespace namespace, String name) {
+        Field field = new Field(alias, namespace, name, null);
+        fieldMap.put(field.alias, field);
+    }
+
+    protected static void createField(String alias, Namespace namespace, String name, PropertyType propertyType) {
+        Field field = new Field(alias, namespace, name, propertyType);
+        fieldMap.put(field.alias, field);
+    }
+
+    private final DavPropertyName davPropertyName;
+    protected final String alias;
+    protected final String uri;
+    protected final String requestPropertyString;
+    protected final DavPropertyName responsePropertyName;
+    protected final DavPropertyName updatePropertyName;
+    protected final String cast;
+    protected final boolean isIntValue;
+    protected final boolean isMultivalued;
+    protected final boolean isBooleanValue;
+    protected final boolean isFloatValue;
+    protected final boolean isDateValue;
+
+    /**
+     * Create field for namespace and name, use name as alias.
+     *
+     * @param namespace Exchange namespace
+     * @param name      Exchange name
+     */
+    protected Field(Namespace namespace, String name) {
+        this(name, namespace, name, null);
+    }
+
+    /**
+     * Create field for namespace and name of type propertyType.
+     *
+     * @param alias        logical name in DavMail
+     * @param namespace    Exchange namespace
+     * @param name         Exchange name
+     * @param propertyType property type
+     */
+    protected Field(String alias, Namespace namespace, String name, PropertyType propertyType) {
+        this(alias, namespace, name, propertyType, null, null, name);
+    }
+
+    /**
+     * Create field for namespace and name of type propertyType.
+     *
+     * @param alias         logical name in DavMail
+     * @param namespace     Exchange namespace
+     * @param name          Exchange name
+     * @param propertyType  property type
+     * @param responseAlias property name in SEARCH response (as responsealias in request)
+     * @param cast          response cast type (e.g. bin.base64)
+     * @param updateAlias   some properties use a different alias in PROPPATCH requests
+     */
+    protected Field(String alias, Namespace namespace, String name, PropertyType propertyType, String responseAlias, String cast, String updateAlias) {
+        this.alias = alias;
+
+        // property name in PROPFIND requests
+        davPropertyName = DavPropertyName.create(name, namespace);
+        // property name in PROPPATCH requests
+        updatePropertyName = DavPropertyName.create(updateAlias, namespace);
+
+        // a few type based flags
+        isMultivalued = propertyType != null && propertyType.toString().endsWith("Array");
+        isIntValue = propertyType == PropertyType.Long || propertyType == PropertyType.Integer || propertyType == PropertyType.Short;
+        isBooleanValue = propertyType == PropertyType.Boolean;
+        isFloatValue = propertyType == PropertyType.Float || propertyType == PropertyType.Double;
+        isDateValue = propertyType == PropertyType.SystemTime;
+
+        this.uri = namespace.getURI() + name;
+        if (responseAlias == null) {
+            this.requestPropertyString = '"' + uri + '"';
+            this.responsePropertyName = davPropertyName;
+        } else {
+            this.requestPropertyString = '"' + uri + "\" as " + responseAlias;
+            this.responsePropertyName = DavPropertyName.create(responseAlias, EMPTY);
+        }
+        this.cast = cast;
+    }
+
+    /**
+     * Property uri.
+     *
+     * @return uri
+     */
+    public String getUri() {
+        return uri;
+    }
+
+    /**
+     * Integer value property type.
+     *
+     * @return true if the field value is integer
+     */
+    public boolean isIntValue() {
+        return isIntValue;
+    }
+
+    /**
+     * Get Field by alias.
+     *
+     * @param alias field alias
+     * @return field
+     */
+    public static Field get(String alias) {
+        Field field = fieldMap.get(alias);
+        if (field == null) {
+            throw new IllegalArgumentException("Unknown field: " + alias);
+        }
+        return field;
+    }
+
+    /**
+     * Get Mime header field.
+     *
+     * @param headerName header name
+     * @return field object
+     */
+    public static Field getHeader(String headerName) {
+        return new Field(SCHEMAS_MAPI_STRING_INTERNET_HEADERS, headerName);
+    }
+
+    /**
+     * Create DavProperty object for field alias and value.
+     *
+     * @param alias DavMail field alias
+     * @param value field value
+     * @return DavProperty with value or DavPropertyName for null values
+     */
+    public static DavConstants createDavProperty(String alias, String value) {
+        Field field = Field.get(alias);
+        if (value == null) {
+            // return DavPropertyName to remove property
+            return field.updatePropertyName;
+        } else if (field.isMultivalued) {
+            // multivalued field, split values separated by \n
+            List<XmlSerializable> valueList = new ArrayList<XmlSerializable>();
+            String[] values = value.split("\n");
+            for (final String singleValue : values) {
+                valueList.add(new XmlSerializable() {
+                    public Element toXml(Document document) {
+                        return DomUtil.createElement(document, "v", XML, singleValue);
+                    }
+                });
+            }
+
+            return new DefaultDavProperty(field.updatePropertyName, valueList);
+        } else if (field.isBooleanValue && !"haspicture".equals(alias)) {
+            if ("true".equals(value)) {
+                return new DefaultDavProperty(field.updatePropertyName, "1");
+            } else if ("false".equals(value)) {
+                return new DefaultDavProperty(field.updatePropertyName, "0");
+            } else {
+                throw new RuntimeException("Invalid value for " + field.alias + ": " + value);
+            }
+        } else {
+            return new DefaultDavProperty(field.updatePropertyName, value);
+        }
+    }
+
+    /**
+     * Create property value object for field and value.
+     *
+     * @param alias field alias
+     * @param value field value
+     * @return property value object
+     * @see ExchangePropPatchMethod
+     */
+    public static PropertyValue createPropertyValue(String alias, String value) {
+        Field field = Field.get(alias);
+        DavPropertyName davPropertyName = field.davPropertyName;
+        if (value == null) {
+            // return DavPropertyName to remove property
+            return new PropertyValue(davPropertyName.getNamespace().getURI(), davPropertyName.getName());
+        } else if (field.isMultivalued) {
+            StringBuilder buffer = new StringBuilder();
+            // multivalued field, split values separated by \n
+            String[] values = value.split("\n");
+            for (final String singleValue : values) {
+                buffer.append("<v>");
+                buffer.append(StringUtil.xmlEncode(singleValue));
+                buffer.append("</v>");
+            }
+            return new PropertyValue(davPropertyName.getNamespace().getURI(), davPropertyName.getName(), buffer.toString());
+        } else if (field.isBooleanValue) {
+            if ("true".equals(value)) {
+                return new PropertyValue(davPropertyName.getNamespace().getURI(), davPropertyName.getName(), "1", "boolean");
+            } else if ("false".equals(value)) {
+                return new PropertyValue(davPropertyName.getNamespace().getURI(), davPropertyName.getName(), "0", "boolean");
+            } else {
+                throw new RuntimeException("Invalid value for " + field.alias + ": " + value);
+            }
+        } else if (field.isFloatValue) {
+            return new PropertyValue(davPropertyName.getNamespace().getURI(), davPropertyName.getName(), StringUtil.xmlEncode(value), "float");
+        } else if (field.isIntValue) {
+            return new PropertyValue(field.updatePropertyName.getNamespace().getURI(), field.updatePropertyName.getName(), StringUtil.xmlEncode(value), "int");
+        } else if (field.isDateValue) {
+            return new PropertyValue(field.updatePropertyName.getNamespace().getURI(), field.updatePropertyName.getName(), StringUtil.xmlEncode(value), "dateTime.tz");
+        } else {
+            return new PropertyValue(davPropertyName.getNamespace().getURI(), davPropertyName.getName(), StringUtil.xmlEncode(value));
+        }
+    }
+
+    /**
+     * SEARCH request property name for alias
+     *
+     * @param alias field alias
+     * @return request property string
+     */
+    public static String getRequestPropertyString(String alias) {
+        return Field.get(alias).requestPropertyString;
+    }
+
+    /**
+     * PROPFIND request property name
+     *
+     * @param alias field alias
+     * @return request property name
+     */
+    public static DavPropertyName getPropertyName(String alias) {
+        return Field.get(alias).davPropertyName;
+    }
+
+    /**
+     * SEARCH response property name
+     *
+     * @param alias field alias
+     * @return response property name
+     */
+    public static DavPropertyName getResponsePropertyName(String alias) {
+        return Field.get(alias).responsePropertyName;
+    }
+}
diff --git a/src/java/davmail/exchange/dav/PropertyType.java b/src/java/davmail/exchange/dav/PropertyType.java
new file mode 100644
index 0000000..cc493fe
--- /dev/null
+++ b/src/java/davmail/exchange/dav/PropertyType.java
@@ -0,0 +1,29 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.dav;
+
+/**
+ * MAPI property types.
+ */
+ at SuppressWarnings({"UnusedDeclaration", "JavaDoc"})
+public enum PropertyType {
+    ApplicationTime, ApplicationTimeArray, Binary, BinaryArray, Boolean, CLSID, CLSIDArray, Currency, CurrencyArray,
+    Double, DoubleArray, Error, Float, FloatArray, Integer, IntegerArray, Long, LongArray, Null, Object,
+    ObjectArray, Short, ShortArray, SystemTime, SystemTimeArray, String, StringArray
+}
diff --git a/src/java/davmail/exchange/dav/PropertyValue.java b/src/java/davmail/exchange/dav/PropertyValue.java
new file mode 100644
index 0000000..2462258
--- /dev/null
+++ b/src/java/davmail/exchange/dav/PropertyValue.java
@@ -0,0 +1,101 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.dav;
+
+/**
+ * Property value.
+ */
+public class PropertyValue {
+    protected final String namespaceUri;
+    protected final String name;
+    protected final String xmlEncodedValue;
+    protected final String typeString;
+
+    /**
+     * Create Dav property value.
+     *
+     * @param namespaceUri property namespace
+     * @param name         property name
+     */
+    public PropertyValue(String namespaceUri, String name) {
+        this(namespaceUri, name, null, null);
+    }
+
+    /**
+     * Create Dav property value.
+     *
+     * @param namespaceUri    property namespace
+     * @param name            property name
+     * @param xmlEncodedValue xml encoded value
+     */
+    public PropertyValue(String namespaceUri, String name, String xmlEncodedValue) {
+        this(namespaceUri, name, xmlEncodedValue, null);
+    }
+
+    /**
+     * Create Dav property value.
+     *
+     * @param namespaceUri    property namespace
+     * @param name            property name
+     * @param xmlEncodedValue xml encoded value
+     * @param typeString            property type
+     */
+    public PropertyValue(String namespaceUri, String name, String xmlEncodedValue, String typeString) {
+        this.namespaceUri = namespaceUri;
+        this.name = name;
+        this.xmlEncodedValue = xmlEncodedValue;
+        this.typeString = typeString;
+    }
+
+    /**
+     * Get property namespace.
+     *
+     * @return property namespace
+     */
+    public String getNamespaceUri() {
+        return namespaceUri;
+    }
+
+    /**
+     * Get xml encoded value.
+     *
+     * @return Xml encoded value
+     */
+    public String getXmlEncodedValue() {
+        return xmlEncodedValue;
+    }
+
+    /**
+     * Get property type.
+     *
+     * @return property type
+     */
+    public String getTypeString() {
+        return typeString;
+    }
+
+    /**
+     * Get property name.
+     *
+     * @return property name
+     */
+    public String getName() {
+        return name;
+    }
+}
diff --git a/src/java/davmail/exchange/ews/AffectedTaskOccurrences.java b/src/java/davmail/exchange/ews/AffectedTaskOccurrences.java
new file mode 100644
index 0000000..546d931
--- /dev/null
+++ b/src/java/davmail/exchange/ews/AffectedTaskOccurrences.java
@@ -0,0 +1,32 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2011  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+/**
+ * Item delete option.
+ */
+ at SuppressWarnings({"JavaDoc", "UnusedDeclaration"})
+public final class AffectedTaskOccurrences extends AttributeOption {
+    private AffectedTaskOccurrences(String value) {
+        super("AffectedTaskOccurrences", value);
+    }
+
+    public static final AffectedTaskOccurrences AllOccurrences = new AffectedTaskOccurrences("AllOccurrences");
+    public static final AffectedTaskOccurrences SpecifiedOccurrenceOnly = new AffectedTaskOccurrences("SpecifiedOccurrenceOnly");
+}
diff --git a/src/java/davmail/exchange/ews/AttributeOption.java b/src/java/davmail/exchange/ews/AttributeOption.java
new file mode 100644
index 0000000..d168e8b
--- /dev/null
+++ b/src/java/davmail/exchange/ews/AttributeOption.java
@@ -0,0 +1,51 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Generic attribute option.
+ */
+public class AttributeOption extends Option {
+
+    protected AttributeOption(String name, String value) {
+        super(name, value);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public void appendTo(StringBuilder buffer) {
+        buffer.append(' ').append(name).append("=\"").append(value).append('"');
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    public void write(Writer writer) throws IOException {
+        writer.write(" ");
+        writer.write(name);
+        writer.write("=\"");
+        writer.write(value);
+        writer.write("\"");
+    }
+}
diff --git a/src/java/davmail/exchange/ews/BaseShape.java b/src/java/davmail/exchange/ews/BaseShape.java
new file mode 100644
index 0000000..8e5757c
--- /dev/null
+++ b/src/java/davmail/exchange/ews/BaseShape.java
@@ -0,0 +1,42 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+/**
+ * Item or folder base shape.
+ */
+ at SuppressWarnings({"UnusedDeclaration"})
+public final class BaseShape extends ElementOption {
+    private BaseShape(String value) {
+        super("t:BaseShape", value);
+    }
+
+    /**
+     * Return id only.
+     */
+    public static final BaseShape ID_ONLY = new BaseShape("IdOnly");
+    /**
+     * Return default properties.
+     */
+    public static final BaseShape DEFAULT = new BaseShape("Default");
+    /**
+     * Return all properties, except MAPI extended properties.
+     */
+    public static final BaseShape ALL_PROPERTIES = new BaseShape("AllProperties");
+}
\ No newline at end of file
diff --git a/src/java/davmail/exchange/ews/ConflictResolution.java b/src/java/davmail/exchange/ews/ConflictResolution.java
new file mode 100644
index 0000000..fd217f8
--- /dev/null
+++ b/src/java/davmail/exchange/ews/ConflictResolution.java
@@ -0,0 +1,33 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+/**
+ * Item update conflict resolution
+ */
+ at SuppressWarnings({"JavaDoc", "UnusedDeclaration"})
+public final class ConflictResolution extends AttributeOption {
+    private ConflictResolution(String value) {
+        super("ConflictResolution", value);
+    }
+
+    public static final ConflictResolution NeverOverwrite = new ConflictResolution("NeverOverwrite");
+    public static final ConflictResolution AutoResolve = new ConflictResolution("AutoResolve");
+    public static final ConflictResolution AlwaysOverwrite = new ConflictResolution("AlwaysOverwrite");
+}
diff --git a/src/java/davmail/exchange/ews/ContainmentComparison.java b/src/java/davmail/exchange/ews/ContainmentComparison.java
new file mode 100644
index 0000000..fb7bc36
--- /dev/null
+++ b/src/java/davmail/exchange/ews/ContainmentComparison.java
@@ -0,0 +1,39 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+/**
+ * Contains comparison mode.
+ */
+ at SuppressWarnings({"UnusedDeclaration", "JavaDoc"})
+public final class ContainmentComparison extends AttributeOption {
+    private ContainmentComparison(String value) {
+        super("ContainmentComparison", value);
+    }
+
+    public static final ContainmentComparison Exact = new ContainmentComparison("Exact");
+    public static final ContainmentComparison IgnoreCase = new ContainmentComparison("IgnoreCase");
+    public static final ContainmentComparison IgnoreNonSpacingCharacters = new ContainmentComparison("IgnoreNonSpacingCharacters");
+    public static final ContainmentComparison Loose = new ContainmentComparison("Loose");
+    public static final ContainmentComparison IgnoreCaseAndNonSpacingCharacters = new ContainmentComparison("IgnoreCaseAndNonSpacingCharacters");
+    public static final ContainmentComparison LooseAndIgnoreCase = new ContainmentComparison("LooseAndIgnoreCase");
+    public static final ContainmentComparison LooseAndIgnoreNonSpace = new ContainmentComparison("LooseAndIgnoreNonSpace");
+    public static final ContainmentComparison LooseAndIgnoreCaseAndIgnoreNonSpace = new ContainmentComparison("LooseAndIgnoreCaseAndIgnoreNonSpace");
+
+}
diff --git a/src/java/davmail/exchange/ews/ContainmentMode.java b/src/java/davmail/exchange/ews/ContainmentMode.java
new file mode 100644
index 0000000..af16832
--- /dev/null
+++ b/src/java/davmail/exchange/ews/ContainmentMode.java
@@ -0,0 +1,49 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+/**
+ * Contains search mode.
+ */
+ at SuppressWarnings({"UnusedDeclaration", "JavaDoc"})
+public final class ContainmentMode extends AttributeOption {
+    private ContainmentMode(String value) {
+        super("ContainmentMode", value);
+    }
+
+    @Override
+    public String toString() {
+        return value;
+    }
+
+    /**
+     * Full String.
+     */
+    public static final ContainmentMode FullString = new ContainmentMode("FullString");
+    /**
+     * Starts with.
+     */
+    public static final ContainmentMode Prefixed = new ContainmentMode("Prefixed");
+    /**
+     * Contains
+     */
+    public static final ContainmentMode Substring = new ContainmentMode("Substring");
+    public static final ContainmentMode PrefixOnWords = new ContainmentMode("PrefixOnWords");
+    public static final ContainmentMode ExactPhrase = new ContainmentMode("ExactPhrase");
+}
diff --git a/src/java/davmail/exchange/ews/CopyItemMethod.java b/src/java/davmail/exchange/ews/CopyItemMethod.java
new file mode 100644
index 0000000..1d0d7f8
--- /dev/null
+++ b/src/java/davmail/exchange/ews/CopyItemMethod.java
@@ -0,0 +1,36 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+/**
+ * Copy item to another folder.
+ */
+public class CopyItemMethod extends EWSMethod {
+    /**
+     * Copy item method.
+     *
+     * @param itemId     item id
+     * @param toFolderId target folder id
+     */
+    public CopyItemMethod(ItemId itemId, FolderId toFolderId) {
+        super("Item", "CopyItem");
+        this.itemId = itemId;
+        this.toFolderId = toFolderId;
+    }
+}
diff --git a/src/java/davmail/exchange/ews/CreateAttachmentMethod.java b/src/java/davmail/exchange/ews/CreateAttachmentMethod.java
new file mode 100644
index 0000000..31aa891
--- /dev/null
+++ b/src/java/davmail/exchange/ews/CreateAttachmentMethod.java
@@ -0,0 +1,36 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+/**
+ * Create Attachment Method.
+ */
+public class CreateAttachmentMethod extends EWSMethod {
+    /**
+     * Create attachment method.
+     *
+     * @param parentItemId parent item id
+     * @param attachment   attachment object
+     */
+    public CreateAttachmentMethod(ItemId parentItemId, FileAttachment attachment) {
+        super("Item", "CreateAttachment");
+        this.parentItemId = parentItemId;
+        this.attachment = attachment;
+    }
+}
diff --git a/src/java/davmail/exchange/ews/CreateFolderMethod.java b/src/java/davmail/exchange/ews/CreateFolderMethod.java
new file mode 100644
index 0000000..4ea01d8
--- /dev/null
+++ b/src/java/davmail/exchange/ews/CreateFolderMethod.java
@@ -0,0 +1,36 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+/**
+ * Create Folder method.
+ */
+public class CreateFolderMethod extends EWSMethod {
+    /**
+     * Update folder method.
+     *
+     * @param parentFolderId parent folder id
+     * @param item           folder item
+     */
+    public CreateFolderMethod(FolderId parentFolderId, Item item) {
+        super("Folder", "CreateFolder");
+        this.parentFolderId = parentFolderId;
+        this.item = item;
+    }
+}
diff --git a/src/java/davmail/exchange/ews/CreateItemMethod.java b/src/java/davmail/exchange/ews/CreateItemMethod.java
new file mode 100644
index 0000000..d486fd0
--- /dev/null
+++ b/src/java/davmail/exchange/ews/CreateItemMethod.java
@@ -0,0 +1,55 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+/**
+ * Create Item method.
+ */
+public class CreateItemMethod extends EWSMethod {
+    /**
+     * Create exchange item.
+     *
+     * @param messageDisposition save or send option
+     * @param savedItemFolderId  saved item folder id
+     * @param item               item content
+     */
+    public CreateItemMethod(MessageDisposition messageDisposition, FolderId savedItemFolderId, EWSMethod.Item item) {
+        super("Item", "CreateItem");
+        this.savedItemFolderId = savedItemFolderId;
+        this.item = item;
+        addMethodOption(messageDisposition);
+    }
+
+    /**
+     * Create exchange item.
+     *
+     * @param messageDisposition     save or send option
+     * @param sendMeetingInvitations send invitation option
+     * @param savedItemFolderId      saved item folder id
+     * @param item                   item content
+     */
+    public CreateItemMethod(MessageDisposition messageDisposition, SendMeetingInvitations sendMeetingInvitations, FolderId savedItemFolderId, EWSMethod.Item item) {
+        super("Item", "CreateItem");
+        this.savedItemFolderId = savedItemFolderId;
+        this.item = item;
+        addMethodOption(messageDisposition);
+        addMethodOption(sendMeetingInvitations);
+    }
+
+}
diff --git a/src/java/davmail/exchange/ews/DeleteAttachmentMethod.java b/src/java/davmail/exchange/ews/DeleteAttachmentMethod.java
new file mode 100644
index 0000000..6bab757
--- /dev/null
+++ b/src/java/davmail/exchange/ews/DeleteAttachmentMethod.java
@@ -0,0 +1,34 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+/**
+ * Delete attachment method.
+ */
+public class DeleteAttachmentMethod extends EWSMethod {
+    /**
+     * Delete attachment method.
+     *
+     * @param attachmentId attachment id
+     */
+    public DeleteAttachmentMethod(String attachmentId) {
+        super("Item", "DeleteAttachment");
+        this.attachmentId = attachmentId;
+    }
+}
diff --git a/src/java/davmail/exchange/ews/DeleteFolderMethod.java b/src/java/davmail/exchange/ews/DeleteFolderMethod.java
new file mode 100644
index 0000000..7207653
--- /dev/null
+++ b/src/java/davmail/exchange/ews/DeleteFolderMethod.java
@@ -0,0 +1,35 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+/**
+ * Delete Folder method.
+ */
+public class DeleteFolderMethod extends EWSMethod {
+    /**
+     * Delete folder method.
+     *
+     * @param folderId folder id
+     */
+    public DeleteFolderMethod(FolderId folderId) {
+        super("Folder", "DeleteFolder");
+        this.folderId = folderId;
+        this.deleteType = Disposal.HardDelete;
+    }
+}
diff --git a/src/java/davmail/exchange/ews/DeleteItemMethod.java b/src/java/davmail/exchange/ews/DeleteItemMethod.java
new file mode 100644
index 0000000..71bfe09
--- /dev/null
+++ b/src/java/davmail/exchange/ews/DeleteItemMethod.java
@@ -0,0 +1,40 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+/**
+ * Delete Item method.
+ */
+public class DeleteItemMethod extends EWSMethod {
+    /**
+     * Delete item method.
+     *
+     * @param itemId                   item id
+     * @param deleteType               delete mode
+     * @param sendMeetingCancellations send meeting cancellation notifications
+     */
+    public DeleteItemMethod(ItemId itemId, DeleteType deleteType, SendMeetingCancellations sendMeetingCancellations) {
+        super("Item", "DeleteItem");
+        addMethodOption(deleteType);
+        addMethodOption(sendMeetingCancellations);
+        addMethodOption(AffectedTaskOccurrences.AllOccurrences);
+        this.itemId = itemId;
+    }
+
+}
diff --git a/src/java/davmail/exchange/ews/DeleteType.java b/src/java/davmail/exchange/ews/DeleteType.java
new file mode 100644
index 0000000..0b54317
--- /dev/null
+++ b/src/java/davmail/exchange/ews/DeleteType.java
@@ -0,0 +1,33 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+/**
+ * DeleteItem disposal type.
+ */
+ at SuppressWarnings({"JavaDoc", "UnusedDeclaration"})
+public final class DeleteType extends AttributeOption {
+    private DeleteType(String value) {
+        super("DeleteType", value);
+    }
+
+    public static final DeleteType HardDelete = new DeleteType("HardDelete");
+    public static final DeleteType SoftDelete = new DeleteType("SoftDelete");
+    public static final DeleteType MoveToDeletedItems = new DeleteType("MoveToDeletedItems");
+}
diff --git a/src/java/davmail/exchange/ews/Disposal.java b/src/java/davmail/exchange/ews/Disposal.java
new file mode 100644
index 0000000..c359311
--- /dev/null
+++ b/src/java/davmail/exchange/ews/Disposal.java
@@ -0,0 +1,33 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+/**
+ * Disposal.
+ */
+ at SuppressWarnings({"JavaDoc", "UnusedDeclaration"})
+public final class Disposal extends AttributeOption {
+    private Disposal(String value) {
+        super("DeleteType", value);
+    }
+
+    public static final Disposal HardDelete = new Disposal("HardDelete");
+    public static final Disposal SoftDelete = new Disposal("SoftDelete");
+    public static final Disposal MoveToDeletedItems = new Disposal("MoveToDeletedItems");
+}
diff --git a/src/java/davmail/exchange/ews/DistinguishedFolderId.java b/src/java/davmail/exchange/ews/DistinguishedFolderId.java
new file mode 100644
index 0000000..0267425
--- /dev/null
+++ b/src/java/davmail/exchange/ews/DistinguishedFolderId.java
@@ -0,0 +1,70 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Distinguished Folder Id.
+ */
+public final class DistinguishedFolderId extends FolderId {
+
+    private DistinguishedFolderId(String value) {
+        super("t:DistinguishedFolderId", value, null);
+    }
+
+    private DistinguishedFolderId(String value, String mailbox) {
+        super("t:DistinguishedFolderId", value, null, mailbox);
+    }
+
+    /**
+     * DistinguishedFolderId names
+     */
+    @SuppressWarnings({"UnusedDeclaration", "JavaDoc"})
+    public static enum Name {
+        calendar, contacts, deleteditems, drafts, inbox, journal, notes, outbox, sentitems, tasks, msgfolderroot,
+        publicfoldersroot, root, junkemail, searchfolders, voicemail,
+        archivemsgfolderroot
+    }
+
+    private static final Map<Name, DistinguishedFolderId> folderIdMap = new HashMap<Name, DistinguishedFolderId>();
+
+    static {
+        for (Name name : Name.values()) {
+            folderIdMap.put(name, new DistinguishedFolderId(name.toString()));
+        }
+    }
+
+    /**
+     * Get DistinguishedFolderId object for mailbox and name.
+     *
+     * @param mailbox mailbox name
+     * @param name    folder id name
+     * @return DistinguishedFolderId object
+     */
+    public static DistinguishedFolderId getInstance(String mailbox, Name name) {
+        if (mailbox == null) {
+            return folderIdMap.get(name);
+        } else {
+            return new DistinguishedFolderId(name.toString(), mailbox);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/java/davmail/exchange/ews/EWSException.java b/src/java/davmail/exchange/ews/EWSException.java
new file mode 100644
index 0000000..edad327
--- /dev/null
+++ b/src/java/davmail/exchange/ews/EWSException.java
@@ -0,0 +1,35 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+import java.io.IOException;
+
+/**
+ * EWS Exception
+ */
+public class EWSException extends IOException {
+    /**
+     * Create EWS Exception with detailed error message
+     *
+     * @param message error message
+     */
+    public EWSException(String message) {
+        super(message);
+    }
+}
diff --git a/src/java/davmail/exchange/ews/EWSMethod.java b/src/java/davmail/exchange/ews/EWSMethod.java
new file mode 100644
index 0000000..914853a
--- /dev/null
+++ b/src/java/davmail/exchange/ews/EWSMethod.java
@@ -0,0 +1,1118 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+import davmail.BundleMessage;
+import davmail.Settings;
+import davmail.exchange.XMLStreamUtil;
+import davmail.http.DavGatewayHttpClientFacade;
+import davmail.ui.tray.DavGatewayTray;
+import davmail.util.StringUtil;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.httpclient.*;
+import org.apache.commons.httpclient.methods.PostMethod;
+import org.apache.commons.httpclient.methods.RequestEntity;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.codehaus.stax2.typed.TypedXMLStreamReader;
+
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+import java.io.*;
+import java.util.*;
+import java.util.zip.GZIPInputStream;
+
+/**
+ * EWS SOAP method.
+ */
+ at SuppressWarnings("Since15")
+public abstract class EWSMethod extends PostMethod {
+    protected static final Logger LOGGER = Logger.getLogger(EWSMethod.class);
+
+    protected FolderQueryTraversal traversal;
+    protected BaseShape baseShape;
+    protected boolean includeMimeContent;
+    protected FolderId folderId;
+    protected FolderId savedItemFolderId;
+    protected FolderId toFolderId;
+    protected FolderId parentFolderId;
+    protected ItemId itemId;
+    protected ItemId parentItemId;
+    protected Set<FieldURI> additionalProperties;
+    protected Disposal deleteType;
+    protected Set<AttributeOption> methodOptions;
+    protected ElementOption unresolvedEntry;
+
+    // paging request
+    protected int maxCount;
+    protected int offset;
+    // paging response
+    protected boolean includesLastItemInRange;
+
+    protected List<FieldUpdate> updates;
+
+    protected FileAttachment attachment;
+
+    protected String attachmentId;
+
+    protected final String itemType;
+    protected final String methodName;
+    protected final String responseCollectionName;
+
+    protected List<Item> responseItems;
+    protected String errorDetail;
+    protected String errorDescription;
+    protected Item item;
+
+    protected SearchExpression searchExpression;
+
+    protected String serverVersion;
+    protected String timezoneContext;
+
+    /**
+     * Build EWS method
+     *
+     * @param itemType   item type
+     * @param methodName method name
+     */
+    public EWSMethod(String itemType, String methodName) {
+        this(itemType, methodName, itemType + 's');
+    }
+
+    /**
+     * Build EWS method
+     *
+     * @param itemType               item type
+     * @param methodName             method name
+     * @param responseCollectionName item response collection name
+     */
+    public EWSMethod(String itemType, String methodName, String responseCollectionName) {
+        super("/ews/exchange.asmx");
+        this.itemType = itemType;
+        this.methodName = methodName;
+        this.responseCollectionName = responseCollectionName;
+        if (Settings.getBooleanProperty("davmail.acceptEncodingGzip", true) &&
+                !Level.DEBUG.toString().equals(Settings.getBooleanProperty("log4j.logger.httpclient.wire"))) {
+            setRequestHeader("Accept-Encoding", "gzip");
+        }
+        setRequestEntity(new RequestEntity() {
+            byte[] content;
+
+            public boolean isRepeatable() {
+                return true;
+            }
+
+            public void writeRequest(OutputStream outputStream) throws IOException {
+                if (content == null) {
+                    content = generateSoapEnvelope();
+                }
+                outputStream.write(content);
+            }
+
+            public long getContentLength() {
+                if (content == null) {
+                    content = generateSoapEnvelope();
+                }
+                return content.length;
+            }
+
+            public String getContentType() {
+                return "text/xml; charset=UTF-8";
+            }
+        });
+    }
+
+
+    @Override
+    public String getName() {
+        return "POST";
+    }
+
+    protected void addAdditionalProperty(FieldURI additionalProperty) {
+        if (additionalProperties == null) {
+            additionalProperties = new HashSet<FieldURI>();
+        }
+        additionalProperties.add(additionalProperty);
+    }
+
+    protected void addMethodOption(AttributeOption attributeOption) {
+        if (methodOptions == null) {
+            methodOptions = new HashSet<AttributeOption>();
+        }
+        methodOptions.add(attributeOption);
+    }
+
+    protected void setSearchExpression(SearchExpression searchExpression) {
+        this.searchExpression = searchExpression;
+    }
+
+    protected void writeShape(Writer writer) throws IOException {
+        if (baseShape != null) {
+            writer.write("<m:");
+            writer.write(itemType);
+            writer.write("Shape>");
+            baseShape.write(writer);
+            if (includeMimeContent) {
+                writer.write("<t:IncludeMimeContent>true</t:IncludeMimeContent>");
+            }
+            if (additionalProperties != null) {
+                writer.write("<t:AdditionalProperties>");
+                StringBuilder buffer = new StringBuilder();
+                for (FieldURI fieldURI : additionalProperties) {
+                    fieldURI.appendTo(buffer);
+                }
+                writer.write(buffer.toString());
+                writer.write("</t:AdditionalProperties>");
+            }
+            writer.write("</m:");
+            writer.write(itemType);
+            writer.write("Shape>");
+        }
+    }
+
+    protected void writeItemId(Writer writer) throws IOException {
+        if (itemId != null) {
+            if (updates == null) {
+                writer.write("<m:ItemIds>");
+            }
+            itemId.write(writer);
+            if (updates == null) {
+                writer.write("</m:ItemIds>");
+            }
+        }
+    }
+
+    protected void writeParentItemId(Writer writer) throws IOException {
+        if (parentItemId != null) {
+            writer.write("<m:ParentItemId Id=\"");
+            writer.write(parentItemId.id);
+            if (parentItemId.changeKey != null) {
+                writer.write("\" ChangeKey=\"");
+                writer.write(parentItemId.changeKey);
+            }
+            writer.write("\"/>");
+        }
+    }
+
+    protected void writeFolderId(Writer writer) throws IOException {
+        if (folderId != null) {
+            if (updates == null) {
+                writer.write("<m:FolderIds>");
+            }
+            folderId.write(writer);
+            if (updates == null) {
+                writer.write("</m:FolderIds>");
+            }
+        }
+    }
+
+    protected void writeSavedItemFolderId(Writer writer) throws IOException {
+        if (savedItemFolderId != null) {
+            writer.write("<m:SavedItemFolderId>");
+            savedItemFolderId.write(writer);
+            writer.write("</m:SavedItemFolderId>");
+        }
+    }
+
+    protected void writeToFolderId(Writer writer) throws IOException {
+        if (toFolderId != null) {
+            writer.write("<m:ToFolderId>");
+            toFolderId.write(writer);
+            writer.write("</m:ToFolderId>");
+        }
+    }
+
+    protected void writeParentFolderId(Writer writer) throws IOException {
+        if (parentFolderId != null) {
+            writer.write("<m:ParentFolderId");
+            if (item == null) {
+                writer.write("s");
+            }
+            writer.write(">");
+            parentFolderId.write(writer);
+            writer.write("</m:ParentFolderId");
+            if (item == null) {
+                writer.write("s");
+            }
+            writer.write(">");
+        }
+    }
+
+    protected void writeItem(Writer writer) throws IOException {
+        if (item != null) {
+            writer.write("<m:");
+            writer.write(itemType);
+            writer.write("s>");
+            item.write(writer);
+            writer.write("</m:");
+            writer.write(itemType);
+            writer.write("s>");
+        }
+    }
+
+    protected void writeRestriction(Writer writer) throws IOException {
+        if (searchExpression != null) {
+            writer.write("<m:Restriction>");
+            StringBuilder buffer = new StringBuilder();
+            searchExpression.appendTo(buffer);
+            writer.write(buffer.toString());
+            writer.write("</m:Restriction>");
+        }
+    }
+
+    protected void startChanges(Writer writer) throws IOException {
+        //noinspection VariableNotUsedInsideIf
+        if (updates != null) {
+            writer.write("<m:");
+            writer.write(itemType);
+            writer.write("Changes>");
+            writer.write("<t:");
+            writer.write(itemType);
+            writer.write("Change>");
+        }
+    }
+
+    protected void writeUpdates(Writer writer) throws IOException {
+        if (updates != null) {
+            writer.write("<t:Updates>");
+            for (FieldUpdate fieldUpdate : updates) {
+                fieldUpdate.write(itemType, writer);
+            }
+            writer.write("</t:Updates>");
+        }
+    }
+
+    protected void writeUnresolvedEntry(Writer writer) throws IOException {
+        if (unresolvedEntry != null) {
+            unresolvedEntry.write(writer);
+        }
+    }
+
+    protected void endChanges(Writer writer) throws IOException {
+        //noinspection VariableNotUsedInsideIf
+        if (updates != null) {
+            writer.write("</t:");
+            writer.write(itemType);
+            writer.write("Change>");
+            writer.write("</m:");
+            writer.write(itemType);
+            writer.write("Changes>");
+        }
+    }
+
+    protected byte[] generateSoapEnvelope() {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try {
+            OutputStreamWriter writer = new OutputStreamWriter(baos, "UTF-8");
+            writer.write("<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" " +
+                    "xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\" " +
+                    "xmlns:m=\"http://schemas.microsoft.com/exchange/services/2006/messages\">" +
+                    "");
+            writer.write("<soap:Header>");
+            if (serverVersion != null) {
+                writer.write("<t:RequestServerVersion Version=\"");
+                writer.write(serverVersion);
+                writer.write("\"/>");
+            }
+            if (timezoneContext != null) {
+                writer.write("<t:TimeZoneContext><t:TimeZoneDefinition Id=\"");
+                writer.write(timezoneContext);
+                writer.write("\"/></t:TimeZoneContext>");
+            }
+            writer.write("</soap:Header>");
+
+            writer.write("<soap:Body>");
+            writer.write("<m:");
+            writer.write(methodName);
+            if (traversal != null) {
+                traversal.write(writer);
+            }
+            if (deleteType != null) {
+                deleteType.write(writer);
+            }
+            if (methodOptions != null) {
+                for (AttributeOption attributeOption : methodOptions) {
+                    attributeOption.write(writer);
+                }
+            }
+            writer.write(">");
+            writeSoapBody(writer);
+            writer.write("</m:");
+            writer.write(methodName);
+            writer.write(">");
+            writer.write("</soap:Body>" +
+                    "</soap:Envelope>");
+            writer.flush();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        return baos.toByteArray();
+    }
+
+    protected void writeSoapBody(Writer writer) throws IOException {
+        startChanges(writer);
+        writeShape(writer);
+        writeIndexedPageItemView(writer);
+        writeRestriction(writer);
+        writeParentFolderId(writer);
+        writeToFolderId(writer);
+        writeItemId(writer);
+        writeParentItemId(writer);
+        writeAttachments(writer);
+        writeAttachmentId(writer);
+        writeFolderId(writer);
+        writeSavedItemFolderId(writer);
+        writeItem(writer);
+        writeUpdates(writer);
+        writeUnresolvedEntry(writer);
+        endChanges(writer);
+    }
+
+
+    protected void writeIndexedPageItemView(Writer writer) throws IOException {
+        if (maxCount > 0) {
+            writer.write("<m:IndexedPageItemView MaxEntriesReturned=\"");
+            writer.write(String.valueOf(maxCount));
+            writer.write("\" Offset=\"");
+            writer.write(String.valueOf(offset));
+            writer.write("\" BasePoint=\"Beginning\"/>");
+
+        }
+    }
+
+    protected void writeAttachmentId(Writer writer) throws IOException {
+        if (attachmentId != null) {
+            if ("CreateAttachment".equals(methodName)) {
+                writer.write("<m:AttachmentShape>");
+                writer.write("<t:IncludeMimeContent>true</t:IncludeMimeContent>");
+                writer.write("</m:AttachmentShape>");
+            }
+            writer.write("<m:AttachmentIds>");
+            writer.write("<t:AttachmentId Id=\"");
+            writer.write(attachmentId);
+            writer.write("\"/>");
+            writer.write("</m:AttachmentIds>");
+        }
+    }
+
+    protected void writeAttachments(Writer writer) throws IOException {
+        if (attachment != null) {
+            writer.write("<m:Attachments>");
+            attachment.write(writer);
+            writer.write("</m:Attachments>");
+        }
+    }
+
+    /**
+     * Get Exchange server version, Exchange2010 or Exchange2007_SP1
+     *
+     * @return server version
+     */
+    public String getServerVersion() {
+        return serverVersion;
+    }
+
+    /**
+     * Set Exchange server version, Exchange2010 or Exchange2007_SP1
+     *
+     * @param serverVersion server version
+     */
+    public void setServerVersion(String serverVersion) {
+        this.serverVersion = serverVersion;
+    }
+
+    /**
+     * Set Exchange timezone context
+     *
+     * @param timezoneContext user timezone context
+     */
+    public void setTimezoneContext(String timezoneContext) {
+        this.timezoneContext = timezoneContext;
+    }
+
+    /**
+     * Meeting attendee object
+     */
+    public static class Attendee {
+        /**
+         * attendee role
+         */
+        public String role;
+        /**
+         * attendee email address
+         */
+        public String email;
+        /**
+         * attendee participation status
+         */
+        public String partstat;
+        /**
+         * attendee fullname
+         */
+        public String name;
+            }
+
+        /**
+     * Recurring event occurrence
+     */
+    public static class Occurrence {
+        /**
+         * Original occurence start date
+         */
+        public String originalStart;
+    }
+
+    /**
+     * Item
+     */
+    public static class Item extends HashMap<String, String> {
+        /**
+         * Item type.
+         */
+        public String type;
+        protected byte[] mimeContent;
+        protected List<FieldUpdate> fieldUpdates;
+        protected List<FileAttachment> attachments;
+        protected List<Attendee> attendees;
+        protected final List<String> fieldNames = new ArrayList<String>();
+        protected List<Occurrence> occurrences;
+        protected ItemId referenceItemId;
+
+        @Override
+        public String toString() {
+            return "type: " + type + ' ' + super.toString();
+        }
+
+        @Override
+        public String put(String key, String value) {
+            if (value != null) {
+                if (get(key) == null) {
+                    fieldNames.add(key);
+                }
+                return super.put(key, value);
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Write XML content to writer.
+         *
+         * @param writer writer
+         * @throws IOException on error
+         */
+        public void write(Writer writer) throws IOException {
+            writer.write("<t:");
+            writer.write(type);
+            writer.write(">");
+            if (mimeContent != null) {
+                writer.write("<t:MimeContent>");
+                writer.write(new String(mimeContent));
+                writer.write("</t:MimeContent>");
+            }
+            if (referenceItemId != null) {
+                referenceItemId.write(writer);
+            }
+            // write ordered fields
+            for (String key : fieldNames) {
+                if ("MeetingTimeZone".equals(key)) {
+                    writer.write("<t:MeetingTimeZone TimeZoneName=\"");
+                    writer.write(StringUtil.xmlEncodeAttribute(get(key)));
+                    writer.write("\"></t:MeetingTimeZone>");
+                } else if ("StartTimeZone".equals(key)) {
+                    writer.write("<t:StartTimeZone Id=\"");
+                    writer.write(StringUtil.xmlEncodeAttribute(get(key)));
+                    writer.write("\"></t:StartTimeZone>");
+                } else {
+                    writer.write("<t:");
+                    writer.write(key);
+                    writer.write(">");
+                    writer.write(StringUtil.xmlEncode(get(key)));
+                    writer.write("</t:");
+                    writer.write(key);
+                    writer.write(">");
+                }
+            }
+            if (fieldUpdates != null) {
+                for (FieldUpdate fieldUpdate : fieldUpdates) {
+                    fieldUpdate.write(null, writer);
+                }
+            }
+            writer.write("</t:");
+            writer.write(type);
+            writer.write(">");
+        }
+
+        /**
+         * Field updates.
+         *
+         * @param fieldUpdates field updates
+         */
+        public void setFieldUpdates(List<FieldUpdate> fieldUpdates) {
+            this.fieldUpdates = fieldUpdates;
+        }
+
+        /**
+         * Get property value as int
+         *
+         * @param key property response name
+         * @return property value
+         */
+        public int getInt(String key) {
+            int result = 0;
+            String value = get(key);
+            if (value != null && value.length() > 0) {
+                result = Integer.parseInt(value);
+            }
+            return result;
+        }
+
+        /**
+         * Get property value as long
+         *
+         * @param key property response name
+         * @return property value
+         */
+        public long getLong(String key) {
+            long result = 0;
+            String value = get(key);
+            if (value != null && value.length() > 0) {
+                result = Long.parseLong(value);
+            }
+            return result;
+        }
+
+
+        /**
+         * Get property value as boolean
+         *
+         * @param key property response name
+         * @return property value
+         */
+        public boolean getBoolean(String key) {
+            boolean result = false;
+            String value = get(key);
+            if (value != null && value.length() > 0) {
+                result = Boolean.parseBoolean(value);
+            }
+            return result;
+        }
+
+        /**
+         * Get file attachment by file name
+         *
+         * @param attachmentName attachment name
+         * @return attachment
+         */
+        public FileAttachment getAttachmentByName(String attachmentName) {
+            FileAttachment result = null;
+            if (attachments != null) {
+                for (FileAttachment attachment : attachments) {
+                    if (attachmentName.equals(attachment.name)) {
+                        result = attachment;
+                        break;
+                    }
+                }
+            }
+            return result;
+        }
+
+        /**
+         * Get all attendees.
+         *
+         * @return all attendees
+         */
+        public List<Attendee> getAttendees() {
+            return attendees;
+        }
+
+        /**
+         * Add attendee.
+         *
+         * @param attendee attendee object
+         */
+        public void addAttendee(Attendee attendee) {
+            if (attendees == null) {
+                attendees = new ArrayList<Attendee>();
+            }
+            attendees.add(attendee);
+        }
+
+        /**
+         * Add occurrence.
+         *
+         * @param occurrence event occurence
+         */
+        public void addOccurrence(Occurrence occurrence) {
+            if (occurrences == null) {
+                occurrences = new ArrayList<Occurrence>();
+            }
+            occurrences.add(occurrence);
+        }
+
+        /**
+         * Get occurences.
+         *
+         * @return event occurences
+         */
+        public List<Occurrence> getOccurrences() {
+            return occurrences;
+        }
+    }
+
+    /**
+     * Check method success.
+     *
+     * @throws EWSException on error
+     */
+    public void checkSuccess() throws EWSException {
+        if (errorDetail != null) {
+            if (!"ErrorAccessDenied".equals(errorDetail)
+                    && !"ErrorMailRecipientNotFound".equals(errorDetail)
+                    && !"ErrorItemNotFound".equals(errorDetail)
+                    ) {
+                try {
+                    throw new EWSException(errorDetail +" "+((errorDescription!=null)?errorDescription:"")+ "\n request: " + new String(generateSoapEnvelope(), "UTF-8"));
+                } catch (UnsupportedEncodingException e) {
+                    throw new EWSException(e.getMessage());
+                }
+            }
+        }
+        if (getStatusCode() == HttpStatus.SC_BAD_REQUEST) {
+             throw new EWSException(getStatusText());
+        }
+    }
+
+    @Override
+    public int getStatusCode() {
+        if ("ErrorAccessDenied".equals(errorDetail)) {
+            return HttpStatus.SC_FORBIDDEN;
+        } else {
+            return super.getStatusCode();
+        }
+    }
+
+    /**
+     * Get response items.
+     *
+     * @return response items
+     * @throws EWSException on error
+     */
+    public List<Item> getResponseItems() throws EWSException {
+        checkSuccess();
+        if (responseItems != null) {
+            return responseItems;
+        } else {
+            return new ArrayList<Item>();
+        }
+    }
+
+    /**
+     * Get single response item.
+     *
+     * @return response item
+     * @throws EWSException on error
+     */
+    public Item getResponseItem() throws EWSException {
+        checkSuccess();
+        if (responseItems != null && responseItems.size() > 0) {
+            return responseItems.get(0);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Get response mime content.
+     *
+     * @return mime content
+     * @throws EWSException on error
+     */
+    public byte[] getMimeContent() throws EWSException {
+        checkSuccess();
+        Item responseItem = getResponseItem();
+        if (responseItem != null) {
+            return responseItem.mimeContent;
+        } else {
+            return null;
+        }
+    }
+
+    protected String handleTag(XMLStreamReader reader, String localName) throws XMLStreamException {
+        String result = null;
+        int event = reader.getEventType();
+        if (event == XMLStreamConstants.START_ELEMENT && localName.equals(reader.getLocalName())) {
+            while (reader.hasNext() &&
+                    !((event == XMLStreamConstants.END_ELEMENT && localName.equals(reader.getLocalName())))) {
+                event = reader.next();
+                if (event == XMLStreamConstants.CHARACTERS) {
+                    result = reader.getText();
+                }
+            }
+        }
+        return result;
+    }
+
+    protected void handleErrors(XMLStreamReader reader) throws XMLStreamException {
+        String result = handleTag(reader, "ResponseCode");
+        // store error description;
+        String messageText = handleTag(reader, "MessageText");
+        if (messageText != null) {
+            errorDescription = messageText;
+        }
+        if (errorDetail == null && result != null
+                && !"NoError".equals(result)
+                && !"ErrorNameResolutionMultipleResults".equals(result)
+                && !"ErrorNameResolutionNoResults".equals(result)
+                && !"ErrorFolderExists".equals(result)
+                ) {
+            errorDetail = result;
+        }
+        if (XMLStreamUtil.isStartTag(reader, "faultstring")) {
+            errorDetail = XMLStreamUtil.getElementText(reader);
+        }
+    }
+
+    protected Item handleItem(XMLStreamReader reader) throws XMLStreamException {
+        Item responseItem = new Item();
+        responseItem.type = reader.getLocalName();
+        while (reader.hasNext() && !XMLStreamUtil.isEndTag(reader, responseItem.type)) {
+            reader.next();
+            if (XMLStreamUtil.isStartTag(reader)) {
+                String tagLocalName = reader.getLocalName();
+                String value = null;
+                if ("ExtendedProperty".equals(tagLocalName)) {
+                    addExtendedPropertyValue(reader, responseItem);
+                } else if (tagLocalName.endsWith("MimeContent")) {
+                    handleMimeContent(reader, responseItem);
+                } else if ("Attachments".equals(tagLocalName)) {
+                    responseItem.attachments = handleAttachments(reader);
+                } else if ("EmailAddresses".equals(tagLocalName)) {
+                    handleEmailAddresses(reader, responseItem);
+                } else if ("RequiredAttendees".equals(tagLocalName) || "OptionalAttendees".equals(tagLocalName)) {
+                    handleAttendees(reader, responseItem, tagLocalName);
+                } else if ("ModifiedOccurrences".equals(tagLocalName)) {
+                    handleModifiedOccurrences(reader, responseItem);
+                } else {
+                    if (tagLocalName.endsWith("Id")) {
+                        value = getAttributeValue(reader, "Id");
+                        // get change key
+                        responseItem.put("ChangeKey", getAttributeValue(reader, "ChangeKey"));
+                    }
+                    if (value == null) {
+                        value = getTagContent(reader);
+                    }
+                    if (value != null) {
+                        responseItem.put(tagLocalName, value);
+                    }
+                }
+            }
+        }
+        return responseItem;
+    }
+
+    protected void handleEmailAddresses(XMLStreamReader reader, Item item) throws XMLStreamException {
+        while (reader.hasNext() && !(XMLStreamUtil.isEndTag(reader, "EmailAddresses"))) {
+            reader.next();
+            if (XMLStreamUtil.isStartTag(reader)) {
+                String tagLocalName = reader.getLocalName();
+                if ("Entry".equals(tagLocalName)) {
+                    item.put(reader.getAttributeValue(null, "Key"), XMLStreamUtil.getElementText(reader));
+                }
+            }
+        }
+    }
+
+    protected void handleAttendees(XMLStreamReader reader, Item item, String attendeeType) throws XMLStreamException {
+        while (reader.hasNext() && !(XMLStreamUtil.isEndTag(reader, attendeeType))) {
+            reader.next();
+            if (XMLStreamUtil.isStartTag(reader)) {
+                String tagLocalName = reader.getLocalName();
+                if ("Attendee".equals(tagLocalName)) {
+                    handleAttendee(reader, item, attendeeType);
+                }
+            }
+        }
+    }
+
+    protected void handleModifiedOccurrences(XMLStreamReader reader, Item item) throws XMLStreamException {
+        while (reader.hasNext() && !(XMLStreamUtil.isEndTag(reader, "ModifiedOccurrences"))) {
+            reader.next();
+            if (XMLStreamUtil.isStartTag(reader)) {
+                String tagLocalName = reader.getLocalName();
+                if ("Occurrence".equals(tagLocalName)) {
+                    handleOccurrence(reader, item);
+                }
+            }
+        }
+    }
+
+    protected void handleOccurrence(XMLStreamReader reader, Item item) throws XMLStreamException {
+        while (reader.hasNext() && !(XMLStreamUtil.isEndTag(reader, "Occurrence"))) {
+            reader.next();
+            if (XMLStreamUtil.isStartTag(reader)) {
+                String tagLocalName = reader.getLocalName();
+                if ("OriginalStart".equals(tagLocalName)) {
+                    Occurrence occurrence = new Occurrence();
+                    occurrence.originalStart = XMLStreamUtil.getElementText(reader);
+                    item.addOccurrence(occurrence);
+                }
+            }
+        }
+    }
+    
+    public static String responseTypeToPartstat(String responseType) {
+        if ("Accept".equals(responseType)) {
+            return "ACCEPTED";
+        } else if ("Tentative".equals(responseType)) {
+            return "TENTATIVE";
+        } else if ("Decline".equals(responseType)) {
+            return "DECLINED";
+        } else {
+            return "NEEDS-ACTION";
+        }
+    }
+
+    protected void handleAttendee(XMLStreamReader reader, Item item, String attendeeType) throws XMLStreamException {
+        Attendee attendee = new Attendee();
+        if ("RequiredAttendees".equals(attendeeType)) {
+            attendee.role = "REQ-PARTICIPANT";
+        } else {
+            attendee.role = "OPT-PARTICIPANT";
+        }
+        while (reader.hasNext() && !(XMLStreamUtil.isEndTag(reader, "Attendee"))) {
+            reader.next();
+            if (XMLStreamUtil.isStartTag(reader)) {
+                String tagLocalName = reader.getLocalName();
+                if ("EmailAddress".equals(tagLocalName)) {
+                    attendee.email = reader.getElementText();
+                } else if ("Name".equals(tagLocalName)) {
+                    attendee.name = XMLStreamUtil.getElementText(reader);
+                } else if ("ResponseType".equals(tagLocalName)) {
+                    String responseType = XMLStreamUtil.getElementText(reader);
+                    attendee.partstat = responseTypeToPartstat(responseType);
+                }
+            }
+        }
+        item.addAttendee(attendee);
+    }
+
+    protected List<FileAttachment> handleAttachments(XMLStreamReader reader) throws XMLStreamException {
+        List<FileAttachment> attachments = new ArrayList<FileAttachment>();
+        while (reader.hasNext() && !(XMLStreamUtil.isEndTag(reader, "Attachments"))) {
+            reader.next();
+            if (XMLStreamUtil.isStartTag(reader)) {
+                String tagLocalName = reader.getLocalName();
+                if ("FileAttachment".equals(tagLocalName)) {
+                    attachments.add(handleFileAttachment(reader));
+                }
+            }
+        }
+        return attachments;
+    }
+
+    protected FileAttachment handleFileAttachment(XMLStreamReader reader) throws XMLStreamException {
+        FileAttachment fileAttachment = new FileAttachment();
+        while (reader.hasNext() && !(XMLStreamUtil.isEndTag(reader, "FileAttachment"))) {
+            reader.next();
+            if (XMLStreamUtil.isStartTag(reader)) {
+                String tagLocalName = reader.getLocalName();
+                if ("AttachmentId".equals(tagLocalName)) {
+                    fileAttachment.attachmentId = getAttributeValue(reader, "Id");
+                } else if ("Name".equals(tagLocalName)) {
+                    fileAttachment.name = getTagContent(reader);
+                } else if ("ContentType".equals(tagLocalName)) {
+                    fileAttachment.contentType = getTagContent(reader);
+                }
+            }
+        }
+        return fileAttachment;
+    }
+
+
+    protected void handleMimeContent(XMLStreamReader reader, Item responseItem) throws XMLStreamException {
+        if (reader instanceof TypedXMLStreamReader) {
+            // Stax2 parser: use enhanced base64 conversion
+            responseItem.mimeContent = ((TypedXMLStreamReader) reader).getElementAsBinary();
+        } else {
+            // failover: slow and memory consuming conversion
+            byte[] base64MimeContent = reader.getElementText().getBytes();
+            responseItem.mimeContent = Base64.decodeBase64(base64MimeContent);
+        }
+    }
+
+    protected void addExtendedPropertyValue(XMLStreamReader reader, Item item) throws XMLStreamException {
+        String propertyTag = null;
+        String propertyValue = null;
+        while (reader.hasNext() && !(XMLStreamUtil.isEndTag(reader, "ExtendedProperty"))) {
+            reader.next();
+            if (XMLStreamUtil.isStartTag(reader)) {
+                String tagLocalName = reader.getLocalName();
+                if ("ExtendedFieldURI".equals(tagLocalName)) {
+                    propertyTag = getAttributeValue(reader, "PropertyTag");
+                    // property name is in PropertyId or PropertyName with DistinguishedPropertySetId
+                    if (propertyTag == null) {
+                        propertyTag = getAttributeValue(reader, "PropertyId");
+                    }
+                    if (propertyTag == null) {
+                        propertyTag = getAttributeValue(reader, "PropertyName");
+                    }
+                } else if ("Value".equals(tagLocalName)) {
+                    propertyValue = XMLStreamUtil.getElementText(reader);
+                } else if ("Values".equals(tagLocalName)) {
+                    StringBuilder buffer = new StringBuilder();
+                    while (reader.hasNext() && !(XMLStreamUtil.isEndTag(reader, "Values"))) {
+                        reader.next();
+                        if (XMLStreamUtil.isStartTag(reader)) {
+
+                            if (buffer.length() > 0) {
+                                buffer.append(',');
+                            }
+                            String singleValue = XMLStreamUtil.getElementText(reader);
+                            if (singleValue != null) {
+                                buffer.append(singleValue);
+                            }
+                        }
+                    }
+                    propertyValue = buffer.toString();
+                }
+            }
+        }
+        if ((propertyTag != null) && (propertyValue != null)) {
+            item.put(propertyTag, propertyValue);
+        }
+    }
+
+    protected String getTagContent(XMLStreamReader reader) throws XMLStreamException {
+        String tagLocalName = reader.getLocalName();
+        while (reader.hasNext() && !(reader.getEventType() == XMLStreamConstants.END_ELEMENT)) {
+            reader.next();
+            if (reader.getEventType() == XMLStreamConstants.CHARACTERS) {
+                return reader.getText();
+            }
+        }
+        // empty tag
+        if (reader.hasNext()) {
+            return null;
+        } else {
+            throw new XMLStreamException("End element for " + tagLocalName + " not found");
+        }
+    }
+
+    protected String getAttributeValue(XMLStreamReader reader, String attributeName) {
+        for (int i = 0; i < reader.getAttributeCount(); i++) {
+            if (attributeName.equals(reader.getAttributeLocalName(i))) {
+                return reader.getAttributeValue(i);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    protected void processResponseBody(HttpState httpState, HttpConnection httpConnection) {
+        Header contentTypeHeader = getResponseHeader("Content-Type");
+        if (contentTypeHeader != null && "text/xml; charset=utf-8".equals(contentTypeHeader.getValue())) {
+            try {
+                if (DavGatewayHttpClientFacade.isGzipEncoded(this)) {
+                    processResponseStream(new GZIPInputStream(getResponseBodyAsStream()));
+                } else {
+                    processResponseStream(getResponseBodyAsStream());
+                }
+            } catch (IOException e) {
+                LOGGER.error("Error while parsing soap response: " + e, e);
+            }
+        }
+    }
+
+    protected void processResponseStream(InputStream inputStream) {
+        responseItems = new ArrayList<Item>();
+        XMLStreamReader reader = null;
+        try {
+            inputStream = new FilterInputStream(inputStream) {
+                int totalCount;
+                int lastLogCount;
+
+                @Override
+                public int read(byte[] buffer, int offset, int length) throws IOException {
+                    int count = super.read(buffer, offset, length);
+                    totalCount += count;
+                    if (totalCount - lastLogCount > 1024 * 128) {
+                        DavGatewayTray.debug(new BundleMessage("LOG_DOWNLOAD_PROGRESS", String.valueOf(totalCount / 1024), EWSMethod.this.getURI()));
+                        DavGatewayTray.switchIcon();
+                        lastLogCount = totalCount;
+                    }
+                    return count;
+                }
+            };
+            reader = XMLStreamUtil.createXMLStreamReader(inputStream);
+            while (reader.hasNext()) {
+                reader.next();
+                handleErrors(reader);
+                if (serverVersion == null && XMLStreamUtil.isStartTag(reader, "ServerVersionInfo")) {
+                    String majorVersion = getAttributeValue(reader, "MajorVersion");
+                    if ("14".equals(majorVersion)) {
+                        String minorVersion = getAttributeValue(reader, "MinorVersion");
+                        if ("0".equals(minorVersion)) {
+                            serverVersion = "Exchange2010";
+                        } else {
+                            serverVersion = "Exchange2010_SP1";
+                        }
+                    } else {
+                        serverVersion = "Exchange2007_SP1";
+                    }
+                } else if (XMLStreamUtil.isStartTag(reader, "RootFolder")) {
+                    includesLastItemInRange = "true".equals(reader.getAttributeValue(null, "IncludesLastItemInRange"));
+                } else if (XMLStreamUtil.isStartTag(reader, responseCollectionName)) {
+                    handleItems(reader);
+                } else {
+                    handleCustom(reader);
+                }
+            }
+        } catch (XMLStreamException e) {
+            LOGGER.error("Error while parsing soap response: " + e, e);
+            if (reader != null) {
+                try {
+                    LOGGER.error("Current text: " + reader.getText());
+                } catch (IllegalStateException ise) {
+                    LOGGER.error(e+" "+e.getMessage());
+                }
+            }
+        }
+        if (errorDetail != null) {
+            LOGGER.debug(errorDetail);
+        }
+    }
+
+    @SuppressWarnings({"NoopMethodInAbstractClass"})
+    protected void handleCustom(XMLStreamReader reader) throws XMLStreamException {
+        // override to handle custom content
+    }
+
+    private void handleItems(XMLStreamReader reader) throws XMLStreamException {
+        while (reader.hasNext() && !XMLStreamUtil.isEndTag(reader, responseCollectionName)) {
+            reader.next();
+            if (XMLStreamUtil.isStartTag(reader)) {
+                responseItems.add(handleItem(reader));
+            }
+        }
+
+    }
+
+}
diff --git a/src/java/davmail/exchange/ews/ElementOption.java b/src/java/davmail/exchange/ews/ElementOption.java
new file mode 100644
index 0000000..c1bbf75
--- /dev/null
+++ b/src/java/davmail/exchange/ews/ElementOption.java
@@ -0,0 +1,53 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+import davmail.util.StringUtil;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Generic element option.
+ */
+public class ElementOption extends Option {
+    /**
+     * Create element option.
+     *
+     * @param name  element tag name
+     * @param value element value
+     */
+    protected ElementOption(String name, String value) {
+        super(name, value);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    public void write(Writer writer) throws IOException {
+        writer.write('<');
+        writer.write(name);
+        writer.write('>');
+        writer.write(StringUtil.xmlEncode(value));
+        writer.write("</");
+        writer.write(name);
+        writer.write('>');
+    }
+}
diff --git a/src/java/davmail/exchange/ews/EwsExchangeSession.java b/src/java/davmail/exchange/ews/EwsExchangeSession.java
new file mode 100644
index 0000000..7e9293a
--- /dev/null
+++ b/src/java/davmail/exchange/ews/EwsExchangeSession.java
@@ -0,0 +1,2409 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+import davmail.Settings;
+import davmail.exception.DavMailAuthenticationException;
+import davmail.exception.DavMailException;
+import davmail.exception.HttpNotFoundException;
+import davmail.exchange.ExchangeSession;
+import davmail.exchange.VCalendar;
+import davmail.exchange.VObject;
+import davmail.exchange.VProperty;
+import davmail.http.DavGatewayHttpClientFacade;
+import davmail.util.IOUtil;
+import davmail.util.StringUtil;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.httpclient.*;
+import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.httpclient.methods.PostMethod;
+import org.apache.commons.httpclient.params.HttpClientParams;
+
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+import javax.mail.util.SharedByteArrayInputStream;
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.SocketException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * EWS Exchange adapter.
+ * Compatible with Exchange 2007 and hopefully 2010.
+ */
+public class EwsExchangeSession extends ExchangeSession {
+
+    protected static final int PAGE_SIZE = 100;
+
+    protected static final String ARCHIVE_ROOT = "/archive/";
+
+    /**
+     * Message types.
+     *
+     * @see <a href="http://msdn.microsoft.com/en-us/library/aa565652%28v=EXCHG.140%29.aspx">http://msdn.microsoft.com/en-us/library/aa565652%28v=EXCHG.140%29.aspx</a>
+     */
+    protected static final Set<String> MESSAGE_TYPES = new HashSet<String>();
+
+    static {
+        MESSAGE_TYPES.add("Message");
+        MESSAGE_TYPES.add("CalendarItem");
+
+        MESSAGE_TYPES.add("MeetingMessage");
+        MESSAGE_TYPES.add("MeetingRequest");
+        MESSAGE_TYPES.add("MeetingResponse");
+        MESSAGE_TYPES.add("MeetingCancellation");
+
+        // exclude types from IMAP
+        //MESSAGE_TYPES.add("Item");
+        //MESSAGE_TYPES.add("PostItem");
+        //MESSAGE_TYPES.add("Contact");
+        //MESSAGE_TYPES.add("DistributionList");
+        //MESSAGE_TYPES.add("Task");
+
+        //ReplyToItem
+        //ForwardItem
+        //ReplyAllToItem
+        //AcceptItem
+        //TentativelyAcceptItem
+        //DeclineItem
+        //CancelCalendarItem
+        //RemoveItem
+        //PostReplyItem
+        //SuppressReadReceipt
+        //AcceptSharingInvitation
+    }
+
+    static final Map<String, String> vTodoToTaskStatusMap = new HashMap<String, String>();
+    static final Map<String, String> taskTovTodoStatusMap = new HashMap<String, String>();
+
+    static {
+        //taskTovTodoStatusMap.put("NotStarted", null);
+        taskTovTodoStatusMap.put("InProgress", "IN-PROCESS");
+        taskTovTodoStatusMap.put("Completed", "COMPLETED");
+        taskTovTodoStatusMap.put("WaitingOnOthers", "NEEDS-ACTION");
+        taskTovTodoStatusMap.put("Deferred", "CANCELLED");
+
+        //vTodoToTaskStatusMap.put(null, "NotStarted");
+        vTodoToTaskStatusMap.put("IN-PROCESS", "InProgress");
+        vTodoToTaskStatusMap.put("COMPLETED", "Completed");
+        vTodoToTaskStatusMap.put("NEEDS-ACTION", "WaitingOnOthers");
+        vTodoToTaskStatusMap.put("CANCELLED", "Deferred");
+    }
+
+    protected Map<String, String> folderIdMap;
+
+    protected class Folder extends ExchangeSession.Folder {
+        public FolderId folderId;
+    }
+
+    protected static class FolderPath {
+        protected final String parentPath;
+        protected final String folderName;
+
+        protected FolderPath(String folderPath) {
+            int slashIndex = folderPath.lastIndexOf('/');
+            if (slashIndex < 0) {
+                parentPath = "";
+                folderName = folderPath;
+            } else {
+                parentPath = folderPath.substring(0, slashIndex);
+                folderName = folderPath.substring(slashIndex + 1);
+            }
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public EwsExchangeSession(String url, String userName, String password) throws IOException {
+        super(url, userName, password);
+    }
+
+    @Override
+    protected HttpMethod formLogin(HttpClient httpClient, HttpMethod initmethod, String userName, String password) throws IOException {
+        LOGGER.debug("Form based authentication detected");
+
+        HttpMethod logonMethod = buildLogonMethod(httpClient, initmethod);
+        if (logonMethod == null) {
+            LOGGER.debug("Authentication form not found at " + initmethod.getURI() + ", will try direct EWS access");
+        } else {
+            logonMethod = postLogonMethod(httpClient, logonMethod, userName, password);
+        }
+
+        return logonMethod;
+    }
+
+
+    /**
+     * Check endpoint url.
+     *
+     * @param endPointUrl endpoint url
+     * @throws IOException on error
+     */
+    protected void checkEndPointUrl(String endPointUrl) throws IOException {
+        HttpMethod getMethod = new GetMethod(endPointUrl);
+        getMethod.setFollowRedirects(false);
+        try {
+            int status = DavGatewayHttpClientFacade.executeNoRedirect(httpClient, getMethod);
+            if (status == HttpStatus.SC_UNAUTHORIZED) {
+                throw new DavMailAuthenticationException("EXCEPTION_AUTHENTICATION_FAILED");
+            } else if (status != HttpStatus.SC_MOVED_TEMPORARILY) {
+                throw DavGatewayHttpClientFacade.buildHttpException(getMethod);
+            }
+            // check Location
+            Header locationHeader = getMethod.getResponseHeader("Location");
+            if (locationHeader == null || !"/ews/services.wsdl".equalsIgnoreCase(locationHeader.getValue())) {
+                throw new IOException("Ews endpoint not available at " + getMethod.getURI().toString());
+            }
+        } finally {
+            getMethod.releaseConnection();
+        }
+    }
+
+    @Override
+    protected void buildSessionInfo(HttpMethod method) throws DavMailException {
+        // no need to check logon method body
+        if (method != null) {
+            method.releaseConnection();
+            // need to retrieve email and alias
+            getEmailAndAliasFromOptions();
+        }
+
+        if (email == null || alias == null) {
+            // OWA authentication failed, get email address from login
+            if (userName.indexOf('@') >= 0) {
+                // userName is email address
+                email = userName;
+                alias = userName.substring(0, userName.indexOf('@'));
+            } else {
+                // userName or domain\\username, rebuild email address
+                alias = getAliasFromLogin();
+                email = getAliasFromLogin() + getEmailSuffixFromHostname();
+            }
+        }
+
+        currentMailboxPath = "/users/" + email.toLowerCase();
+
+        // check EWS access
+        try {
+            checkEndPointUrl("/ews/exchange.asmx");
+            // workaround for Exchange bug: send fake request
+            internalGetFolder("");
+        } catch (IOException e) {
+            // first failover: retry with NTLM
+            DavGatewayHttpClientFacade.addNTLM(httpClient);
+            try {
+                checkEndPointUrl("/ews/exchange.asmx");
+                // workaround for Exchange bug: send fake request
+                internalGetFolder("");
+            } catch (IOException e2) {
+                LOGGER.debug(e2.getMessage());
+                try {
+                    // failover, try to retrieve EWS url from autodiscover
+                    checkEndPointUrl(getEwsUrlFromAutoDiscover());
+                    // workaround for Exchange bug: send fake request
+                    internalGetFolder("");
+                } catch (IOException e3) {
+                    // autodiscover failed and initial exception was authentication failure => throw original exception
+                    if (e instanceof DavMailAuthenticationException) {
+                        throw (DavMailAuthenticationException) e;
+                    }
+                    LOGGER.error(e2.getMessage());
+                    throw new DavMailAuthenticationException("EXCEPTION_EWS_NOT_AVAILABLE");
+                }
+            }
+        }
+
+        // enable preemptive authentication on non NTLM endpoints
+        if (!DavGatewayHttpClientFacade.hasNTLM(httpClient)) {
+            httpClient.getParams().setParameter(HttpClientParams.PREEMPTIVE_AUTHENTICATION, true);
+        }
+
+        try {
+            folderIdMap = new HashMap<String, String>();
+            // load actual well known folder ids
+            folderIdMap.put(internalGetFolder(INBOX).folderId.value, INBOX);
+            folderIdMap.put(internalGetFolder(CALENDAR).folderId.value, CALENDAR);
+            folderIdMap.put(internalGetFolder(CONTACTS).folderId.value, CONTACTS);
+            folderIdMap.put(internalGetFolder(SENT).folderId.value, SENT);
+            folderIdMap.put(internalGetFolder(DRAFTS).folderId.value, DRAFTS);
+            folderIdMap.put(internalGetFolder(TRASH).folderId.value, TRASH);
+            folderIdMap.put(internalGetFolder(JUNK).folderId.value, JUNK);
+            folderIdMap.put(internalGetFolder(UNSENT).folderId.value, UNSENT);
+        } catch (IOException e) {
+            LOGGER.error(e.getMessage(), e);
+            throw new DavMailAuthenticationException("EXCEPTION_EWS_NOT_AVAILABLE");
+        }
+        LOGGER.debug("Current user email is " + email + ", alias is " + alias + " on " + serverVersion);
+    }
+
+    protected static class AutoDiscoverMethod extends PostMethod {
+        AutoDiscoverMethod(String autodiscoverHost, String userEmail) {
+            super("https://" + autodiscoverHost + "/autodiscover/autodiscover.xml");
+            setAutoDiscoverRequestEntity(userEmail);
+        }
+
+        AutoDiscoverMethod(String userEmail) {
+            super("/autodiscover/autodiscover.xml");
+            setAutoDiscoverRequestEntity(userEmail);
+        }
+
+        void setAutoDiscoverRequestEntity(String userEmail) {
+            String body = "<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006\">" +
+                    "<Request>" +
+                    "<EMailAddress>" + userEmail + "</EMailAddress>" +
+                    "<AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema>" +
+                    "</Request>" +
+                    "</Autodiscover>";
+            setRequestEntity(new ByteArrayRequestEntity(body.getBytes(), "text/xml"));
+        }
+
+        String ewsUrl;
+
+        @Override
+        protected void processResponseBody(HttpState httpState, HttpConnection httpConnection) {
+            Header contentTypeHeader = getResponseHeader("Content-Type");
+            if (contentTypeHeader != null &&
+                    ("text/xml; charset=utf-8".equals(contentTypeHeader.getValue())
+                            || "text/html; charset=utf-8".equals(contentTypeHeader.getValue())
+                    )) {
+                BufferedReader autodiscoverReader = null;
+                try {
+                    autodiscoverReader = new BufferedReader(new InputStreamReader(getResponseBodyAsStream()));
+                    String line;
+                    // find ews url
+                    while ((line = autodiscoverReader.readLine()) != null
+                            && (line.indexOf("<EwsUrl>") == -1)
+                            && (line.indexOf("</EwsUrl>") == -1)) {
+                    }
+                    if (line != null) {
+                        ewsUrl = line.substring(line.indexOf("<EwsUrl>") + 8, line.indexOf("</EwsUrl>"));
+                    }
+                } catch (IOException e) {
+                    LOGGER.debug(e);
+                } finally {
+                    if (autodiscoverReader != null) {
+                        try {
+                            autodiscoverReader.close();
+                        } catch (IOException e) {
+                            LOGGER.debug(e);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    protected String getEwsUrlFromAutoDiscover() throws DavMailAuthenticationException {
+        String ewsUrl;
+        try {
+            ewsUrl = getEwsUrlFromAutoDiscover(null);
+        } catch (IOException e) {
+            try {
+                ewsUrl = getEwsUrlFromAutoDiscover("autodiscover." + email.substring(email.indexOf('@') + 1));
+            } catch (IOException e2) {
+                LOGGER.error(e2.getMessage());
+                throw new DavMailAuthenticationException("EXCEPTION_EWS_NOT_AVAILABLE");
+            }
+        }
+        return ewsUrl;
+    }
+
+    protected String getEwsUrlFromAutoDiscover(String autodiscoverHostname) throws IOException {
+        String ewsUrl;
+        AutoDiscoverMethod autoDiscoverMethod;
+        if (autodiscoverHostname != null) {
+            autoDiscoverMethod = new AutoDiscoverMethod(autodiscoverHostname, email);
+        } else {
+            autoDiscoverMethod = new AutoDiscoverMethod(email);
+        }
+        try {
+            int status = DavGatewayHttpClientFacade.executeNoRedirect(httpClient, autoDiscoverMethod);
+            if (status != HttpStatus.SC_OK) {
+                throw DavGatewayHttpClientFacade.buildHttpException(autoDiscoverMethod);
+            }
+            ewsUrl = autoDiscoverMethod.ewsUrl;
+
+            // update host name
+            DavGatewayHttpClientFacade.setClientHost(httpClient, ewsUrl);
+
+            if (ewsUrl == null) {
+                throw new IOException("Ews url not found");
+            }
+        } finally {
+            autoDiscoverMethod.releaseConnection();
+        }
+        return ewsUrl;
+    }
+
+    class Message extends ExchangeSession.Message {
+        // message item id
+        ItemId itemId;
+
+        @Override
+        public String getPermanentId() {
+            return itemId.id;
+        }
+
+        @Override
+        protected InputStream getMimeHeaders() {
+            InputStream result = null;
+            try {
+                GetItemMethod getItemMethod = new GetItemMethod(BaseShape.ID_ONLY, itemId, false);
+                getItemMethod.addAdditionalProperty(Field.get("messageheaders"));
+                getItemMethod.addAdditionalProperty(Field.get("from"));
+                executeMethod(getItemMethod);
+                EWSMethod.Item item = getItemMethod.getResponseItem();
+
+                String messageHeaders = item.get(Field.get("messageheaders").getResponseName());
+                if (messageHeaders != null) {
+                    // workaround for messages in Sent folder
+                    if (messageHeaders.indexOf("From:") < 0) {
+                        String from = item.get(Field.get("from").getResponseName());
+                        messageHeaders = "From: "+from+"\n"+messageHeaders;
+                    }
+
+                    result = new ByteArrayInputStream(messageHeaders.getBytes("UTF-8"));
+                }
+            } catch (Exception e) {
+                LOGGER.warn(e.getMessage());
+            }
+
+            return result;
+        }
+    }
+
+    /**
+     * Message create/update properties
+     *
+     * @param properties flag values map
+     * @return field values
+     */
+    protected List<FieldUpdate> buildProperties(Map<String, String> properties) {
+        ArrayList<FieldUpdate> list = new ArrayList<FieldUpdate>();
+        for (Map.Entry<String, String> entry : properties.entrySet()) {
+            if ("read".equals(entry.getKey())) {
+                list.add(Field.createFieldUpdate("read", Boolean.toString("1".equals(entry.getValue()))));
+            } else if ("junk".equals(entry.getKey())) {
+                list.add(Field.createFieldUpdate("junk", entry.getValue()));
+            } else if ("flagged".equals(entry.getKey())) {
+                list.add(Field.createFieldUpdate("flagStatus", entry.getValue()));
+            } else if ("answered".equals(entry.getKey())) {
+                list.add(Field.createFieldUpdate("lastVerbExecuted", entry.getValue()));
+                if ("102".equals(entry.getValue())) {
+                    list.add(Field.createFieldUpdate("iconIndex", "261"));
+                }
+            } else if ("forwarded".equals(entry.getKey())) {
+                list.add(Field.createFieldUpdate("lastVerbExecuted", entry.getValue()));
+                if ("104".equals(entry.getValue())) {
+                    list.add(Field.createFieldUpdate("iconIndex", "262"));
+                }
+            } else if ("draft".equals(entry.getKey())) {
+                // note: draft is readonly after create
+                list.add(Field.createFieldUpdate("messageFlags", entry.getValue()));
+            } else if ("deleted".equals(entry.getKey())) {
+                list.add(Field.createFieldUpdate("deleted", entry.getValue()));
+            } else if ("datereceived".equals(entry.getKey())) {
+                list.add(Field.createFieldUpdate("datereceived", entry.getValue()));
+            }
+        }
+        return list;
+    }
+
+    @Override
+    public void createMessage(String folderPath, String messageName, HashMap<String, String> properties, MimeMessage mimeMessage) throws IOException {
+        EWSMethod.Item item = new EWSMethod.Item();
+        item.type = "Message";
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try {
+            mimeMessage.writeTo(baos);
+        } catch (MessagingException e) {
+            throw new IOException(e.getMessage());
+        }
+        baos.close();
+        item.mimeContent = Base64.encodeBase64(baos.toByteArray());
+
+        List<FieldUpdate> fieldUpdates = buildProperties(properties);
+        if (!properties.containsKey("draft")) {
+            // need to force draft flag to false
+            if (properties.containsKey("read")) {
+                fieldUpdates.add(Field.createFieldUpdate("messageFlags", "1"));
+            } else {
+                fieldUpdates.add(Field.createFieldUpdate("messageFlags", "0"));
+            }
+        }
+        fieldUpdates.add(Field.createFieldUpdate("urlcompname", messageName));
+        item.setFieldUpdates(fieldUpdates);
+        CreateItemMethod createItemMethod = new CreateItemMethod(MessageDisposition.SaveOnly, getFolderId(folderPath), item);
+        executeMethod(createItemMethod);
+    }
+
+    @Override
+    public void updateMessage(ExchangeSession.Message message, Map<String, String> properties) throws IOException {
+        if (properties.containsKey("read") && "urn:content-classes:appointment".equals(message.contentClass)) {
+            properties.remove("read");
+        }
+        if (!properties.isEmpty()) {
+            UpdateItemMethod updateItemMethod = new UpdateItemMethod(MessageDisposition.SaveOnly,
+                    ConflictResolution.AlwaysOverwrite,
+                    SendMeetingInvitationsOrCancellations.SendToNone,
+                    ((EwsExchangeSession.Message) message).itemId, buildProperties(properties));
+            executeMethod(updateItemMethod);
+        }
+    }
+
+    @Override
+    public void deleteMessage(ExchangeSession.Message message) throws IOException {
+        LOGGER.debug("Delete " + message.permanentUrl);
+        DeleteItemMethod deleteItemMethod = new DeleteItemMethod(((EwsExchangeSession.Message) message).itemId, DeleteType.HardDelete, SendMeetingCancellations.SendToNone);
+        executeMethod(deleteItemMethod);
+    }
+
+
+    protected void sendMessage(String itemClass, byte[] messageBody) throws IOException {
+        EWSMethod.Item item = new EWSMethod.Item();
+        item.type = "Message";
+        item.mimeContent = Base64.encodeBase64(messageBody);
+        if (itemClass != null) {
+            item.put("ItemClass", itemClass);
+        }
+
+        MessageDisposition messageDisposition;
+        if (Settings.getBooleanProperty("davmail.smtpSaveInSent", true)) {
+            messageDisposition = MessageDisposition.SendAndSaveCopy;
+        } else {
+            messageDisposition = MessageDisposition.SendOnly;
+        }
+
+        CreateItemMethod createItemMethod = new CreateItemMethod(messageDisposition, getFolderId(SENT), item);
+        executeMethod(createItemMethod);
+    }
+
+    @Override
+    public void sendMessage(MimeMessage mimeMessage) throws IOException, MessagingException {
+        String itemClass = null;
+        if (mimeMessage.getContentType().startsWith("multipart/report")) {
+            itemClass = "REPORT.IPM.Note.IPNRN";
+        }
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try {
+            mimeMessage.writeTo(baos);
+        } catch (MessagingException e) {
+            throw new IOException(e.getMessage());
+        }
+        sendMessage(itemClass, baos.toByteArray());
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    protected byte[] getContent(ExchangeSession.Message message) throws IOException {
+        return getContent(((EwsExchangeSession.Message) message).itemId);
+    }
+
+    /**
+     * Get item content.
+     *
+     * @param itemId EWS item id
+     * @return item content as byte array
+     * @throws IOException on error
+     */
+    protected byte[] getContent(ItemId itemId) throws IOException {
+        GetItemMethod getItemMethod = new GetItemMethod(BaseShape.ID_ONLY, itemId, true);
+        byte[] mimeContent = null;
+        try {
+            executeMethod(getItemMethod);
+            mimeContent = getItemMethod.getMimeContent();
+        } catch (EWSException e) {
+            LOGGER.warn("GetItem with MimeContent failed: " + e.getMessage());
+        }
+        if (mimeContent == null) {
+            LOGGER.warn("MimeContent not available, trying to rebuild from properties");
+            try {
+                ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                getItemMethod = new GetItemMethod(BaseShape.ID_ONLY, itemId, false);
+                getItemMethod.addAdditionalProperty(Field.get("contentclass"));
+                getItemMethod.addAdditionalProperty(Field.get("message-id"));
+                getItemMethod.addAdditionalProperty(Field.get("from"));
+                getItemMethod.addAdditionalProperty(Field.get("to"));
+                getItemMethod.addAdditionalProperty(Field.get("cc"));
+                getItemMethod.addAdditionalProperty(Field.get("subject"));
+                getItemMethod.addAdditionalProperty(Field.get("date"));
+                getItemMethod.addAdditionalProperty(Field.get("body"));
+                executeMethod(getItemMethod);
+                EWSMethod.Item item = getItemMethod.getResponseItem();
+
+                MimeMessage mimeMessage = new MimeMessage((Session) null);
+                mimeMessage.addHeader("Content-class", item.get(Field.get("contentclass").getResponseName()));
+                mimeMessage.setSentDate(parseDateFromExchange(item.get(Field.get("date").getResponseName())));
+                mimeMessage.addHeader("From", item.get(Field.get("from").getResponseName()));
+                mimeMessage.addHeader("To", item.get(Field.get("to").getResponseName()));
+                mimeMessage.addHeader("Cc", item.get(Field.get("cc").getResponseName()));
+                mimeMessage.setSubject(item.get(Field.get("subject").getResponseName()));
+                String propertyValue = item.get(Field.get("body").getResponseName());
+                if (propertyValue == null) {
+                    propertyValue = "";
+                }
+                mimeMessage.setContent(propertyValue, "text/html; charset=UTF-8");
+
+                mimeMessage.writeTo(baos);
+                if (LOGGER.isDebugEnabled()) {
+                    LOGGER.debug("Rebuilt message content: " + new String(baos.toByteArray()));
+                }
+                mimeContent = baos.toByteArray();
+
+            } catch (IOException e2) {
+                LOGGER.warn(e2);
+            } catch (MessagingException e2) {
+                LOGGER.warn(e2);
+            }
+            if (mimeContent == null) {
+                throw new IOException("GetItem returned null MimeContent");
+            }
+        }
+        return mimeContent;
+    }
+
+    protected Message buildMessage(EWSMethod.Item response) throws DavMailException {
+        Message message = new Message();
+
+        // get item id
+        message.itemId = new ItemId(response);
+
+        message.permanentUrl = response.get(Field.get("permanenturl").getResponseName());
+
+        message.size = response.getInt(Field.get("messageSize").getResponseName());
+        message.uid = response.get(Field.get("uid").getResponseName());
+        message.contentClass = response.get(Field.get("contentclass").getResponseName());
+        message.imapUid = response.getLong(Field.get("imapUid").getResponseName());
+        message.read = response.getBoolean(Field.get("read").getResponseName());
+        message.junk = response.getBoolean(Field.get("junk").getResponseName());
+        message.flagged = "2".equals(response.get(Field.get("flagStatus").getResponseName()));
+        message.draft = (response.getInt(Field.get("messageFlags").getResponseName()) & 8) != 0;
+        String lastVerbExecuted = response.get(Field.get("lastVerbExecuted").getResponseName());
+        message.answered = "102".equals(lastVerbExecuted) || "103".equals(lastVerbExecuted);
+        message.forwarded = "104".equals(lastVerbExecuted);
+        message.date = convertDateFromExchange(response.get(Field.get("date").getResponseName()));
+        message.deleted = "1".equals(response.get(Field.get("deleted").getResponseName()));
+
+        String lastmodified = convertDateFromExchange(response.get(Field.get("lastmodified").getResponseName()));
+        message.recent = !message.read && lastmodified != null && lastmodified.equals(message.date);
+
+        if (LOGGER.isDebugEnabled()) {
+            StringBuilder buffer = new StringBuilder();
+            buffer.append("Message");
+            if (message.imapUid != 0) {
+                buffer.append(" IMAP uid: ").append(message.imapUid);
+            }
+            if (message.uid != null) {
+                buffer.append(" uid: ").append(message.uid);
+            }
+            buffer.append(" ItemId: ").append(message.itemId.id);
+            buffer.append(" ChangeKey: ").append(message.itemId.changeKey);
+            LOGGER.debug(buffer.toString());
+        }
+        return message;
+    }
+
+    @Override
+    public MessageList searchMessages(String folderPath, Set<String> attributes, Condition condition) throws IOException {
+        MessageList messages = new MessageList();
+        List<EWSMethod.Item> responses = searchItems(folderPath, attributes, condition, FolderQueryTraversal.SHALLOW, 0);
+
+        for (EWSMethod.Item response : responses) {
+            if (MESSAGE_TYPES.contains(response.type)) {
+                Message message = buildMessage(response);
+                message.messageList = messages;
+                messages.add(message);
+            }
+        }
+        Collections.sort(messages);
+        return messages;
+    }
+
+    protected List<EWSMethod.Item> searchItems(String folderPath, Set<String> attributes, Condition condition, FolderQueryTraversal folderQueryTraversal, int maxCount) throws IOException {
+        int offset = 0;
+        List<EWSMethod.Item> results = new ArrayList<EWSMethod.Item>();
+        FindItemMethod findItemMethod;
+        do {
+            int fetchCount = PAGE_SIZE;
+            if (maxCount > 0) {
+                fetchCount = Math.min(PAGE_SIZE, maxCount - offset);
+            }
+            findItemMethod = new FindItemMethod(folderQueryTraversal, BaseShape.ID_ONLY, getFolderId(folderPath), offset, fetchCount);
+            for (String attribute : attributes) {
+                findItemMethod.addAdditionalProperty(Field.get(attribute));
+            }
+            if (condition != null && !condition.isEmpty()) {
+                findItemMethod.setSearchExpression((SearchExpression) condition);
+            }
+            executeMethod(findItemMethod);
+            results.addAll(findItemMethod.getResponseItems());
+            offset = results.size();
+        } while (!(findItemMethod.includesLastItemInRange || (maxCount > 0 && offset == maxCount)));
+        return results;
+    }
+
+    protected static class MultiCondition extends ExchangeSession.MultiCondition implements SearchExpression {
+        protected MultiCondition(Operator operator, Condition... condition) {
+            super(operator, condition);
+        }
+
+        public void appendTo(StringBuilder buffer) {
+            int actualConditionCount = 0;
+            for (Condition condition : conditions) {
+                if (!condition.isEmpty()) {
+                    actualConditionCount++;
+                }
+            }
+            if (actualConditionCount > 0) {
+                if (actualConditionCount > 1) {
+                    buffer.append("<t:").append(operator.toString()).append('>');
+                }
+
+                for (Condition condition : conditions) {
+                    condition.appendTo(buffer);
+                }
+
+                if (actualConditionCount > 1) {
+                    buffer.append("</t:").append(operator.toString()).append('>');
+                }
+            }
+        }
+    }
+
+    protected static class NotCondition extends ExchangeSession.NotCondition implements SearchExpression {
+        protected NotCondition(Condition condition) {
+            super(condition);
+        }
+
+        public void appendTo(StringBuilder buffer) {
+            buffer.append("<t:Not>");
+            condition.appendTo(buffer);
+            buffer.append("</t:Not>");
+        }
+    }
+
+
+    protected static class AttributeCondition extends ExchangeSession.AttributeCondition implements SearchExpression {
+        protected ContainmentMode containmentMode;
+        protected ContainmentComparison containmentComparison;
+
+        protected AttributeCondition(String attributeName, Operator operator, String value) {
+            super(attributeName, operator, value);
+        }
+
+        protected AttributeCondition(String attributeName, Operator operator, String value,
+                                     ContainmentMode containmentMode, ContainmentComparison containmentComparison) {
+            super(attributeName, operator, value);
+            this.containmentMode = containmentMode;
+            this.containmentComparison = containmentComparison;
+        }
+
+        protected FieldURI getFieldURI() {
+            FieldURI fieldURI = Field.get(attributeName);
+            if (fieldURI == null) {
+                throw new IllegalArgumentException("Unknown field: " + attributeName);
+            }
+            return fieldURI;
+        }
+
+        protected Operator getOperator() {
+            return operator;
+        }
+
+        public void appendTo(StringBuilder buffer) {
+            buffer.append("<t:").append(operator.toString());
+            if (containmentMode != null) {
+                containmentMode.appendTo(buffer);
+            }
+            if (containmentComparison != null) {
+                containmentComparison.appendTo(buffer);
+            }
+            buffer.append('>');
+            FieldURI fieldURI = getFieldURI();
+            fieldURI.appendTo(buffer);
+
+            if (operator != Operator.Contains) {
+                buffer.append("<t:FieldURIOrConstant>");
+            }
+            buffer.append("<t:Constant Value=\"");
+            // encode urlcompname
+            if (fieldURI instanceof ExtendedFieldURI && "0x10f3".equals(((ExtendedFieldURI) fieldURI).propertyTag)) {
+                buffer.append(StringUtil.xmlEncodeAttribute(StringUtil.encodeUrlcompname(value)));
+            } else if (fieldURI instanceof ExtendedFieldURI
+                    && ((ExtendedFieldURI) fieldURI).propertyType == ExtendedFieldURI.PropertyType.Integer) {
+                // check value
+                try {
+                    Integer.parseInt(value);
+                    buffer.append(value);
+                } catch (NumberFormatException e) {
+                    // invalid value, replace with 0
+                    buffer.append('0');
+                }
+            } else {
+                buffer.append(StringUtil.xmlEncodeAttribute(value));
+            }
+            buffer.append("\"/>");
+            if (operator != Operator.Contains) {
+                buffer.append("</t:FieldURIOrConstant>");
+            }
+
+            buffer.append("</t:").append(operator.toString()).append('>');
+        }
+
+        public boolean isMatch(ExchangeSession.Contact contact) {
+            String lowerCaseValue = value.toLowerCase();
+
+            String actualValue = contact.get(attributeName);
+            if (actualValue == null) {
+                return false;
+            }
+            actualValue = actualValue.toLowerCase();
+            if (operator == Operator.IsEqualTo) {
+                return lowerCaseValue.equals(actualValue);
+            } else {
+                return operator == Operator.Contains && ((containmentMode.equals(ContainmentMode.Substring) && actualValue.contains(lowerCaseValue)) ||
+                        (containmentMode.equals(ContainmentMode.Prefixed) && actualValue.startsWith(lowerCaseValue)));
+            }
+        }
+
+    }
+
+    protected static class HeaderCondition extends AttributeCondition {
+
+        protected HeaderCondition(String attributeName, Operator operator, String value) {
+            super(attributeName, operator, value);
+        }
+
+        @Override
+        protected FieldURI getFieldURI() {
+            return new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.InternetHeaders, attributeName);
+        }
+
+    }
+
+    protected static class IsNullCondition implements ExchangeSession.Condition, SearchExpression {
+        protected final String attributeName;
+
+        protected IsNullCondition(String attributeName) {
+            this.attributeName = attributeName;
+        }
+
+        public void appendTo(StringBuilder buffer) {
+            buffer.append("<t:Not><t:Exists>");
+            Field.get(attributeName).appendTo(buffer);
+            buffer.append("</t:Exists></t:Not>");
+        }
+
+        public boolean isEmpty() {
+            return false;
+        }
+
+        public boolean isMatch(ExchangeSession.Contact contact) {
+            String actualValue = contact.get(attributeName);
+            return actualValue == null;
+        }
+
+    }
+
+    @Override
+    public ExchangeSession.MultiCondition and(Condition... condition) {
+        return new MultiCondition(Operator.And, condition);
+    }
+
+    @Override
+    public ExchangeSession.MultiCondition or(Condition... condition) {
+        return new MultiCondition(Operator.Or, condition);
+    }
+
+    @Override
+    public Condition not(Condition condition) {
+        return new NotCondition(condition);
+    }
+
+    @Override
+    public Condition isEqualTo(String attributeName, String value) {
+        return new AttributeCondition(attributeName, Operator.IsEqualTo, value);
+    }
+
+    @Override
+    public Condition isEqualTo(String attributeName, int value) {
+        return new AttributeCondition(attributeName, Operator.IsEqualTo, String.valueOf(value));
+    }
+
+    @Override
+    public Condition headerIsEqualTo(String headerName, String value) {
+        return new HeaderCondition(headerName, Operator.IsEqualTo, value);
+    }
+
+    @Override
+    public Condition gte(String attributeName, String value) {
+        return new AttributeCondition(attributeName, Operator.IsGreaterThanOrEqualTo, value);
+    }
+
+    @Override
+    public Condition lte(String attributeName, String value) {
+        return new AttributeCondition(attributeName, Operator.IsLessThanOrEqualTo, value);
+    }
+
+    @Override
+    public Condition lt(String attributeName, String value) {
+        return new AttributeCondition(attributeName, Operator.IsLessThan, value);
+    }
+
+    @Override
+    public Condition gt(String attributeName, String value) {
+        return new AttributeCondition(attributeName, Operator.IsGreaterThan, value);
+    }
+
+    @Override
+    public Condition contains(String attributeName, String value) {
+        return new AttributeCondition(attributeName, Operator.Contains, value, ContainmentMode.Substring, ContainmentComparison.IgnoreCase);
+    }
+
+    @Override
+    public Condition startsWith(String attributeName, String value) {
+        return new AttributeCondition(attributeName, Operator.Contains, value, ContainmentMode.Prefixed, ContainmentComparison.IgnoreCase);
+    }
+
+    @Override
+    public Condition isNull(String attributeName) {
+        return new IsNullCondition(attributeName);
+    }
+
+    @Override
+    public Condition isTrue(String attributeName) {
+        return new AttributeCondition(attributeName, Operator.IsEqualTo, "true");
+    }
+
+    @Override
+    public Condition isFalse(String attributeName) {
+        return new AttributeCondition(attributeName, Operator.IsEqualTo, "false");
+    }
+
+    protected static final HashSet<FieldURI> FOLDER_PROPERTIES = new HashSet<FieldURI>();
+
+    static {
+        FOLDER_PROPERTIES.add(Field.get("urlcompname"));
+        FOLDER_PROPERTIES.add(Field.get("folderDisplayName"));
+        FOLDER_PROPERTIES.add(Field.get("lastmodified"));
+        FOLDER_PROPERTIES.add(Field.get("folderclass"));
+        FOLDER_PROPERTIES.add(Field.get("ctag"));
+        FOLDER_PROPERTIES.add(Field.get("unread"));
+        FOLDER_PROPERTIES.add(Field.get("hassubs"));
+        FOLDER_PROPERTIES.add(Field.get("uidNext"));
+        FOLDER_PROPERTIES.add(Field.get("highestUid"));
+    }
+
+    protected Folder buildFolder(EWSMethod.Item item) {
+        Folder folder = new Folder();
+        folder.folderId = new FolderId(item);
+        folder.displayName = item.get(Field.get("folderDisplayName").getResponseName());
+        folder.folderClass = item.get(Field.get("folderclass").getResponseName());
+        folder.etag = item.get(Field.get("lastmodified").getResponseName());
+        folder.ctag = item.get(Field.get("ctag").getResponseName());
+        folder.unreadCount = item.getInt(Field.get("unread").getResponseName());
+        folder.hasChildren = item.getBoolean(Field.get("hassubs").getResponseName());
+        // noInferiors not implemented
+        folder.uidNext = item.getInt(Field.get("uidNext").getResponseName());
+        return folder;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    public List<ExchangeSession.Folder> getSubFolders(String folderPath, Condition condition, boolean recursive) throws IOException {
+        String baseFolderPath = folderPath;
+        if (baseFolderPath.startsWith("/users/")) {
+            int index = baseFolderPath.indexOf('/', "/users/".length());
+            if (index >= 0) {
+                baseFolderPath = baseFolderPath.substring(index + 1);
+            }
+        }
+        List<ExchangeSession.Folder> folders = new ArrayList<ExchangeSession.Folder>();
+        appendSubFolders(folders, baseFolderPath, getFolderId(folderPath), condition, recursive);
+        return folders;
+    }
+
+    protected void appendSubFolders(List<ExchangeSession.Folder> folders,
+                                    String parentFolderPath, FolderId parentFolderId,
+                                    Condition condition, boolean recursive) throws IOException {
+        FindFolderMethod findFolderMethod = new FindFolderMethod(FolderQueryTraversal.SHALLOW,
+                BaseShape.ID_ONLY, parentFolderId, FOLDER_PROPERTIES, (SearchExpression) condition);
+        executeMethod(findFolderMethod);
+        for (EWSMethod.Item item : findFolderMethod.getResponseItems()) {
+            Folder folder = buildFolder(item);
+            if (parentFolderPath.length() > 0) {
+                if (parentFolderPath.endsWith("/")) {
+                    folder.folderPath = parentFolderPath + item.get(Field.get("folderDisplayName").getResponseName());
+                } else {
+                    folder.folderPath = parentFolderPath + '/' + item.get(Field.get("folderDisplayName").getResponseName());
+                }
+            } else if (folderIdMap.get(folder.folderId.value) != null) {
+                folder.folderPath = folderIdMap.get(folder.folderId.value);
+            } else {
+                folder.folderPath = item.get(Field.get("folderDisplayName").getResponseName());
+            }
+            folders.add(folder);
+            if (recursive && folder.hasChildren) {
+                appendSubFolders(folders, folder.folderPath, folder.folderId, condition, recursive);
+            }
+        }
+    }
+
+    /**
+     * Get folder by path.
+     *
+     * @param folderPath folder path
+     * @return folder object
+     * @throws IOException on error
+     */
+    @Override
+    protected EwsExchangeSession.Folder internalGetFolder(String folderPath) throws IOException {
+        FolderId folderId = getFolderId(folderPath);
+        GetFolderMethod getFolderMethod = new GetFolderMethod(BaseShape.ID_ONLY, folderId, FOLDER_PROPERTIES);
+        executeMethod(getFolderMethod);
+        EWSMethod.Item item = getFolderMethod.getResponseItem();
+        Folder folder;
+        if (item != null) {
+            folder = buildFolder(item);
+            folder.folderPath = folderPath;
+        } else {
+            throw new HttpNotFoundException("Folder " + folderPath + " not found");
+        }
+        return folder;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    public int createFolder(String folderPath, String folderClass, Map<String, String> properties) throws IOException {
+        FolderPath path = new FolderPath(folderPath);
+        EWSMethod.Item folder = new EWSMethod.Item();
+        folder.type = "Folder";
+        folder.put("FolderClass", folderClass);
+        folder.put("DisplayName", path.folderName);
+        // TODO: handle properties
+        CreateFolderMethod createFolderMethod = new CreateFolderMethod(getFolderId(path.parentPath), folder);
+        executeMethod(createFolderMethod);
+        return HttpStatus.SC_CREATED;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    public int updateFolder(String folderPath, Map<String, String> properties) throws IOException {
+        ArrayList<FieldUpdate> updates = new ArrayList<FieldUpdate>();
+        for (Map.Entry<String, String> entry : properties.entrySet()) {
+            updates.add(new FieldUpdate(Field.get(entry.getKey()), entry.getValue()));
+        }
+        UpdateFolderMethod updateFolderMethod = new UpdateFolderMethod(internalGetFolder(folderPath).folderId, updates);
+
+        executeMethod(updateFolderMethod);
+        return HttpStatus.SC_CREATED;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    public void deleteFolder(String folderPath) throws IOException {
+        FolderId folderId = getFolderIdIfExists(folderPath);
+        if (folderId != null) {
+            DeleteFolderMethod deleteFolderMethod = new DeleteFolderMethod(folderId);
+            executeMethod(deleteFolderMethod);
+        } else {
+            LOGGER.debug("Folder " + folderPath + " not found");
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    public void moveMessage(ExchangeSession.Message message, String targetFolder) throws IOException {
+        MoveItemMethod moveItemMethod = new MoveItemMethod(((EwsExchangeSession.Message) message).itemId, getFolderId(targetFolder));
+        executeMethod(moveItemMethod);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    public void copyMessage(ExchangeSession.Message message, String targetFolder) throws IOException {
+        CopyItemMethod copyItemMethod = new CopyItemMethod(((EwsExchangeSession.Message) message).itemId, getFolderId(targetFolder));
+        executeMethod(copyItemMethod);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    public void moveFolder(String folderPath, String targetFolderPath) throws IOException {
+        FolderPath path = new FolderPath(folderPath);
+        FolderPath targetPath = new FolderPath(targetFolderPath);
+        FolderId folderId = getFolderId(folderPath);
+        FolderId toFolderId = getFolderId(targetPath.parentPath);
+        toFolderId.changeKey = null;
+        // move folder
+        if (!path.parentPath.equals(targetPath.parentPath)) {
+            MoveFolderMethod moveFolderMethod = new MoveFolderMethod(folderId, toFolderId);
+            executeMethod(moveFolderMethod);
+        }
+        // rename folder
+        if (!path.folderName.equals(targetPath.folderName)) {
+            ArrayList<FieldUpdate> updates = new ArrayList<FieldUpdate>();
+            updates.add(new FieldUpdate(Field.get("folderDisplayName"), targetPath.folderName));
+            UpdateFolderMethod updateFolderMethod = new UpdateFolderMethod(folderId, updates);
+            executeMethod(updateFolderMethod);
+        }
+    }
+
+    @Override
+    public void moveItem(String sourcePath, String targetPath) throws IOException {
+        FolderPath sourceFolderPath = new FolderPath(sourcePath);
+        Item item = getItem(sourceFolderPath.parentPath, sourceFolderPath.folderName);
+        FolderPath targetFolderPath = new FolderPath(targetPath);
+        FolderId toFolderId = getFolderId(targetFolderPath.parentPath);
+        MoveItemMethod moveItemMethod = new MoveItemMethod(((Event) item).itemId, toFolderId);
+        executeMethod(moveItemMethod);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    protected void moveToTrash(ExchangeSession.Message message) throws IOException {
+        MoveItemMethod moveItemMethod = new MoveItemMethod(((EwsExchangeSession.Message) message).itemId, getFolderId(TRASH));
+        executeMethod(moveItemMethod);
+    }
+
+    protected class Contact extends ExchangeSession.Contact {
+        // item id
+        ItemId itemId;
+
+        protected Contact(EWSMethod.Item response) throws DavMailException {
+            itemId = new ItemId(response);
+
+            permanentUrl = response.get(Field.get("permanenturl").getResponseName());
+            etag = response.get(Field.get("etag").getResponseName());
+            displayName = response.get(Field.get("displayname").getResponseName());
+            itemName = StringUtil.decodeUrlcompname(response.get(Field.get("urlcompname").getResponseName()));
+            // workaround for missing urlcompname in Exchange 2010
+            if (itemName == null) {
+                itemName = StringUtil.base64ToUrl(itemId.id) + ".EML";
+            }
+            for (String attributeName : CONTACT_ATTRIBUTES) {
+                String value = response.get(Field.get(attributeName).getResponseName());
+                if (value != null && value.length() > 0) {
+                    if ("bday".equals(attributeName) || "anniversary".equals(attributeName) || "lastmodified".equals(attributeName) || "datereceived".equals(attributeName)) {
+                        value = convertDateFromExchange(value);
+                    }
+                    put(attributeName, value);
+                }
+            }
+        }
+
+        /**
+         * @inheritDoc
+         */
+        protected Contact(String folderPath, String itemName, Map<String, String> properties, String etag, String noneMatch) {
+            super(folderPath, itemName, properties, etag, noneMatch);
+        }
+
+        /**
+         * Empty constructor for GalFind
+         */
+        protected Contact() {
+        }
+
+        protected void buildProperties(List<FieldUpdate> updates) {
+            for (Map.Entry<String, String> entry : entrySet()) {
+                if ("photo".equals(entry.getKey())) {
+                    updates.add(Field.createFieldUpdate("haspicture", "true"));
+                } else if (!entry.getKey().startsWith("email") && !entry.getKey().startsWith("smtpemail")
+                        && !entry.getKey().equals("fileas")) {
+                    updates.add(Field.createFieldUpdate(entry.getKey(), entry.getValue()));
+                }
+            }
+            if (get("fileas") != null) {
+                updates.add(Field.createFieldUpdate("fileas", get("fileas")));
+            }
+            // handle email addresses
+            IndexedFieldUpdate emailFieldUpdate = null;
+            for (Map.Entry<String, String> entry : entrySet()) {
+                if (entry.getKey().startsWith("smtpemail") && entry.getValue() != null) {
+                    if (emailFieldUpdate == null) {
+                        emailFieldUpdate = new IndexedFieldUpdate("EmailAddresses");
+                    }
+                    emailFieldUpdate.addFieldValue(Field.createFieldUpdate(entry.getKey(), entry.getValue()));
+                }
+            }
+            if (emailFieldUpdate != null) {
+                updates.add(emailFieldUpdate);
+            }
+        }
+
+
+        /**
+         * Create or update contact
+         *
+         * @return action result
+         * @throws IOException on error
+         */
+        public ItemResult createOrUpdate() throws IOException {
+            String photo = get("photo");
+
+            ItemResult itemResult = new ItemResult();
+            EWSMethod createOrUpdateItemMethod;
+
+            // first try to load existing event
+            String currentEtag = null;
+            ItemId currentItemId = null;
+            FileAttachment currentFileAttachment = null;
+            EWSMethod.Item currentItem = getEwsItem(folderPath, itemName);
+            if (currentItem != null) {
+                currentItemId = new ItemId(currentItem);
+                currentEtag = currentItem.get(Field.get("etag").getResponseName());
+
+                // load current picture
+                GetItemMethod getItemMethod = new GetItemMethod(BaseShape.ID_ONLY, currentItemId, false);
+                getItemMethod.addAdditionalProperty(Field.get("attachments"));
+                executeMethod(getItemMethod);
+                EWSMethod.Item item = getItemMethod.getResponseItem();
+                if (item != null) {
+                    currentFileAttachment = item.getAttachmentByName("ContactPicture.jpg");
+                }
+            }
+            if ("*".equals(noneMatch)) {
+                // create requested
+                if (currentItemId != null) {
+                    itemResult.status = HttpStatus.SC_PRECONDITION_FAILED;
+                    return itemResult;
+                }
+            } else if (etag != null) {
+                // update requested
+                if (currentItemId == null || !etag.equals(currentEtag)) {
+                    itemResult.status = HttpStatus.SC_PRECONDITION_FAILED;
+                    return itemResult;
+                }
+            }
+
+            List<FieldUpdate> properties = new ArrayList<FieldUpdate>();
+            if (currentItemId != null) {
+                buildProperties(properties);
+                // update
+                createOrUpdateItemMethod = new UpdateItemMethod(MessageDisposition.SaveOnly,
+                        ConflictResolution.AlwaysOverwrite,
+                        SendMeetingInvitationsOrCancellations.SendToNone,
+                        currentItemId, properties);
+            } else {
+                // create
+                EWSMethod.Item newItem = new EWSMethod.Item();
+                newItem.type = "Contact";
+                // force urlcompname on create
+                properties.add(Field.createFieldUpdate("urlcompname", convertItemNameToEML(itemName)));
+                buildProperties(properties);
+                newItem.setFieldUpdates(properties);
+                createOrUpdateItemMethod = new CreateItemMethod(MessageDisposition.SaveOnly, getFolderId(folderPath), newItem);
+            }
+            executeMethod(createOrUpdateItemMethod);
+
+            itemResult.status = createOrUpdateItemMethod.getStatusCode();
+            if (itemResult.status == HttpURLConnection.HTTP_OK) {
+                //noinspection VariableNotUsedInsideIf
+                if (etag == null) {
+                    itemResult.status = HttpStatus.SC_CREATED;
+                    LOGGER.debug("Created contact " + getHref());
+                } else {
+                    LOGGER.debug("Updated contact " + getHref());
+                }
+            } else {
+                return itemResult;
+            }
+
+            ItemId newItemId = new ItemId(createOrUpdateItemMethod.getResponseItem());
+
+            // disable contact picture handling on Exchange 2007
+            if (!"Exchange2007_SP1".equals(serverVersion)) {
+                // first delete current picture
+                if (currentFileAttachment != null) {
+                    DeleteAttachmentMethod deleteAttachmentMethod = new DeleteAttachmentMethod(currentFileAttachment.attachmentId);
+                    executeMethod(deleteAttachmentMethod);
+                }
+
+                if (photo != null) {
+                    // convert image to jpeg
+                    byte[] resizedImageBytes = IOUtil.resizeImage(Base64.decodeBase64(photo.getBytes()), 90);
+
+                    FileAttachment attachment = new FileAttachment("ContactPicture.jpg", "image/jpeg", new String(Base64.encodeBase64(resizedImageBytes)));
+                    attachment.setIsContactPhoto(true);
+
+                    // update photo attachment
+                    CreateAttachmentMethod createAttachmentMethod = new CreateAttachmentMethod(newItemId, attachment);
+                    executeMethod(createAttachmentMethod);
+                }
+            }
+
+            GetItemMethod getItemMethod = new GetItemMethod(BaseShape.ID_ONLY, newItemId, false);
+            getItemMethod.addAdditionalProperty(Field.get("etag"));
+            executeMethod(getItemMethod);
+            itemResult.etag = getItemMethod.getResponseItem().get(Field.get("etag").getResponseName());
+
+            return itemResult;
+        }
+    }
+
+    protected class Event extends ExchangeSession.Event {
+        // item id
+        ItemId itemId;
+        String type;
+        boolean isException;
+
+        protected Event(EWSMethod.Item response) {
+            itemId = new ItemId(response);
+
+            type = response.type;
+
+            permanentUrl = response.get(Field.get("permanenturl").getResponseName());
+            etag = response.get(Field.get("etag").getResponseName());
+            displayName = response.get(Field.get("displayname").getResponseName());
+            subject = response.get(Field.get("subject").getResponseName());
+            itemName = StringUtil.decodeUrlcompname(response.get(Field.get("urlcompname").getResponseName()));
+            // workaround for missing urlcompname in Exchange 2010
+            if (itemName == null) {
+                itemName = StringUtil.base64ToUrl(itemId.id) + ".EML";
+            }
+            String instancetype = response.get(Field.get("instancetype").getResponseName());
+            boolean isrecurring = "true".equals(response.get(Field.get("isrecurring").getResponseName()));
+            String calendaritemtype = response.get(Field.get("calendaritemtype").getResponseName());
+            isException = "3".equals(instancetype) || (isrecurring && "Single".equals(calendaritemtype));
+        }
+
+        /**
+         * @inheritDoc
+         */
+        protected Event(String folderPath, String itemName, String contentClass, String itemBody, String etag, String noneMatch) throws IOException {
+            super(folderPath, itemName, contentClass, itemBody, etag, noneMatch);
+        }
+
+        @Override
+        public ItemResult createOrUpdate() throws IOException {
+            if (vCalendar.isTodo() && isMainCalendar(folderPath)) {
+                // task item, move to tasks folder
+                folderPath = TASKS;
+            }
+
+            ItemResult itemResult = new ItemResult();
+            EWSMethod createOrUpdateItemMethod;
+
+            // first try to load existing event
+            String currentEtag = null;
+            ItemId currentItemId = null;
+            String ownerResponseReply = null;
+
+            EWSMethod.Item currentItem = getEwsItem(folderPath, itemName);
+            if (currentItem != null) {
+                currentItemId = new ItemId(currentItem);
+                currentEtag = currentItem.get(Field.get("etag").getResponseName());
+                LOGGER.debug("Existing item found with etag: " + currentEtag + " client etag: " + etag + " id: " + currentItemId.id);
+            }
+            if ("*".equals(noneMatch)) {
+                // create requested
+                if (currentItemId != null) {
+                    itemResult.status = HttpStatus.SC_PRECONDITION_FAILED;
+                    return itemResult;
+                }
+            } else if (etag != null) {
+                // update requested
+                if (currentItemId == null || !etag.equals(currentEtag)) {
+                    itemResult.status = HttpStatus.SC_PRECONDITION_FAILED;
+                    return itemResult;
+                }
+            }
+            if (vCalendar.isTodo()) {
+                // create or update task method
+                EWSMethod.Item newItem = new EWSMethod.Item();
+                newItem.type = "Task";
+                List<FieldUpdate> updates = new ArrayList<FieldUpdate>();
+                updates.add(Field.createFieldUpdate("importance", convertPriorityToExchange(vCalendar.getFirstVeventPropertyValue("PRIORITY"))));
+                updates.add(Field.createFieldUpdate("calendaruid", vCalendar.getFirstVeventPropertyValue("UID")));
+                // force urlcompname
+                updates.add(Field.createFieldUpdate("urlcompname", convertItemNameToEML(itemName)));
+                updates.add(Field.createFieldUpdate("subject", vCalendar.getFirstVeventPropertyValue("SUMMARY")));
+                updates.add(Field.createFieldUpdate("description", vCalendar.getFirstVeventPropertyValue("DESCRIPTION")));
+                updates.add(Field.createFieldUpdate("keywords", vCalendar.getFirstVeventPropertyValue("CATEGORIES")));
+                updates.add(Field.createFieldUpdate("startdate", convertTaskDateToZulu(vCalendar.getFirstVeventPropertyValue("DTSTART"))));
+                updates.add(Field.createFieldUpdate("duedate", convertTaskDateToZulu(vCalendar.getFirstVeventPropertyValue("DUE"))));
+                updates.add(Field.createFieldUpdate("datecompleted", convertTaskDateToZulu(vCalendar.getFirstVeventPropertyValue("COMPLETED"))));
+
+                updates.add(Field.createFieldUpdate("commonstart", convertTaskDateToZulu(vCalendar.getFirstVeventPropertyValue("DTSTART"))));
+                updates.add(Field.createFieldUpdate("commonend", convertTaskDateToZulu(vCalendar.getFirstVeventPropertyValue("DUE"))));
+
+                String percentComplete = vCalendar.getFirstVeventPropertyValue("PERCENT-COMPLETE");
+                if (percentComplete == null) {
+                    percentComplete = "0";
+                }
+                updates.add(Field.createFieldUpdate("percentcomplete", percentComplete));
+                String vTodoStatus = vCalendar.getFirstVeventPropertyValue("STATUS");
+                if (vTodoStatus == null) {
+                    updates.add(Field.createFieldUpdate("taskstatus", "NotStarted"));
+                } else {
+                    updates.add(Field.createFieldUpdate("taskstatus", vTodoToTaskStatusMap.get(vTodoStatus)));
+                }
+
+                //updates.add(Field.createFieldUpdate("iscomplete", "COMPLETED".equals(vTodoStatus)?"True":"False"));
+
+                if (currentItemId != null) {
+                    // update
+                    createOrUpdateItemMethod = new UpdateItemMethod(MessageDisposition.SaveOnly,
+                            ConflictResolution.AutoResolve,
+                            SendMeetingInvitationsOrCancellations.SendToNone,
+                            currentItemId, updates);
+                } else {
+                    newItem.setFieldUpdates(updates);
+                    // create
+                    createOrUpdateItemMethod = new CreateItemMethod(MessageDisposition.SaveOnly, SendMeetingInvitations.SendToNone, getFolderId(folderPath), newItem);
+                }
+
+            } else {
+
+                if (currentItemId != null) {
+                    /*Set<FieldUpdate> updates = new HashSet<FieldUpdate>();
+                    // TODO: update properties instead of brute force delete/add
+                    updates.add(new FieldUpdate(Field.get("mimeContent"), new String(Base64.encodeBase64(itemContent))));
+                    // update
+                    createOrUpdateItemMethod = new UpdateItemMethod(MessageDisposition.SaveOnly,
+                           ConflictResolution.AutoResolve,
+                           SendMeetingInvitationsOrCancellations.SendToNone,
+                           currentItemId, updates);*/
+                    // hard method: delete/create on update
+                    DeleteItemMethod deleteItemMethod = new DeleteItemMethod(currentItemId, DeleteType.HardDelete, SendMeetingCancellations.SendToNone);
+                    executeMethod(deleteItemMethod);
+                } //else {
+                // create
+                EWSMethod.Item newItem = new EWSMethod.Item();
+                newItem.type = "CalendarItem";
+                newItem.mimeContent = Base64.encodeBase64(vCalendar.toString().getBytes("UTF-8"));
+                ArrayList<FieldUpdate> updates = new ArrayList<FieldUpdate>();
+                if (!vCalendar.hasVAlarm()) {
+                    updates.add(Field.createFieldUpdate("reminderset", "false"));
+                }
+                //updates.add(Field.createFieldUpdate("outlookmessageclass", "IPM.Appointment"));
+                // force urlcompname
+                updates.add(Field.createFieldUpdate("urlcompname", convertItemNameToEML(itemName)));
+                if (vCalendar.isMeeting()) {
+                    if (vCalendar.isMeetingOrganizer()) {
+                        updates.add(Field.createFieldUpdate("apptstateflags", "1"));
+                    } else {
+                        updates.add(Field.createFieldUpdate("apptstateflags", "3"));
+                    }
+                } else {
+                    updates.add(Field.createFieldUpdate("apptstateflags", "0"));
+                }
+                // store mozilla invitations option
+                String xMozSendInvitations = vCalendar.getFirstVeventPropertyValue("X-MOZ-SEND-INVITATIONS");
+                if (xMozSendInvitations != null) {
+                    updates.add(Field.createFieldUpdate("xmozsendinvitations", xMozSendInvitations));
+                }
+                // handle mozilla alarm
+                String xMozLastack = vCalendar.getFirstVeventPropertyValue("X-MOZ-LASTACK");
+                if (xMozLastack != null) {
+                    updates.add(Field.createFieldUpdate("xmozlastack", xMozLastack));
+                }
+                String xMozSnoozeTime = vCalendar.getFirstVeventPropertyValue("X-MOZ-SNOOZE-TIME");
+                if (xMozSnoozeTime != null) {
+                    updates.add(Field.createFieldUpdate("xmozsnoozetime", xMozSnoozeTime));
+                }
+
+                if (vCalendar.isMeeting() && "Exchange2007_SP1".equals(serverVersion)) {
+                    Set<String> requiredAttendees = new HashSet<String>();
+                    Set<String> optionalAttendees = new HashSet<String>();
+                    List<VProperty> attendeeProperties = vCalendar.getFirstVeventProperties("ATTENDEE");
+                    if (attendeeProperties != null) {
+                        for (VProperty property : attendeeProperties) {
+                            String attendeeEmail = vCalendar.getEmailValue(property);
+                            if (attendeeEmail != null && attendeeEmail.indexOf('@') >= 0) {
+                                if (email.equals(attendeeEmail)) {
+                                    String ownerPartStat = property.getParamValue("PARTSTAT");
+                                    if ("ACCEPTED".equals(ownerPartStat)) {
+                                        ownerResponseReply = "AcceptItem";
+                                        // do not send DeclineItem to avoid deleting target event
+                                        //} else if  ("DECLINED".equals(ownerPartStat)) {
+                                        //    ownerResponseReply = "DeclineItem";
+                                    } else if ("TENTATIVE".equals(ownerPartStat)) {
+                                        ownerResponseReply = "TentativelyAcceptItem";
+                                    }
+                                }
+                                InternetAddress internetAddress = new InternetAddress(attendeeEmail, property.getParamValue("CN"));
+                                String attendeeRole = property.getParamValue("ROLE");
+                                if ("REQ-PARTICIPANT".equals(attendeeRole)) {
+                                    requiredAttendees.add(internetAddress.toString());
+                                } else {
+                                    optionalAttendees.add(internetAddress.toString());
+                                }
+                            }
+                        }
+                    }
+                    List<VProperty> organizerProperties = vCalendar.getFirstVeventProperties("ORGANIZER");
+                    if (organizerProperties != null) {
+                        VProperty property = organizerProperties.get(0);
+                        String organizerEmail = vCalendar.getEmailValue(property);
+                        if (organizerEmail != null && organizerEmail.indexOf('@') >= 0) {
+                            updates.add(Field.createFieldUpdate("from", organizerEmail));
+                        }
+                    }
+
+                    if (requiredAttendees.size() > 0) {
+                        updates.add(Field.createFieldUpdate("to", StringUtil.join(requiredAttendees, ", ")));
+                    }
+                    if (optionalAttendees.size() > 0) {
+                        updates.add(Field.createFieldUpdate("cc", StringUtil.join(optionalAttendees, ", ")));
+                    }
+                }
+
+                // patch allday date values, only on 2007
+                if ("Exchange2007_SP1".equals(serverVersion) && vCalendar.isCdoAllDay()) {
+                    updates.add(Field.createFieldUpdate("dtstart", convertCalendarDateToExchange(vCalendar.getFirstVeventPropertyValue("DTSTART"))));
+                    updates.add(Field.createFieldUpdate("dtend", convertCalendarDateToExchange(vCalendar.getFirstVeventPropertyValue("DTEND"))));
+                }
+                updates.add(Field.createFieldUpdate("busystatus", "BUSY".equals(vCalendar.getFirstVeventPropertyValue("X-MICROSOFT-CDO-BUSYSTATUS")) ? "Busy" : "Free"));
+                if ("Exchange2007_SP1".equals(serverVersion) && vCalendar.isCdoAllDay()) {
+                    updates.add(Field.createFieldUpdate("meetingtimezone", vCalendar.getVTimezone().getPropertyValue("TZID")));
+                }
+
+                newItem.setFieldUpdates(updates);
+                createOrUpdateItemMethod = new CreateItemMethod(MessageDisposition.SaveOnly, SendMeetingInvitations.SendToNone, getFolderId(folderPath), newItem);
+                // force context Timezone on Exchange 2010
+                if (serverVersion != null && serverVersion.startsWith("Exchange2010")) {
+                    createOrUpdateItemMethod.setTimezoneContext(EwsExchangeSession.this.getVTimezone().getPropertyValue("TZID"));
+                }
+                //}
+            }
+            executeMethod(createOrUpdateItemMethod);
+
+            itemResult.status = createOrUpdateItemMethod.getStatusCode();
+            if (itemResult.status == HttpURLConnection.HTTP_OK) {
+                //noinspection VariableNotUsedInsideIf
+                if (currentItemId == null) {
+                    itemResult.status = HttpStatus.SC_CREATED;
+                    LOGGER.debug("Created event " + getHref());
+                } else {
+                    LOGGER.warn("Overwritten event " + getHref());
+                }
+            }
+
+            // force responsetype on Exchange 2007
+            if (ownerResponseReply != null) {
+                EWSMethod.Item responseTypeItem = new EWSMethod.Item();
+                responseTypeItem.referenceItemId = new ItemId("ReferenceItemId", createOrUpdateItemMethod.getResponseItem());
+                responseTypeItem.type = ownerResponseReply;
+                createOrUpdateItemMethod = new CreateItemMethod(MessageDisposition.SaveOnly, SendMeetingInvitations.SendToNone, null, responseTypeItem);
+                executeMethod(createOrUpdateItemMethod);
+
+                // force urlcompname again
+                ArrayList<FieldUpdate> updates = new ArrayList<FieldUpdate>();
+                updates.add(Field.createFieldUpdate("urlcompname", convertItemNameToEML(itemName)));
+                createOrUpdateItemMethod = new UpdateItemMethod(MessageDisposition.SaveOnly,
+                        ConflictResolution.AlwaysOverwrite,
+                        SendMeetingInvitationsOrCancellations.SendToNone,
+                        new ItemId(createOrUpdateItemMethod.getResponseItem()),
+                        updates);
+                executeMethod(createOrUpdateItemMethod);
+            }
+
+            ItemId newItemId = new ItemId(createOrUpdateItemMethod.getResponseItem());
+            GetItemMethod getItemMethod = new GetItemMethod(BaseShape.ID_ONLY, newItemId, false);
+            getItemMethod.addAdditionalProperty(Field.get("etag"));
+            executeMethod(getItemMethod);
+            itemResult.etag = getItemMethod.getResponseItem().get(Field.get("etag").getResponseName());
+
+            return itemResult;
+
+        }
+
+        @Override
+        public byte[] getEventContent() throws IOException {
+            byte[] content;
+            if (LOGGER.isDebugEnabled()) {
+                LOGGER.debug("Get event: " + itemName);
+            }
+            try {
+                GetItemMethod getItemMethod;
+                if ("Task".equals(type)) {
+                    getItemMethod = new GetItemMethod(BaseShape.ID_ONLY, itemId, false);
+                    getItemMethod.addAdditionalProperty(Field.get("importance"));
+                    getItemMethod.addAdditionalProperty(Field.get("subject"));
+                    getItemMethod.addAdditionalProperty(Field.get("created"));
+                    getItemMethod.addAdditionalProperty(Field.get("lastmodified"));
+                    getItemMethod.addAdditionalProperty(Field.get("calendaruid"));
+                    getItemMethod.addAdditionalProperty(Field.get("description"));
+                    getItemMethod.addAdditionalProperty(Field.get("percentcomplete"));
+                    getItemMethod.addAdditionalProperty(Field.get("taskstatus"));
+                    getItemMethod.addAdditionalProperty(Field.get("startdate"));
+                    getItemMethod.addAdditionalProperty(Field.get("duedate"));
+                    getItemMethod.addAdditionalProperty(Field.get("datecompleted"));
+                    getItemMethod.addAdditionalProperty(Field.get("keywords"));
+
+                } else if (!"Message".equals(type)) {
+                    getItemMethod = new GetItemMethod(BaseShape.ID_ONLY, itemId, true);
+                    getItemMethod.addAdditionalProperty(Field.get("reminderset"));
+                    getItemMethod.addAdditionalProperty(Field.get("calendaruid"));
+                    getItemMethod.addAdditionalProperty(Field.get("myresponsetype"));
+                    getItemMethod.addAdditionalProperty(Field.get("requiredattendees"));
+                    getItemMethod.addAdditionalProperty(Field.get("optionalattendees"));
+                    getItemMethod.addAdditionalProperty(Field.get("modifiedoccurrences"));
+                    getItemMethod.addAdditionalProperty(Field.get("xmozlastack"));
+                    getItemMethod.addAdditionalProperty(Field.get("xmozsnoozetime"));
+                    getItemMethod.addAdditionalProperty(Field.get("xmozsendinvitations"));
+                } else {
+                    getItemMethod = new GetItemMethod(BaseShape.ID_ONLY, itemId, true);
+                }
+
+                executeMethod(getItemMethod);
+                if ("Task".equals(type)) {
+                    VObject vTimezone = getVTimezone();
+                    VCalendar localVCalendar = new VCalendar();
+                    VObject vTodo = new VObject();
+                    vTodo.type = "VTODO";
+                    localVCalendar.setTimezone(vTimezone);
+                    vTodo.setPropertyValue("LAST-MODIFIED", convertDateFromExchange(getItemMethod.getResponseItem().get(Field.get("lastmodified").getResponseName())));
+                    vTodo.setPropertyValue("CREATED", convertDateFromExchange(getItemMethod.getResponseItem().get(Field.get("created").getResponseName())));
+                    String calendarUid = getItemMethod.getResponseItem().get(Field.get("calendaruid").getResponseName());
+                    if (calendarUid == null) {
+                        // use item id as uid for Exchange created tasks
+                        calendarUid = itemId.id;
+                    }
+                    vTodo.setPropertyValue("UID", calendarUid);
+                    vTodo.setPropertyValue("SUMMARY", getItemMethod.getResponseItem().get(Field.get("subject").getResponseName()));
+                    vTodo.setPropertyValue("DESCRIPTION", getItemMethod.getResponseItem().get(Field.get("description").getResponseName()));
+                    vTodo.setPropertyValue("PRIORITY", convertPriorityFromExchange(getItemMethod.getResponseItem().get(Field.get("importance").getResponseName())));
+                    vTodo.setPropertyValue("PERCENT-COMPLETE", getItemMethod.getResponseItem().get(Field.get("percentcomplete").getResponseName()));
+                    vTodo.setPropertyValue("STATUS", taskTovTodoStatusMap.get(getItemMethod.getResponseItem().get(Field.get("taskstatus").getResponseName())));
+
+                    vTodo.setPropertyValue("DUE;VALUE=DATE", convertDateFromExchangeToTaskDate(getItemMethod.getResponseItem().get(Field.get("duedate").getResponseName())));
+                    vTodo.setPropertyValue("DTSTART;VALUE=DATE", convertDateFromExchangeToTaskDate(getItemMethod.getResponseItem().get(Field.get("startdate").getResponseName())));
+                    vTodo.setPropertyValue("COMPLETED;VALUE=DATE", convertDateFromExchangeToTaskDate(getItemMethod.getResponseItem().get(Field.get("datecompleted").getResponseName())));
+
+                    vTodo.setPropertyValue("CATEGORIES", getItemMethod.getResponseItem().get(Field.get("keywords").getResponseName()));
+
+                    localVCalendar.addVObject(vTodo);
+                    content = localVCalendar.toString().getBytes("UTF-8");
+                } else {
+                    content = getItemMethod.getMimeContent();
+                    if (content == null) {
+                        throw new IOException("empty event body");
+                    }
+                    if (!"CalendarItem".equals(type)) {
+                        content = getICS(new SharedByteArrayInputStream(content));
+                    }
+                    VCalendar localVCalendar = new VCalendar(content, email, getVTimezone());
+                    // remove additional reminder
+                    if (!"true".equals(getItemMethod.getResponseItem().get(Field.get("reminderset").getResponseName()))) {
+                        localVCalendar.removeVAlarm();
+                    }
+                    String calendaruid = getItemMethod.getResponseItem().get(Field.get("calendaruid").getResponseName());
+                    if (calendaruid != null) {
+                        localVCalendar.setFirstVeventPropertyValue("UID", calendaruid);
+                    }
+                    List<EWSMethod.Attendee> attendees = getItemMethod.getResponseItem().getAttendees();
+                    if (attendees != null) {
+                        for (EWSMethod.Attendee attendee : attendees) {
+                            VProperty attendeeProperty = new VProperty("ATTENDEE", "mailto:" + attendee.email);
+                            attendeeProperty.addParam("CN", attendee.name);
+                            String myResponseType = getItemMethod.getResponseItem().get(Field.get("myresponsetype").getResponseName());
+                            if ("Exchange2007_SP1".equals(serverVersion) && email.equalsIgnoreCase(attendee.email) && myResponseType != null) {
+                                attendeeProperty.addParam("PARTSTAT", EWSMethod.responseTypeToPartstat(myResponseType));
+                            } else {
+                                attendeeProperty.addParam("PARTSTAT", attendee.partstat);
+                            }
+                            //attendeeProperty.addParam("RSVP", "TRUE");
+                            attendeeProperty.addParam("ROLE", attendee.role);
+                            localVCalendar.addFirstVeventProperty(attendeeProperty);
+                        }
+                    }
+                    // fix UID and RECURRENCE-ID, broken at least on Exchange 2007
+                    List<EWSMethod.Occurrence> occurences = getItemMethod.getResponseItem().getOccurrences();
+                    if (occurences != null) {
+                        Iterator<VObject> modifiedOccurrencesIterator = localVCalendar.getModifiedOccurrences().iterator();
+                        for (EWSMethod.Occurrence occurrence : occurences) {
+                            if (modifiedOccurrencesIterator.hasNext()) {
+                                VObject modifiedOccurrence = modifiedOccurrencesIterator.next();
+                                // fix uid, should be the same as main VEVENT
+                                if (calendaruid != null) {
+                                    modifiedOccurrence.setPropertyValue("UID", calendaruid);
+                                }
+                                VProperty recurrenceId = modifiedOccurrence.getProperty("RECURRENCE-ID");
+                                if (recurrenceId != null) {
+                                    recurrenceId.removeParam("TZID");
+                                    recurrenceId.getValues().set(0, convertDateFromExchange(occurrence.originalStart));
+                                }
+                            }
+                        }
+                    }
+                    // restore mozilla invitations option
+                    localVCalendar.setFirstVeventPropertyValue("X-MOZ-SEND-INVITATIONS",
+                            getItemMethod.getResponseItem().get(Field.get("xmozsendinvitations").getResponseName()));
+                    // restore mozilla alarm status
+                    localVCalendar.setFirstVeventPropertyValue("X-MOZ-LASTACK",
+                            getItemMethod.getResponseItem().get(Field.get("xmozlastack").getResponseName()));
+                    localVCalendar.setFirstVeventPropertyValue("X-MOZ-SNOOZE-TIME",
+                            getItemMethod.getResponseItem().get(Field.get("xmozsnoozetime").getResponseName()));
+                    // overwrite method
+                    // localVCalendar.setPropertyValue("METHOD", "REQUEST");
+                    content = localVCalendar.toString().getBytes("UTF-8");
+                }
+            } catch (IOException e) {
+                throw buildHttpException(e);
+            } catch (MessagingException e) {
+                throw buildHttpException(e);
+            }
+            return content;
+        }
+    }
+
+    @Override
+    public List<ExchangeSession.Contact> searchContacts(String folderPath, Set<String> attributes, Condition condition, int maxCount) throws IOException {
+        List<ExchangeSession.Contact> contacts = new ArrayList<ExchangeSession.Contact>();
+        List<EWSMethod.Item> responses = searchItems(folderPath, attributes, condition,
+                FolderQueryTraversal.SHALLOW, maxCount);
+
+        for (EWSMethod.Item response : responses) {
+            contacts.add(new Contact(response));
+        }
+        return contacts;
+    }
+
+    @Override
+    protected Condition getCalendarItemCondition(Condition dateCondition) {
+        // tasks in calendar not supported over EWS => do not look for instancetype null
+        return or(
+                // Exchange 2010
+                or(isTrue("isrecurring"),
+                        and(isFalse("isrecurring"), dateCondition)),
+                // Exchange 2007
+                or(isEqualTo("instancetype", 1),
+                        and(isEqualTo("instancetype", 0), dateCondition))
+        );
+    }
+
+    @Override
+    public List<ExchangeSession.Event> getEventMessages(String folderPath) throws IOException {
+        return searchEvents(folderPath, ITEM_PROPERTIES,
+                and(startsWith("outlookmessageclass", "IPM.Schedule.Meeting."),
+                        or(isNull("processed"), isFalse("processed"))));
+    }
+
+    @Override
+    public List<ExchangeSession.Event> searchEvents(String folderPath, Set<String> attributes, Condition condition) throws IOException {
+        List<ExchangeSession.Event> events = new ArrayList<ExchangeSession.Event>();
+        List<EWSMethod.Item> responses = searchItems(folderPath, attributes,
+                condition,
+                FolderQueryTraversal.SHALLOW, 0);
+        for (EWSMethod.Item response : responses) {
+            Event event = new Event(response);
+            if ("Message".equals(event.type)) {
+                // TODO: just exclude
+                // need to check body
+                try {
+                    event.getEventContent();
+                    events.add(event);
+                } catch (HttpException e) {
+                    LOGGER.warn("Ignore invalid event " + event.getHref());
+                }
+                // exclude exceptions
+            } else if (event.isException) {
+                LOGGER.debug("Exclude recurrence exception " + event.getHref());
+            } else {
+                events.add(event);
+            }
+
+        }
+
+        return events;
+    }
+
+    /**
+     * Common item properties
+     */
+    protected static final Set<String> ITEM_PROPERTIES = new HashSet<String>();
+
+    static {
+        ITEM_PROPERTIES.add("etag");
+        ITEM_PROPERTIES.add("displayname");
+        // calendar CdoInstanceType
+        ITEM_PROPERTIES.add("instancetype");
+        ITEM_PROPERTIES.add("urlcompname");
+        ITEM_PROPERTIES.add("subject");
+
+        ITEM_PROPERTIES.add("calendaritemtype");
+        ITEM_PROPERTIES.add("isrecurring");
+    }
+
+    protected static final HashSet<String> EVENT_REQUEST_PROPERTIES = new HashSet<String>();
+
+    static {
+        EVENT_REQUEST_PROPERTIES.add("permanenturl");
+        EVENT_REQUEST_PROPERTIES.add("etag");
+        EVENT_REQUEST_PROPERTIES.add("displayname");
+        EVENT_REQUEST_PROPERTIES.add("subject");
+        EVENT_REQUEST_PROPERTIES.add("urlcompname");
+    }
+
+    protected Set<String> getItemProperties() {
+        return ITEM_PROPERTIES;
+    }
+
+    protected EWSMethod.Item getEwsItem(String folderPath, String itemName) throws IOException {
+        EWSMethod.Item item = null;
+        String urlcompname = convertItemNameToEML(itemName);
+        // workaround for missing urlcompname in Exchange 2010
+        if (isItemId(urlcompname)) {
+            ItemId itemId = new ItemId(StringUtil.urlToBase64(urlcompname.substring(0, itemName.length() - 4)));
+            GetItemMethod getItemMethod = new GetItemMethod(BaseShape.ID_ONLY, itemId, false);
+            for (String attribute : EVENT_REQUEST_PROPERTIES) {
+                getItemMethod.addAdditionalProperty(Field.get(attribute));
+            }
+            executeMethod(getItemMethod);
+            item = getItemMethod.getResponseItem();
+        }
+        // find item by urlcompname
+        if (item == null) {
+            List<EWSMethod.Item> responses = searchItems(folderPath, EVENT_REQUEST_PROPERTIES, isEqualTo("urlcompname", urlcompname), FolderQueryTraversal.SHALLOW, 0);
+            if (!responses.isEmpty()) {
+                item = responses.get(0);
+            }
+        }
+        return item;
+    }
+
+
+    @Override
+    public Item getItem(String folderPath, String itemName) throws IOException {
+        EWSMethod.Item item = getEwsItem(folderPath, itemName);
+        if (item == null && isMainCalendar(folderPath)) {
+            // look for item in task folder, replace extension first
+            if (itemName.endsWith(".ics")) {
+                itemName = itemName.substring(0, itemName.length() - 3) + "EML";
+            }
+            item = getEwsItem(TASKS, itemName);
+        }
+
+        if (item == null) {
+            throw new HttpNotFoundException(itemName + " not found in " + folderPath);
+        }
+
+        String itemType = item.type;
+        if ("Contact".equals(itemType)) {
+            // retrieve Contact properties
+            ItemId itemId = new ItemId(item);
+            GetItemMethod getItemMethod = new GetItemMethod(BaseShape.ID_ONLY, itemId, false);
+            for (String attribute : CONTACT_ATTRIBUTES) {
+                getItemMethod.addAdditionalProperty(Field.get(attribute));
+            }
+            executeMethod(getItemMethod);
+            item = getItemMethod.getResponseItem();
+            if (item == null) {
+                throw new HttpNotFoundException(itemName + " not found in " + folderPath);
+            }
+            return new Contact(item);
+        } else if ("CalendarItem".equals(itemType)
+                || "MeetingRequest".equals(itemType)
+                || "Task".equals(itemType)
+                // VTODOs appear as Messages
+                || "Message".equals(itemType)) {
+            return new Event(item);
+        } else {
+            throw new HttpNotFoundException(itemName + " not found in " + folderPath);
+        }
+
+    }
+
+    @Override
+    public ContactPhoto getContactPhoto(ExchangeSession.Contact contact) throws IOException {
+        ContactPhoto contactPhoto = null;
+
+        GetItemMethod getItemMethod = new GetItemMethod(BaseShape.ID_ONLY, ((EwsExchangeSession.Contact) contact).itemId, false);
+        getItemMethod.addAdditionalProperty(Field.get("attachments"));
+        executeMethod(getItemMethod);
+        EWSMethod.Item item = getItemMethod.getResponseItem();
+        if (item != null) {
+            FileAttachment attachment = item.getAttachmentByName("ContactPicture.jpg");
+            if (attachment == null) {
+                throw new IOException("Missing contact picture");
+            }
+            // get attachment content
+            GetAttachmentMethod getAttachmentMethod = new GetAttachmentMethod(attachment.attachmentId);
+            executeMethod(getAttachmentMethod);
+
+            contactPhoto = new ContactPhoto();
+            contactPhoto.content = getAttachmentMethod.getResponseItem().get("Content");
+            if (attachment.contentType == null) {
+                contactPhoto.contentType = "image/jpeg";
+            } else {
+                contactPhoto.contentType = attachment.contentType;
+            }
+        }
+
+        return contactPhoto;
+    }
+
+    @Override
+    public void deleteItem(String folderPath, String itemName) throws IOException {
+        EWSMethod.Item item = getEwsItem(folderPath, itemName);
+        if (item == null && isMainCalendar(folderPath)) {
+            // look for item in task folder
+            item = getEwsItem(TASKS, itemName);
+        }
+        if (item != null) {
+            DeleteItemMethod deleteItemMethod = new DeleteItemMethod(new ItemId(item), DeleteType.HardDelete, SendMeetingCancellations.SendToNone);
+            executeMethod(deleteItemMethod);
+        }
+    }
+
+    @Override
+    public void processItem(String folderPath, String itemName) throws IOException {
+        EWSMethod.Item item = getEwsItem(folderPath, itemName);
+        if (item != null) {
+            HashMap<String, String> localProperties = new HashMap<String, String>();
+            localProperties.put("processed", "1");
+            localProperties.put("read", "1");
+            UpdateItemMethod updateItemMethod = new UpdateItemMethod(MessageDisposition.SaveOnly,
+                    ConflictResolution.AlwaysOverwrite,
+                    SendMeetingInvitationsOrCancellations.SendToNone,
+                    new ItemId(item), buildProperties(localProperties));
+            executeMethod(updateItemMethod);
+        }
+    }
+
+    @Override
+    public int sendEvent(String icsBody) throws IOException {
+        String itemName = UUID.randomUUID().toString() + ".EML";
+        byte[] mimeContent = new Event(DRAFTS, itemName, "urn:content-classes:calendarmessage", icsBody, null, null).createMimeContent();
+        if (mimeContent == null) {
+            // no recipients, cancel
+            return HttpStatus.SC_NO_CONTENT;
+        } else {
+            sendMessage(null, mimeContent);
+            return HttpStatus.SC_OK;
+        }
+    }
+
+    @Override
+    protected ItemResult internalCreateOrUpdateContact(String folderPath, String itemName, Map<String, String> properties, String etag, String noneMatch) throws IOException {
+        return new Contact(folderPath, itemName, properties, StringUtil.removeQuotes(etag), noneMatch).createOrUpdate();
+    }
+
+    @Override
+    protected ItemResult internalCreateOrUpdateEvent(String folderPath, String itemName, String contentClass, String icsBody, String etag, String noneMatch) throws IOException {
+        return new Event(folderPath, itemName, contentClass, icsBody, StringUtil.removeQuotes(etag), noneMatch).createOrUpdate();
+    }
+
+    @Override
+    public boolean isSharedFolder(String folderPath) {
+        return folderPath.startsWith("/") && !folderPath.toLowerCase().startsWith(currentMailboxPath);
+    }
+
+    @Override
+    public boolean isMainCalendar(String folderPath) {
+        return "calendar".equalsIgnoreCase(folderPath) || (currentMailboxPath + "/calendar").equalsIgnoreCase(folderPath);
+    }
+
+    @Override
+    protected String getFreeBusyData(String attendee, String start, String end, int interval) throws IOException {
+        GetUserAvailabilityMethod getUserAvailabilityMethod = new GetUserAvailabilityMethod(attendee, start, end, interval);
+        executeMethod(getUserAvailabilityMethod);
+        return getUserAvailabilityMethod.getMergedFreeBusy();
+    }
+
+    @Override
+    protected void loadVtimezone() {
+
+        try {
+            String timezoneId = null;
+            if (!"Exchange2007_SP1".equals(serverVersion)) {
+                GetUserConfigurationMethod getUserConfigurationMethod = new GetUserConfigurationMethod();
+                executeMethod(getUserConfigurationMethod);
+                EWSMethod.Item item = getUserConfigurationMethod.getResponseItem();
+                if (item != null) {
+                    timezoneId = item.get("timezone");
+                }
+            } else {
+                timezoneId = getTimezoneidFromOptions();
+            }
+            // failover: use timezone id from settings file
+            if (timezoneId == null) {
+                timezoneId = Settings.getProperty("davmail.timezoneId");
+            }
+            // last failover: use GMT
+            if (timezoneId == null) {
+                LOGGER.warn("Unable to get user timezone, using GMT Standard Time. Set davmail.timezoneId setting to override this.");
+                timezoneId = "GMT Standard Time";
+            }
+
+            createCalendarFolder("davmailtemp", null);
+            EWSMethod.Item item = new EWSMethod.Item();
+            item.type = "CalendarItem";
+            if (!"Exchange2007_SP1".equals(serverVersion)) {
+                SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH);
+                dateFormatter.setTimeZone(GMT_TIMEZONE);
+                Calendar cal = Calendar.getInstance();
+                item.put("Start", dateFormatter.format(cal.getTime()));
+                cal.add(Calendar.DAY_OF_MONTH, 1);
+                item.put("End", dateFormatter.format(cal.getTime()));
+                item.put("StartTimeZone", timezoneId);
+            } else {
+                item.put("MeetingTimeZone", timezoneId);
+            }
+            CreateItemMethod createItemMethod = new CreateItemMethod(MessageDisposition.SaveOnly, SendMeetingInvitations.SendToNone, getFolderId("davmailtemp"), item);
+            executeMethod(createItemMethod);
+            item = createItemMethod.getResponseItem();
+            VCalendar vCalendar = new VCalendar(getContent(new ItemId(item)), email, null);
+            this.vTimezone = vCalendar.getVTimezone();
+            // delete temporary folder
+            deleteFolder("davmailtemp");
+        } catch (IOException e) {
+            LOGGER.warn("Unable to get VTIMEZONE info: " + e, e);
+        }
+    }
+
+    protected String getTimezoneidFromOptions() {
+        String result = null;
+        // get user mail URL from html body
+        BufferedReader optionsPageReader = null;
+        GetMethod optionsMethod = new GetMethod("/owa/?ae=Options&t=Regional");
+        try {
+            DavGatewayHttpClientFacade.executeGetMethod(httpClient, optionsMethod, false);
+            optionsPageReader = new BufferedReader(new InputStreamReader(optionsMethod.getResponseBodyAsStream()));
+            String line;
+            // find email
+            //noinspection StatementWithEmptyBody
+            while ((line = optionsPageReader.readLine()) != null
+                    && (line.indexOf("tblTmZn") == -1)
+                    && (line.indexOf("selTmZn") == -1)) {
+            }
+            if (line != null) {
+                if (line.indexOf("tblTmZn") >= 0) {
+                    int start = line.indexOf("oV=\"") + 4;
+                    int end = line.indexOf('\"', start);
+                    result = line.substring(start, end);
+                } else {
+                    int end = line.lastIndexOf("\" selected>");
+                    int start = line.lastIndexOf('\"', end - 1);
+                    result = line.substring(start + 1, end);
+                }
+            }
+        } catch (IOException e) {
+            LOGGER.error("Error parsing options page at " + optionsMethod.getPath());
+        } finally {
+            if (optionsPageReader != null) {
+                try {
+                    optionsPageReader.close();
+                } catch (IOException e) {
+                    LOGGER.error("Error parsing options page at " + optionsMethod.getPath());
+                }
+            }
+            optionsMethod.releaseConnection();
+        }
+
+        return result;
+    }
+
+
+    protected FolderId getFolderId(String folderPath) throws IOException {
+        FolderId folderId = getFolderIdIfExists(folderPath);
+        if (folderId == null) {
+            throw new HttpNotFoundException("Folder '" + folderPath + "' not found");
+        }
+        return folderId;
+    }
+
+    protected static final String USERS_ROOT = "/users/";
+
+    protected FolderId getFolderIdIfExists(String folderPath) throws IOException {
+        String lowerCaseFolderPath = folderPath.toLowerCase();
+        if (currentMailboxPath.equals(lowerCaseFolderPath)) {
+            return getSubFolderIdIfExists(null, "");
+        } else if (lowerCaseFolderPath.startsWith(currentMailboxPath + '/')) {
+            return getSubFolderIdIfExists(null, folderPath.substring(currentMailboxPath.length() + 1));
+        } else if (folderPath.startsWith("/users/")) {
+            int slashIndex = folderPath.indexOf('/', USERS_ROOT.length());
+            String mailbox;
+            String subFolderPath;
+            if (slashIndex >= 0) {
+                mailbox = folderPath.substring(USERS_ROOT.length(), slashIndex);
+                subFolderPath = folderPath.substring(slashIndex + 1);
+            } else {
+                mailbox = folderPath.substring(USERS_ROOT.length());
+                subFolderPath = "";
+            }
+            return getSubFolderIdIfExists(mailbox, subFolderPath);
+        } else {
+            return getSubFolderIdIfExists(null, folderPath);
+        }
+    }
+
+    protected FolderId getSubFolderIdIfExists(String mailbox, String folderPath) throws IOException {
+        String[] folderNames;
+        FolderId currentFolderId;
+
+        if (folderPath.startsWith(PUBLIC_ROOT)) {
+            currentFolderId = DistinguishedFolderId.getInstance(mailbox, DistinguishedFolderId.Name.publicfoldersroot);
+            folderNames = folderPath.substring(PUBLIC_ROOT.length()).split("/");
+        } else if (folderPath.startsWith(ARCHIVE_ROOT)) {
+            currentFolderId = DistinguishedFolderId.getInstance(mailbox, DistinguishedFolderId.Name.archivemsgfolderroot);
+            folderNames = folderPath.substring(ARCHIVE_ROOT.length()).split("/");
+        } else if (folderPath.startsWith(INBOX) || folderPath.startsWith(LOWER_CASE_INBOX)) {
+            currentFolderId = DistinguishedFolderId.getInstance(mailbox, DistinguishedFolderId.Name.inbox);
+            folderNames = folderPath.substring(INBOX.length()).split("/");
+        } else if (folderPath.startsWith(CALENDAR)) {
+            currentFolderId = DistinguishedFolderId.getInstance(mailbox, DistinguishedFolderId.Name.calendar);
+            folderNames = folderPath.substring(CALENDAR.length()).split("/");
+        } else if (folderPath.startsWith(TASKS)) {
+            currentFolderId = DistinguishedFolderId.getInstance(mailbox, DistinguishedFolderId.Name.tasks);
+            folderNames = folderPath.substring(TASKS.length()).split("/");
+        } else if (folderPath.startsWith(CONTACTS)) {
+            currentFolderId = DistinguishedFolderId.getInstance(mailbox, DistinguishedFolderId.Name.contacts);
+            folderNames = folderPath.substring(CONTACTS.length()).split("/");
+        } else if (folderPath.startsWith(SENT)) {
+            currentFolderId = DistinguishedFolderId.getInstance(mailbox, DistinguishedFolderId.Name.sentitems);
+            folderNames = folderPath.substring(SENT.length()).split("/");
+        } else if (folderPath.startsWith(DRAFTS)) {
+            currentFolderId = DistinguishedFolderId.getInstance(mailbox, DistinguishedFolderId.Name.drafts);
+            folderNames = folderPath.substring(DRAFTS.length()).split("/");
+        } else if (folderPath.startsWith(TRASH)) {
+            currentFolderId = DistinguishedFolderId.getInstance(mailbox, DistinguishedFolderId.Name.deleteditems);
+            folderNames = folderPath.substring(TRASH.length()).split("/");
+        } else if (folderPath.startsWith(JUNK)) {
+            currentFolderId = DistinguishedFolderId.getInstance(mailbox, DistinguishedFolderId.Name.junkemail);
+            folderNames = folderPath.substring(JUNK.length()).split("/");
+        } else if (folderPath.startsWith(UNSENT)) {
+            currentFolderId = DistinguishedFolderId.getInstance(mailbox, DistinguishedFolderId.Name.outbox);
+            folderNames = folderPath.substring(UNSENT.length()).split("/");
+        } else {
+            currentFolderId = DistinguishedFolderId.getInstance(mailbox, DistinguishedFolderId.Name.msgfolderroot);
+            folderNames = folderPath.split("/");
+        }
+        for (String folderName : folderNames) {
+            if (folderName.length() > 0) {
+                currentFolderId = getSubFolderByName(currentFolderId, folderName);
+                if (currentFolderId == null) {
+                    break;
+                }
+            }
+        }
+        return currentFolderId;
+    }
+
+    protected FolderId getSubFolderByName(FolderId parentFolderId, String folderName) throws IOException {
+        FolderId folderId = null;
+        FindFolderMethod findFolderMethod = new FindFolderMethod(
+                FolderQueryTraversal.SHALLOW,
+                BaseShape.ID_ONLY,
+                parentFolderId,
+                FOLDER_PROPERTIES,
+                new TwoOperandExpression(TwoOperandExpression.Operator.IsEqualTo,
+                        Field.get("folderDisplayName"), folderName)
+        );
+        executeMethod(findFolderMethod);
+        EWSMethod.Item item = findFolderMethod.getResponseItem();
+        if (item != null) {
+            folderId = new FolderId(item);
+        }
+        return folderId;
+    }
+
+    protected void executeMethod(EWSMethod ewsMethod) throws IOException {
+        try {
+            ewsMethod.setServerVersion(serverVersion);
+            httpClient.executeMethod(ewsMethod);
+            if (serverVersion == null) {
+                serverVersion = ewsMethod.getServerVersion();
+            }
+            ewsMethod.checkSuccess();
+        } catch (SocketException e) {
+            LOGGER.error(e + " " + e.getMessage(), e);
+            throw new EWSException(e + " " + e.getMessage());
+        } finally {
+            ewsMethod.releaseConnection();
+        }
+    }
+
+    protected static final HashMap<String, String> GALFIND_ATTRIBUTE_MAP = new HashMap<String, String>();
+
+    static {
+        GALFIND_ATTRIBUTE_MAP.put("imapUid", "Name");
+        GALFIND_ATTRIBUTE_MAP.put("cn", "DisplayName");
+        GALFIND_ATTRIBUTE_MAP.put("givenName", "GivenName");
+        GALFIND_ATTRIBUTE_MAP.put("sn", "Surname");
+        GALFIND_ATTRIBUTE_MAP.put("smtpemail1", "EmailAddress");
+
+        GALFIND_ATTRIBUTE_MAP.put("roomnumber", "OfficeLocation");
+        GALFIND_ATTRIBUTE_MAP.put("street", "BusinessStreet");
+        GALFIND_ATTRIBUTE_MAP.put("l", "BusinessCity");
+        GALFIND_ATTRIBUTE_MAP.put("o", "CompanyName");
+        GALFIND_ATTRIBUTE_MAP.put("postalcode", "BusinessPostalCode");
+        GALFIND_ATTRIBUTE_MAP.put("st", "BusinessState");
+        GALFIND_ATTRIBUTE_MAP.put("co", "BusinessCountryOrRegion");
+
+        GALFIND_ATTRIBUTE_MAP.put("manager", "Manager");
+        GALFIND_ATTRIBUTE_MAP.put("middlename", "Initials");
+        GALFIND_ATTRIBUTE_MAP.put("title", "JobTitle");
+        GALFIND_ATTRIBUTE_MAP.put("department", "Department");
+
+        GALFIND_ATTRIBUTE_MAP.put("otherTelephone", "OtherTelephone");
+        GALFIND_ATTRIBUTE_MAP.put("telephoneNumber", "BusinessPhone");
+        GALFIND_ATTRIBUTE_MAP.put("mobile", "MobilePhone");
+        GALFIND_ATTRIBUTE_MAP.put("facsimiletelephonenumber", "BusinessFax");
+        GALFIND_ATTRIBUTE_MAP.put("secretarycn", "AssistantName");
+    }
+
+    protected static final HashSet<String> IGNORE_ATTRIBUTE_SET = new HashSet<String>();
+
+    static {
+        IGNORE_ATTRIBUTE_SET.add("ContactSource");
+        IGNORE_ATTRIBUTE_SET.add("Culture");
+        IGNORE_ATTRIBUTE_SET.add("AssistantPhone");
+    }
+
+    protected Contact buildGalfindContact(EWSMethod.Item response) {
+        Contact contact = new Contact();
+        contact.setName(response.get("Name"));
+        contact.put("imapUid", response.get("Name"));
+        contact.put("uid", response.get("Name"));
+        if (LOGGER.isDebugEnabled()) {
+            for (Map.Entry<String, String> entry : response.entrySet()) {
+                String key = entry.getKey();
+                if (!IGNORE_ATTRIBUTE_SET.contains(key) && !GALFIND_ATTRIBUTE_MAP.containsValue(key)) {
+                    LOGGER.debug("Unsupported ResolveNames " + contact.getName() + " response attribute: " + key + " value: " + entry.getValue());
+                }
+            }
+        }
+        for (Map.Entry<String, String> entry : GALFIND_ATTRIBUTE_MAP.entrySet()) {
+            String attributeValue = response.get(entry.getValue());
+            if (attributeValue != null) {
+                contact.put(entry.getKey(), attributeValue);
+            }
+        }
+        return contact;
+    }
+
+    @Override
+    public Map<String, ExchangeSession.Contact> galFind(Condition condition, Set<String> returningAttributes, int sizeLimit) throws IOException {
+        Map<String, ExchangeSession.Contact> contacts = new HashMap<String, ExchangeSession.Contact>();
+        if (condition instanceof MultiCondition) {
+            List<Condition> conditions = ((ExchangeSession.MultiCondition) condition).getConditions();
+            Operator operator = ((ExchangeSession.MultiCondition) condition).getOperator();
+            if (operator == Operator.Or) {
+                for (Condition innerCondition : conditions) {
+                    contacts.putAll(galFind(innerCondition, returningAttributes, sizeLimit));
+                }
+            } else if (operator == Operator.And && !conditions.isEmpty()) {
+                Map<String, ExchangeSession.Contact> innerContacts = galFind(conditions.get(0), returningAttributes, sizeLimit);
+                for (ExchangeSession.Contact contact : innerContacts.values()) {
+                    if (condition.isMatch(contact)) {
+                        contacts.put(contact.getName().toLowerCase(), contact);
+                    }
+                }
+            }
+        } else if (condition instanceof AttributeCondition) {
+            String mappedAttributeName = GALFIND_ATTRIBUTE_MAP.get(((ExchangeSession.AttributeCondition) condition).getAttributeName());
+            if (mappedAttributeName != null) {
+                String value = ((ExchangeSession.AttributeCondition) condition).getValue().toLowerCase();
+                Operator operator = ((AttributeCondition) condition).getOperator();
+                String searchValue = value;
+                if (mappedAttributeName.startsWith("EmailAddress")) {
+                    searchValue = "smtp:" + searchValue;
+                }
+                if (operator == Operator.IsEqualTo) {
+                    searchValue = '=' + searchValue;
+                }
+                ResolveNamesMethod resolveNamesMethod = new ResolveNamesMethod(searchValue);
+                executeMethod(resolveNamesMethod);
+                List<EWSMethod.Item> responses = resolveNamesMethod.getResponseItems();
+                if (LOGGER.isDebugEnabled()) {
+                    LOGGER.debug("ResolveNames(" + searchValue + ") returned " + responses.size() + " results");
+                }
+                for (EWSMethod.Item response : responses) {
+                    Contact contact = buildGalfindContact(response);
+                    if (condition.isMatch(contact)) {
+                        contacts.put(contact.getName().toLowerCase(), contact);
+                    }
+                }
+            }
+        }
+        return contacts;
+    }
+
+    protected Date parseDateFromExchange(String exchangeDateValue) throws DavMailException {
+        Date dateValue = null;
+        if (exchangeDateValue != null) {
+            try {
+                dateValue = getExchangeZuluDateFormat().parse(exchangeDateValue);
+            } catch (ParseException e) {
+                throw new DavMailException("EXCEPTION_INVALID_DATE", exchangeDateValue);
+            }
+        }
+        return dateValue;
+    }
+
+    protected String convertDateFromExchange(String exchangeDateValue) throws DavMailException {
+        String zuluDateValue = null;
+        if (exchangeDateValue != null) {
+            try {
+                zuluDateValue = getZuluDateFormat().format(getExchangeZuluDateFormat().parse(exchangeDateValue));
+            } catch (ParseException e) {
+                throw new DavMailException("EXCEPTION_INVALID_DATE", exchangeDateValue);
+            }
+        }
+        return zuluDateValue;
+    }
+
+    protected String convertCalendarDateToExchange(String vcalendarDateValue) throws DavMailException {
+        String zuluDateValue = null;
+        if (vcalendarDateValue != null) {
+            try {
+                SimpleDateFormat dateParser;
+                if (vcalendarDateValue.length() == 8) {
+                    dateParser = new SimpleDateFormat("yyyyMMdd", Locale.ENGLISH);
+                } else {
+                    dateParser = new SimpleDateFormat("yyyyMMdd'T'HHmmss", Locale.ENGLISH);
+                }
+                dateParser.setTimeZone(GMT_TIMEZONE);
+                SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH);
+                dateFormatter.setTimeZone(GMT_TIMEZONE);
+                zuluDateValue = dateFormatter.format(dateParser.parse(vcalendarDateValue));
+            } catch (ParseException e) {
+                throw new DavMailException("EXCEPTION_INVALID_DATE", vcalendarDateValue);
+            }
+        }
+        return zuluDateValue;
+    }
+
+    protected String convertDateFromExchangeToTaskDate(String exchangeDateValue) throws DavMailException {
+        String zuluDateValue = null;
+        if (exchangeDateValue != null) {
+            try {
+                SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd", Locale.ENGLISH);
+                dateFormat.setTimeZone(GMT_TIMEZONE);
+                zuluDateValue = dateFormat.format(getExchangeZuluDateFormat().parse(exchangeDateValue));
+            } catch (ParseException e) {
+                throw new DavMailException("EXCEPTION_INVALID_DATE", exchangeDateValue);
+            }
+        }
+        return zuluDateValue;
+    }
+
+    protected String convertTaskDateToZulu(String value) {
+        String result = null;
+        if (value != null && value.length() > 0) {
+            try {
+                SimpleDateFormat parser;
+                if (value.length() == 8) {
+                    parser = new SimpleDateFormat("yyyyMMdd", Locale.ENGLISH);
+                    parser.setTimeZone(GMT_TIMEZONE);
+                } else if (value.length() == 15) {
+                    parser = new SimpleDateFormat("yyyyMMdd'T'HHmmss", Locale.ENGLISH);
+                    parser.setTimeZone(GMT_TIMEZONE);
+                } else if (value.length() == 16) {
+                    parser = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.ENGLISH);
+                    parser.setTimeZone(GMT_TIMEZONE);
+                } else {
+                    parser = ExchangeSession.getExchangeZuluDateFormat();
+                }
+                Calendar calendarValue = Calendar.getInstance(GMT_TIMEZONE);
+                calendarValue.setTime(parser.parse(value));
+                // zulu time: add 12 hours
+                if (value.length() == 16) {
+                    calendarValue.add(Calendar.HOUR, 12);
+                }
+                calendarValue.set(Calendar.HOUR, 0);
+                calendarValue.set(Calendar.MINUTE, 0);
+                calendarValue.set(Calendar.SECOND, 0);
+                result = ExchangeSession.getExchangeZuluDateFormat().format(calendarValue.getTime());
+            } catch (ParseException e) {
+                LOGGER.warn("Invalid date: " + value);
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Format date to exchange search format.
+     *
+     * @param date date object
+     * @return formatted search date
+     */
+    @Override
+    public String formatSearchDate(Date date) {
+        SimpleDateFormat dateFormatter = new SimpleDateFormat(YYYY_MM_DD_T_HHMMSS_Z, Locale.ENGLISH);
+        dateFormatter.setTimeZone(GMT_TIMEZONE);
+        return dateFormatter.format(date);
+    }
+
+    protected static boolean isItemId(String itemName) {
+        return itemName.length() >= 152;
+    }
+
+
+    protected static final Map<String, String> importanceToPriorityMap = new HashMap<String, String>();
+
+    static {
+        importanceToPriorityMap.put("High", "1");
+        importanceToPriorityMap.put("Normal", "5");
+        importanceToPriorityMap.put("Low", "9");
+    }
+
+    protected static final Map<String, String> priorityToImportanceMap = new HashMap<String, String>();
+
+    static {
+        priorityToImportanceMap.put("1", "High");
+        priorityToImportanceMap.put("5", "Normal");
+        priorityToImportanceMap.put("9", "Low");
+    }
+
+    protected String convertPriorityFromExchange(String exchangeImportanceValue) {
+        String value = null;
+        if (exchangeImportanceValue != null) {
+            value = importanceToPriorityMap.get(exchangeImportanceValue);
+        }
+        return value;
+    }
+
+    protected String convertPriorityToExchange(String vTodoPriorityValue) {
+        String value = null;
+        if (vTodoPriorityValue != null) {
+            value = priorityToImportanceMap.get(vTodoPriorityValue);
+        }
+        return value;
+    }
+}
+
diff --git a/src/java/davmail/exchange/ews/ExtendedFieldURI.java b/src/java/davmail/exchange/ews/ExtendedFieldURI.java
new file mode 100644
index 0000000..9571801
--- /dev/null
+++ b/src/java/davmail/exchange/ews/ExtendedFieldURI.java
@@ -0,0 +1,172 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+import davmail.util.StringUtil;
+
+/**
+ * Extended MAPI property.
+ */
+public class ExtendedFieldURI implements FieldURI {
+
+    @SuppressWarnings({"UnusedDeclaration"})
+    protected enum PropertyType {
+        ApplicationTime, ApplicationTimeArray, Binary, BinaryArray, Boolean, CLSID, CLSIDArray, Currency, CurrencyArray,
+        Double, DoubleArray, Error, Float, FloatArray, Integer, IntegerArray, Long, LongArray, Null, Object,
+        ObjectArray, Short, ShortArray, SystemTime, SystemTimeArray, String, StringArray
+    }
+
+    @SuppressWarnings({"UnusedDeclaration"})
+    protected static enum DistinguishedPropertySetType {
+        Meeting, Appointment, Common, PublicStrings, Address, InternetHeaders, CalendarAssistant, UnifiedMessaging, Task
+    }
+
+
+    protected String propertyTag;
+    protected DistinguishedPropertySetType distinguishedPropertySetId;
+    protected String propertySetId;
+    protected String propertyName;
+    protected int propertyId;
+    protected final PropertyType propertyType;
+
+    /**
+     * Create extended field uri.
+     *
+     * @param intPropertyTag property tag as int
+     * @param propertyType   property type
+     */
+    public ExtendedFieldURI(int intPropertyTag, PropertyType propertyType) {
+        this.propertyTag = "0x" + Integer.toHexString(intPropertyTag);
+        this.propertyType = propertyType;
+    }
+
+    /**
+     * Create extended field uri.
+     *
+     * @param distinguishedPropertySetId distinguished property set id
+     * @param propertyId                 property id
+     * @param propertyType               property type
+     */
+    public ExtendedFieldURI(DistinguishedPropertySetType distinguishedPropertySetId, int propertyId, PropertyType propertyType) {
+        this.distinguishedPropertySetId = distinguishedPropertySetId;
+        this.propertyId = propertyId;
+        this.propertyType = propertyType;
+    }
+
+    /**
+     * Create extended field uri.
+     *
+     * @param distinguishedPropertySetId distinguished property set id
+     * @param propertyName               property name
+     */
+    public ExtendedFieldURI(DistinguishedPropertySetType distinguishedPropertySetId, String propertyName) {
+        this.distinguishedPropertySetId = distinguishedPropertySetId;
+        this.propertyName = propertyName;
+        this.propertyType = PropertyType.String;
+    }
+
+    /**
+     * Create extended field uri.
+     *
+     * @param distinguishedPropertySetId distinguished property set id
+     * @param propertyName               property name
+     * @param propertyType               property type
+     */
+    public ExtendedFieldURI(DistinguishedPropertySetType distinguishedPropertySetId, String propertyName, PropertyType propertyType) {
+        this.distinguishedPropertySetId = distinguishedPropertySetId;
+        this.propertyName = propertyName;
+        this.propertyType = propertyType;
+    }
+
+    public void appendTo(StringBuilder buffer) {
+        buffer.append("<t:ExtendedFieldURI");
+        if (propertyTag != null) {
+            buffer.append(" PropertyTag=\"").append(propertyTag).append('"');
+        }
+        if (distinguishedPropertySetId != null) {
+            buffer.append(" DistinguishedPropertySetId=\"").append(distinguishedPropertySetId).append('"');
+        }
+        if (propertySetId != null) {
+            buffer.append(" PropertySetId=\"").append(propertySetId).append('"');
+        }
+        if (propertyName != null) {
+            buffer.append(" PropertyName=\"").append(propertyName).append('"');
+        }
+        if (propertyId != 0) {
+            buffer.append(" PropertyId=\"").append(String.valueOf(propertyId)).append('"');
+        }
+        if (propertyType != null) {
+            buffer.append(" PropertyType=\"").append(propertyType.toString()).append('"');
+        }
+        buffer.append("/>");
+    }
+
+    public void appendValue(StringBuilder buffer, String itemType, String value) {
+        if (itemType != null) {
+            appendTo(buffer);
+            buffer.append("<t:");
+            buffer.append(itemType);
+            buffer.append('>');
+        }
+        buffer.append("<t:ExtendedProperty>");
+        appendTo(buffer);
+        if (propertyType == PropertyType.StringArray) {
+            buffer.append("<t:Values>");
+            String[] values = value.split("\n");
+            for (final String singleValue : values) {
+                buffer.append("<t:Value>");
+                buffer.append(StringUtil.xmlEncode(singleValue));
+                buffer.append("</t:Value>");
+
+            }
+            buffer.append("</t:Values>");
+        } else {
+            buffer.append("<t:Value>");
+            if ("0x10f3".equals(propertyTag)) {
+                buffer.append(StringUtil.xmlEncode(StringUtil.encodeUrlcompname(value)));
+            } else {
+                buffer.append(StringUtil.xmlEncode(value));
+            }
+            buffer.append("</t:Value>");
+        }
+        buffer.append("</t:ExtendedProperty>");
+        if (itemType != null) {
+            buffer.append("</t:");
+            buffer.append(itemType);
+            buffer.append('>');
+        }
+    }
+
+    /**
+     * Field name in EWS response.
+     *
+     * @return field name in response
+     */
+    public String getResponseName() {
+        if (propertyTag != null) {
+            return propertyTag;
+        } else if (propertyName != null) {
+            return propertyName;
+        } else {
+            return String.valueOf(propertyId);
+        }
+    }
+
+}
+
diff --git a/src/java/davmail/exchange/ews/Field.java b/src/java/davmail/exchange/ews/Field.java
new file mode 100644
index 0000000..af9daa4
--- /dev/null
+++ b/src/java/davmail/exchange/ews/Field.java
@@ -0,0 +1,262 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * EWS MAPI fields;
+ */
+public final class Field {
+    private static final Map<String, FieldURI> FIELD_MAP = new HashMap<String, FieldURI>();
+
+    private Field() {
+    }
+
+    static {
+        // items
+        FIELD_MAP.put("etag", new ExtendedFieldURI(0x3008, ExtendedFieldURI.PropertyType.SystemTime));
+        FIELD_MAP.put("displayname", new ExtendedFieldURI(0x3001, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("urlcompname", new ExtendedFieldURI(0x10f3, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("lastmodified", new ExtendedFieldURI(0x3008, ExtendedFieldURI.PropertyType.SystemTime));
+        FIELD_MAP.put("created", new ExtendedFieldURI(0x3007, ExtendedFieldURI.PropertyType.SystemTime));
+
+        // folder
+        FIELD_MAP.put("ctag", new ExtendedFieldURI(0x670a, ExtendedFieldURI.PropertyType.SystemTime)); // PR_LOCAL_COMMIT_TIME_MAX
+        FIELD_MAP.put("unread", new ExtendedFieldURI(0x3603, ExtendedFieldURI.PropertyType.Integer)); // PR_CONTENT_UNREAD
+        FIELD_MAP.put("hassubs", new ExtendedFieldURI(0x360a, ExtendedFieldURI.PropertyType.Boolean)); // PR_SUBFOLDERS
+        FIELD_MAP.put("folderDisplayName", new UnindexedFieldURI("folder:DisplayName"));
+
+        FIELD_MAP.put("uidNext", new ExtendedFieldURI(0x6751, ExtendedFieldURI.PropertyType.Integer)); // PR_ARTICLE_NUM_NEXT
+        FIELD_MAP.put("highestUid", new ExtendedFieldURI(0x6752, ExtendedFieldURI.PropertyType.Integer)); // PR_IMAP_LAST_ARTICLE_ID
+
+        FIELD_MAP.put("permanenturl", new ExtendedFieldURI(0x670E, ExtendedFieldURI.PropertyType.String)); //PR_FLAT_URL_NAME
+        FIELD_MAP.put("instancetype", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.PublicStrings, "urn:schemas:calendar:instancetype", ExtendedFieldURI.PropertyType.Integer));
+        //FIELD_MAP.put("dtstart", new ExtendedFieldURI(0x10C3, ExtendedFieldURI.PropertyType.SystemTime));
+        //FIELD_MAP.put("dtend", new ExtendedFieldURI(0x10C4, ExtendedFieldURI.PropertyType.SystemTime));
+        FIELD_MAP.put("dtstart", new UnindexedFieldURI("calendar:Start"));
+        FIELD_MAP.put("dtend", new UnindexedFieldURI("calendar:End"));
+
+        FIELD_MAP.put("mimeContent", new UnindexedFieldURI("item:MimeContent"));
+
+        // use PR_RECORD_KEY as unique key
+        FIELD_MAP.put("uid", new ExtendedFieldURI(0x0FF9, ExtendedFieldURI.PropertyType.Binary));
+        FIELD_MAP.put("messageFlags", new ExtendedFieldURI(0x0e07, ExtendedFieldURI.PropertyType.Integer));//PR_MESSAGE_FLAGS
+        FIELD_MAP.put("imapUid", new ExtendedFieldURI(0x0e23, ExtendedFieldURI.PropertyType.Integer));
+        FIELD_MAP.put("flagStatus", new ExtendedFieldURI(0x1090, ExtendedFieldURI.PropertyType.Integer));
+        FIELD_MAP.put("lastVerbExecuted", new ExtendedFieldURI(0x1081, ExtendedFieldURI.PropertyType.Integer));
+        FIELD_MAP.put("read", new UnindexedFieldURI("message:IsRead"));
+        FIELD_MAP.put("messageSize", new ExtendedFieldURI(0x0e08, ExtendedFieldURI.PropertyType.Integer));
+        FIELD_MAP.put("date", new ExtendedFieldURI(0x0e06, ExtendedFieldURI.PropertyType.SystemTime));
+        // always empty on Exchange 2007
+        //FIELD_MAP.put("messageSize", new ExtendedFieldURI(0x6746, ExtendedFieldURI.PropertyType.Integer)); // PR_MIME_SIZE
+        //FIELD_MAP.put("date", new ExtendedFieldURI(0x65f5, ExtendedFieldURI.PropertyType.SystemTime)); // PR_IMAP_INTERNAL_DATE
+        FIELD_MAP.put("deleted", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Common, 0x8570, ExtendedFieldURI.PropertyType.Integer)); // PidLidImapDeleted
+        FIELD_MAP.put("junk", new ExtendedFieldURI(0x1083, ExtendedFieldURI.PropertyType.Integer));
+
+        FIELD_MAP.put("iconIndex", new ExtendedFieldURI(0x1080, ExtendedFieldURI.PropertyType.Integer));// PR_ICON_INDEX
+        FIELD_MAP.put("datereceived", new ExtendedFieldURI(0x0e06, ExtendedFieldURI.PropertyType.SystemTime));// PR_MESSAGE_DELIVERY_TIME
+        FIELD_MAP.put("bcc", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.InternetHeaders, "bcc"));
+
+        FIELD_MAP.put("to", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.InternetHeaders, "to"));
+        FIELD_MAP.put("cc", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.InternetHeaders, "cc"));
+        FIELD_MAP.put("from", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.InternetHeaders, "from"));
+
+        FIELD_MAP.put("messageheaders", new ExtendedFieldURI(0x007D, ExtendedFieldURI.PropertyType.String)); // PR_TRANSPORT_MESSAGE_HEADERS
+
+        FIELD_MAP.put("contentclass", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.InternetHeaders, "content-class"));
+        FIELD_MAP.put("message-id", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.InternetHeaders, "message-id"));
+
+        FIELD_MAP.put("body", new UnindexedFieldURI("item:Body"));
+
+        // folder
+        FIELD_MAP.put("folderclass", new ExtendedFieldURI(0x3613, ExtendedFieldURI.PropertyType.String));
+
+        // contact
+
+        FIELD_MAP.put("outlookmessageclass", new ExtendedFieldURI(0x001A, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("subject", new ExtendedFieldURI(0x0037, ExtendedFieldURI.PropertyType.String));
+
+        FIELD_MAP.put("middlename", new ExtendedFieldURI(0x3A44, ExtendedFieldURI.PropertyType.String));
+        //FIELD_MAP.put("fileas", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.PublicStrings, "urn:schemas:contacts:fileas"));
+        FIELD_MAP.put("fileas", new UnindexedFieldURI("contacts:FileAs"));
+
+        FIELD_MAP.put("homepostaladdress", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Address, 0x801A, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("otherpostaladdress", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Address, 0x801C, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("mailingaddressid", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Address, 0x8022, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("workaddress", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Address, 0x801B, ExtendedFieldURI.PropertyType.String));
+
+        FIELD_MAP.put("alternaterecipient", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.PublicStrings, "urn:schemas:contacts:alternaterecipient"));
+
+        FIELD_MAP.put("extensionattribute1", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Address, 0x804F, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("extensionattribute2", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Address, 0x8050, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("extensionattribute3", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Address, 0x8051, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("extensionattribute4", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Address, 0x8052, ExtendedFieldURI.PropertyType.String));
+
+        FIELD_MAP.put("bday", new ExtendedFieldURI(0x3A42, ExtendedFieldURI.PropertyType.SystemTime));
+        FIELD_MAP.put("anniversary", new ExtendedFieldURI(0x3A41, ExtendedFieldURI.PropertyType.SystemTime));
+        FIELD_MAP.put("businesshomepage", new ExtendedFieldURI(0x3A51, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("personalHomePage", new ExtendedFieldURI(0x3A50, ExtendedFieldURI.PropertyType.String));
+
+        FIELD_MAP.put("cn", new ExtendedFieldURI(0x3001, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("co", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Address, 0x8049, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("department", new ExtendedFieldURI(0x3A18, ExtendedFieldURI.PropertyType.String));
+
+        /*
+        FIELD_MAP.put("email1", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Address, 0x8083, ExtendedFieldURI.PropertyType.String)); // Email1EmailAddress
+        FIELD_MAP.put("email2", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Address, 0x8093, ExtendedFieldURI.PropertyType.String)); // Email2EmailAddress
+        FIELD_MAP.put("email3", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Address, 0x80A3, ExtendedFieldURI.PropertyType.String)); // Email3EmailAddress
+
+        FIELD_MAP.put("smtpemail1", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Address, 0x8084, ExtendedFieldURI.PropertyType.String)); // Email1OriginalDisplayName
+        FIELD_MAP.put("smtpemail2", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Address, 0x8094, ExtendedFieldURI.PropertyType.String)); // Email2OriginalDisplayName
+        FIELD_MAP.put("smtpemail3", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Address, 0x80A4, ExtendedFieldURI.PropertyType.String)); // Email3OriginalDisplayName
+
+        FIELD_MAP.put("displayemail1", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Address, 0x8080, ExtendedFieldURI.PropertyType.String)); // Email1DisplayName
+        FIELD_MAP.put("displayemail2", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Address, 0x8090, ExtendedFieldURI.PropertyType.String)); // Email2DisplayName
+        FIELD_MAP.put("displayemail3", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Address, 0x80A0, ExtendedFieldURI.PropertyType.String)); // Email3DisplayName
+        */
+        FIELD_MAP.put("smtpemail1", new IndexedFieldURI("contacts:EmailAddress", "EmailAddress1", "Contact", "EmailAddresses"));
+        FIELD_MAP.put("smtpemail2", new IndexedFieldURI("contacts:EmailAddress", "EmailAddress2", "Contact", "EmailAddresses"));
+        FIELD_MAP.put("smtpemail3", new IndexedFieldURI("contacts:EmailAddress", "EmailAddress3", "Contact", "EmailAddresses"));
+
+        FIELD_MAP.put("facsimiletelephonenumber", new ExtendedFieldURI(0x3A24, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("givenName", new ExtendedFieldURI(0x3A06, ExtendedFieldURI.PropertyType.String));
+
+        FIELD_MAP.put("homepostofficebox", new ExtendedFieldURI(0x3A5E, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("homeCity", new ExtendedFieldURI(0x3A59, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("homeCountry", new ExtendedFieldURI(0x3A5A, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("homePhone", new ExtendedFieldURI(0x3A09, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("homePostalCode", new ExtendedFieldURI(0x3A5B, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("homeState", new ExtendedFieldURI(0x3A5C, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("homeStreet", new ExtendedFieldURI(0x3A5D, ExtendedFieldURI.PropertyType.String));
+
+        FIELD_MAP.put("l", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Address, 0x8046, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("manager", new ExtendedFieldURI(0x3A4E, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("mobile", new ExtendedFieldURI(0x3A1C, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("namesuffix", new ExtendedFieldURI(0x3A05, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("nickname", new ExtendedFieldURI(0x3A4F, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("o", new ExtendedFieldURI(0x3A16, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("pager", new ExtendedFieldURI(0x3A21, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("personaltitle", new ExtendedFieldURI(0x3A45, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("postalcode", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Address, 0x8048, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("postofficebox", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Address, 0x804A, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("profession", new ExtendedFieldURI(0x3A46, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("roomnumber", new ExtendedFieldURI(0x3A19, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("secretarycn", new ExtendedFieldURI(0x3A30, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("sn", new ExtendedFieldURI(0x3A11, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("spousecn", new ExtendedFieldURI(0x3A48, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("st", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Address, 0x8047, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("street", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Address, 0x8045, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("telephoneNumber", new ExtendedFieldURI(0x3A08, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("title", new ExtendedFieldURI(0x3A17, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("description", new ExtendedFieldURI(0x1000, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("im", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Address, 0x8062, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("othermobile", new ExtendedFieldURI(0x3A1E, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("internationalisdnnumber", new ExtendedFieldURI(0x3A2D, ExtendedFieldURI.PropertyType.String));
+
+        FIELD_MAP.put("otherTelephone", new ExtendedFieldURI(0x3A21, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("homefax", new ExtendedFieldURI(0x3A25, ExtendedFieldURI.PropertyType.String));
+
+        FIELD_MAP.put("otherstreet", new ExtendedFieldURI(0x3A63, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("otherstate", new ExtendedFieldURI(0x3A62, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("otherpostofficebox", new ExtendedFieldURI(0x3A64, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("otherpostalcode", new ExtendedFieldURI(0x3A61, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("othercountry", new ExtendedFieldURI(0x3A60, ExtendedFieldURI.PropertyType.String));
+        FIELD_MAP.put("othercity", new ExtendedFieldURI(0x3A5F, ExtendedFieldURI.PropertyType.String));
+
+        FIELD_MAP.put("gender", new ExtendedFieldURI(0x3A4D, ExtendedFieldURI.PropertyType.Short));
+
+        FIELD_MAP.put("keywords", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.PublicStrings, "Keywords", ExtendedFieldURI.PropertyType.StringArray));
+
+        FIELD_MAP.put("private", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Common, 0x8506, ExtendedFieldURI.PropertyType.Boolean));
+        FIELD_MAP.put("sensitivity", new ExtendedFieldURI(0x0036, ExtendedFieldURI.PropertyType.Integer));
+
+        FIELD_MAP.put("haspicture", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Address, 0x8015, ExtendedFieldURI.PropertyType.Boolean));
+
+        FIELD_MAP.put("fburl", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Address, 0x80D8, ExtendedFieldURI.PropertyType.String));
+
+        // calendar
+        FIELD_MAP.put("processed", new ExtendedFieldURI(0x65e8, ExtendedFieldURI.PropertyType.Boolean));
+
+        FIELD_MAP.put("reminderset", new UnindexedFieldURI("item:ReminderIsSet"));
+        FIELD_MAP.put("ismeeting", new UnindexedFieldURI("item:IsMeeting"));
+        FIELD_MAP.put("apptstateflags", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Appointment, 0x8217, ExtendedFieldURI.PropertyType.Integer)); // PidLidAppointmentStateFlags 1: Meeting, 2: Received, 4: Cancelled
+        FIELD_MAP.put("appointmentstate", new UnindexedFieldURI("calendar:AppointmentState"));
+        FIELD_MAP.put("myresponsetype", new UnindexedFieldURI("calendar:MyResponseType"));
+        FIELD_MAP.put("calendaruid", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.PublicStrings, "urn:schemas:calendar:uid", ExtendedFieldURI.PropertyType.String));
+
+        FIELD_MAP.put("meetingtimezone", new UnindexedFieldURI("calendar:MeetingTimeZone"));
+        FIELD_MAP.put("starttimezone", new UnindexedFieldURI("calendar:StartTimeZone"));
+        FIELD_MAP.put("busystatus", new UnindexedFieldURI("calendar:LegacyFreeBusyStatus"));
+
+        FIELD_MAP.put("requiredattendees", new UnindexedFieldURI("calendar:RequiredAttendees"));
+        FIELD_MAP.put("optionalattendees", new UnindexedFieldURI("calendar:OptionalAttendees"));
+        FIELD_MAP.put("modifiedoccurrences", new UnindexedFieldURI("calendar:ModifiedOccurrences"));
+
+        FIELD_MAP.put("isrecurring", new UnindexedFieldURI("calendar:IsRecurring"));
+        FIELD_MAP.put("calendaritemtype", new UnindexedFieldURI("calendar:CalendarItemType"));
+
+        FIELD_MAP.put("xmozlastack", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.PublicStrings, "xmozlastack"));
+        FIELD_MAP.put("xmozsnoozetime", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.PublicStrings, "xmozsnoozetime"));
+        FIELD_MAP.put("xmozsendinvitations", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.PublicStrings, "xmozsendinvitations"));
+
+        // task
+        FIELD_MAP.put("importance", new UnindexedFieldURI("item:Importance"));
+        FIELD_MAP.put("percentcomplete", new UnindexedFieldURI("task:PercentComplete"));
+        FIELD_MAP.put("taskstatus", new UnindexedFieldURI("task:Status"));
+
+        FIELD_MAP.put("startdate", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Task, 0x8104, ExtendedFieldURI.PropertyType.SystemTime));
+        FIELD_MAP.put("duedate", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Task, 0x8105, ExtendedFieldURI.PropertyType.SystemTime));
+        FIELD_MAP.put("datecompleted", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Task, 0x810F, ExtendedFieldURI.PropertyType.SystemTime));
+        FIELD_MAP.put("iscomplete", new UnindexedFieldURI("task:IsComplete"));
+
+        FIELD_MAP.put("commonstart", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Task, 0x8516, ExtendedFieldURI.PropertyType.SystemTime));
+        FIELD_MAP.put("commonend", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Task, 0x8517, ExtendedFieldURI.PropertyType.SystemTime));
+
+        // attachments
+        FIELD_MAP.put("attachments", new UnindexedFieldURI("item:Attachments"));
+    }
+
+    /**
+     * Get Field by alias.
+     *
+     * @param alias field alias
+     * @return field
+     */
+    public static FieldURI get(String alias) {
+        FieldURI field = FIELD_MAP.get(alias);
+        if (field == null) {
+            throw new IllegalArgumentException("Unknown field: " + alias);
+        }
+        return field;
+    }
+
+    /**
+     * Create property update field
+     *
+     * @param alias property alias
+     * @param value property value
+     * @return field update
+     */
+    public static FieldUpdate createFieldUpdate(String alias, String value) {
+        return new FieldUpdate(Field.get(alias), value);
+    }
+
+}
diff --git a/src/java/davmail/exchange/ews/FieldURI.java b/src/java/davmail/exchange/ews/FieldURI.java
new file mode 100644
index 0000000..2cecb6d
--- /dev/null
+++ b/src/java/davmail/exchange/ews/FieldURI.java
@@ -0,0 +1,49 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+/**
+ * Field URI.
+ */
+public interface FieldURI {
+
+    /**
+     * Append field to buffer
+     *
+     * @param buffer current buffer
+     */
+    void appendTo(StringBuilder buffer);
+
+    /**
+     * Append updated field value to buffer
+     *
+     * @param buffer   current buffer
+     * @param itemType item type
+     * @param value    field value
+     */
+    void appendValue(StringBuilder buffer, String itemType, String value);
+
+    /**
+     * Property name in EWS response.
+     *
+     * @return property name
+     */
+    String getResponseName();
+
+}
diff --git a/src/java/davmail/exchange/ews/FieldUpdate.java b/src/java/davmail/exchange/ews/FieldUpdate.java
new file mode 100644
index 0000000..6df9a6a
--- /dev/null
+++ b/src/java/davmail/exchange/ews/FieldUpdate.java
@@ -0,0 +1,86 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Field update
+ */
+public class FieldUpdate {
+    FieldURI fieldURI;
+    String value;
+
+    /**
+     * Create field update with value.
+     *
+     * @param fieldURI target field
+     * @param value    field value
+     */
+    public FieldUpdate(FieldURI fieldURI, String value) {
+        this.fieldURI = fieldURI;
+        this.value = value;
+    }
+
+    protected FieldUpdate() {
+        // empty constructor for subclass
+    }
+
+    /**
+     * Write field to request writer.
+     *
+     * @param itemType item type
+     * @param writer   request writer
+     * @throws IOException on error
+     */
+    public void write(String itemType, Writer writer) throws IOException {
+        String action;
+        //noinspection VariableNotUsedInsideIf
+        if (value == null) {
+            action = "Delete";
+        } else {
+            action = "Set";
+        }
+        if (itemType != null) {
+            writer.write("<t:");
+            writer.write(action);
+            writer.write(itemType);
+            writer.write("Field>");
+        }
+
+        // do not try to set empty value on create
+        if (itemType != null || value != null) {
+            StringBuilder buffer = new StringBuilder();
+            if (value == null) {
+                fieldURI.appendTo(buffer);
+            } else {
+                fieldURI.appendValue(buffer, itemType, value);
+            }
+            writer.write(buffer.toString());
+        }
+
+        if (itemType != null) {
+            writer.write("</t:");
+            writer.write(action);
+            writer.write(itemType);
+            writer.write("Field>");
+        }
+    }
+}
diff --git a/src/java/davmail/exchange/ews/FileAttachment.java b/src/java/davmail/exchange/ews/FileAttachment.java
new file mode 100644
index 0000000..d7c4f7c
--- /dev/null
+++ b/src/java/davmail/exchange/ews/FileAttachment.java
@@ -0,0 +1,92 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * File Attachment.
+ */
+public class FileAttachment {
+    protected String name;
+    protected String contentType;
+    protected String content;
+    protected String attachmentId;
+    protected boolean isContactPhoto;
+
+    /**
+     * Default constructor
+     */
+    public FileAttachment() {
+        // empty constructor
+    }
+
+    /**
+     * Build file attachment.
+     *
+     * @param name        attachment name
+     * @param contentType content type
+     * @param content     body as string
+     */
+    public FileAttachment(String name, String contentType, String content) {
+        this.name = name;
+        this.contentType = contentType;
+        this.content = content;
+    }
+
+    /**
+     * Write XML content to writer.
+     *
+     * @param writer writer
+     * @throws IOException on error
+     */
+    public void write(Writer writer) throws IOException {
+        writer.write("<t:FileAttachment>");
+        if (name != null) {
+            writer.write("<t:Name>");
+            writer.write(name);
+            writer.write("</t:Name>");
+        }
+        if (contentType != null) {
+            writer.write("<t:ContentType>");
+            writer.write(contentType);
+            writer.write("</t:ContentType>");
+        }
+        if (isContactPhoto) {
+            writer.write("<t:IsContactPhoto>true</t:IsContactPhoto>");
+        }
+        if (content != null) {
+            writer.write("<t:Content>");
+            writer.write(content);
+            writer.write("</t:Content>");
+        }
+        writer.write("</t:FileAttachment>");
+    }
+
+    /**
+     * Exchange 2010 only: set contact photo flag on attachment.
+     *
+     * @param isContactPhoto contact photo flag
+     */
+    public void setIsContactPhoto(boolean isContactPhoto) {
+        this.isContactPhoto = isContactPhoto;
+    }
+
+}
diff --git a/src/java/davmail/exchange/ews/FindFolderMethod.java b/src/java/davmail/exchange/ews/FindFolderMethod.java
new file mode 100644
index 0000000..3c3ab22
--- /dev/null
+++ b/src/java/davmail/exchange/ews/FindFolderMethod.java
@@ -0,0 +1,58 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+import java.util.Set;
+
+/**
+ * EWS Find Folder.
+ */
+public class FindFolderMethod extends EWSMethod {
+
+    /**
+     * Find Exchange Folder.
+     *
+     * @param traversal            traversal type
+     * @param baseShape            base shape
+     * @param parentFolderId       parent folder id
+     * @param additionalProperties folder properties
+     */
+    public FindFolderMethod(FolderQueryTraversal traversal, BaseShape baseShape, FolderId parentFolderId, Set<FieldURI> additionalProperties) {
+        super("Folder", "FindFolder");
+        this.traversal = traversal;
+        this.baseShape = baseShape;
+        this.parentFolderId = parentFolderId;
+        this.additionalProperties = additionalProperties;
+    }
+
+    /**
+     * Find Exchange Folder.
+     *
+     * @param traversal            traversal type
+     * @param baseShape            base shape
+     * @param parentFolderId       parent folder id
+     * @param additionalProperties folder properties
+     * @param searchExpression     search expression
+     */
+    public FindFolderMethod(FolderQueryTraversal traversal, BaseShape baseShape, FolderId parentFolderId,
+                            Set<FieldURI> additionalProperties, SearchExpression searchExpression) {
+        this(traversal, baseShape, parentFolderId, additionalProperties);
+        this.searchExpression = searchExpression;
+    }
+}
diff --git a/src/java/davmail/exchange/ews/FindItemMethod.java b/src/java/davmail/exchange/ews/FindItemMethod.java
new file mode 100644
index 0000000..033d287
--- /dev/null
+++ b/src/java/davmail/exchange/ews/FindItemMethod.java
@@ -0,0 +1,43 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+/**
+ * EWS Find Item Method.
+ */
+public class FindItemMethod extends EWSMethod {
+    /**
+     * Find item method.
+     *
+     * @param traversal      folder traversal mode
+     * @param baseShape      base item shape
+     * @param parentFolderId parent folder id
+     * @param offset         start offset
+     * @param maxCount       maximum result count
+     */
+    public FindItemMethod(FolderQueryTraversal traversal, BaseShape baseShape, FolderId parentFolderId, int offset, int maxCount) {
+        super("Item", "FindItem");
+        this.traversal = traversal;
+        this.baseShape = baseShape;
+        this.parentFolderId = parentFolderId;
+        this.offset = offset;
+        this.maxCount = maxCount;
+    }
+}
+
diff --git a/src/java/davmail/exchange/ews/FolderId.java b/src/java/davmail/exchange/ews/FolderId.java
new file mode 100644
index 0000000..18da02d
--- /dev/null
+++ b/src/java/davmail/exchange/ews/FolderId.java
@@ -0,0 +1,90 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Folder Id.
+ */
+public class FolderId extends Option {
+    protected String changeKey;
+    protected String mailbox;
+
+    /**
+     * Create FolderId with specified tag name.
+     *
+     * @param name      field tag name
+     * @param value     id value
+     * @param changeKey folder change key
+     * @param mailbox   shared mailbox name
+     */
+    protected FolderId(String name, String value, String changeKey, String mailbox) {
+        this(name, value, changeKey);
+        this.mailbox = mailbox;
+    }
+
+    /**
+     * Create FolderId with specified tag name.
+     *
+     * @param name      field tag name
+     * @param value     id value
+     * @param changeKey folder change key
+     */
+    protected FolderId(String name, String value, String changeKey) {
+        super(name, value);
+        this.changeKey = changeKey;
+    }
+
+    /**
+     * Build Folder id from response item.
+     *
+     * @param item    response item
+     */
+    public FolderId(EWSMethod.Item item) {
+        this("t:FolderId", item.get("FolderId"), item.get("ChangeKey"));
+    }
+
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    public void write(Writer writer) throws IOException {
+        writer.write('<');
+        writer.write(name);
+        writer.write(" Id=\"");
+        writer.write(value);
+        if (changeKey != null) {
+            writer.write("\" ChangeKey=\"");
+            writer.write(changeKey);
+        }
+        if (mailbox == null) {
+            writer.write("\"/>");
+        } else {
+            writer.write("\"><t:Mailbox><t:EmailAddress>");
+            writer.write(mailbox);
+            writer.write("</t:EmailAddress></t:Mailbox></");
+            writer.write(name);
+            writer.write('>');
+        }
+    }
+
+}
diff --git a/src/java/davmail/exchange/ews/FolderQueryTraversal.java b/src/java/davmail/exchange/ews/FolderQueryTraversal.java
new file mode 100644
index 0000000..5677ca7
--- /dev/null
+++ b/src/java/davmail/exchange/ews/FolderQueryTraversal.java
@@ -0,0 +1,39 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+/**
+ * Folder folderQueryTraversalType search mode.
+ */
+public final class FolderQueryTraversal extends AttributeOption {
+
+    private FolderQueryTraversal(String value) {
+        super("Traversal", value);
+    }
+
+    /**
+     * Search only in current folder.
+     */
+    public static final FolderQueryTraversal SHALLOW = new FolderQueryTraversal("Shallow");
+    /**
+     * Recursive search.
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    public static final FolderQueryTraversal DEEP = new FolderQueryTraversal("Deep");
+}
\ No newline at end of file
diff --git a/src/java/davmail/exchange/ews/GetAttachmentMethod.java b/src/java/davmail/exchange/ews/GetAttachmentMethod.java
new file mode 100644
index 0000000..64779af
--- /dev/null
+++ b/src/java/davmail/exchange/ews/GetAttachmentMethod.java
@@ -0,0 +1,35 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+/**
+ * Get Attachment Method.
+ */
+public class GetAttachmentMethod extends EWSMethod {
+
+    /**
+     * Get Attachment Method.
+     *
+     * @param attachmentId attachment id
+     */
+    public GetAttachmentMethod(String attachmentId) {
+        super("Attachment", "GetAttachment");
+        this.attachmentId = attachmentId;
+    }
+}
diff --git a/src/java/davmail/exchange/ews/GetFolderMethod.java b/src/java/davmail/exchange/ews/GetFolderMethod.java
new file mode 100644
index 0000000..b93b1e0
--- /dev/null
+++ b/src/java/davmail/exchange/ews/GetFolderMethod.java
@@ -0,0 +1,42 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+import java.util.Set;
+
+/**
+ * EWS GetFolder method.
+ */
+public class GetFolderMethod extends EWSMethod {
+
+    /**
+     * Get folder method.
+     *
+     * @param baseShape            base requested shape
+     * @param folderId             folder id
+     * @param additionalProperties additional requested properties
+     */
+    public GetFolderMethod(BaseShape baseShape, FolderId folderId, Set<FieldURI> additionalProperties) {
+        super("Folder", "GetFolder");
+        this.baseShape = baseShape;
+        this.folderId = folderId;
+        this.additionalProperties = additionalProperties;
+    }
+
+}
diff --git a/src/java/davmail/exchange/ews/GetItemMethod.java b/src/java/davmail/exchange/ews/GetItemMethod.java
new file mode 100644
index 0000000..86648b2
--- /dev/null
+++ b/src/java/davmail/exchange/ews/GetItemMethod.java
@@ -0,0 +1,39 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+/**
+ * Get Item method.
+ */
+public class GetItemMethod extends EWSMethod {
+    /**
+     * Get item method.
+     *
+     * @param baseShape          base requested shape
+     * @param itemId             item id
+     * @param includeMimeContent return mime content
+     */
+    public GetItemMethod(BaseShape baseShape, ItemId itemId, boolean includeMimeContent) {
+        super("Item", "GetItem");
+        this.baseShape = baseShape;
+        this.itemId = itemId;
+        this.includeMimeContent = includeMimeContent;
+    }
+
+}
diff --git a/src/java/davmail/exchange/ews/GetUserAvailabilityMethod.java b/src/java/davmail/exchange/ews/GetUserAvailabilityMethod.java
new file mode 100644
index 0000000..68f3e6c
--- /dev/null
+++ b/src/java/davmail/exchange/ews/GetUserAvailabilityMethod.java
@@ -0,0 +1,115 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+import davmail.exchange.XMLStreamUtil;
+
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * GetUserAvailability method.
+ */
+public class GetUserAvailabilityMethod extends EWSMethod {
+    protected final String attendee;
+    protected final String start;
+    protected final String end;
+    protected String mergedFreeBusy;
+    protected final int interval;
+
+    /**
+     * Build EWS method
+     *
+     * @param attendee attendee email address
+     * @param start    start date in Exchange zulu format
+     * @param end      end date in Exchange zulu format
+     * @param interval freebusy interval in minutes
+     */
+    public GetUserAvailabilityMethod(String attendee, String start, String end, int interval) {
+        super("FreeBusy", "GetUserAvailabilityRequest");
+        this.attendee = attendee;
+        this.start = start;
+        this.end = end;
+        this.interval = interval;
+    }
+
+    @Override
+    protected void writeSoapBody(Writer writer) throws IOException {
+        // write UTC timezone
+        writer.write("<t:TimeZone>" +
+                "<t:Bias>0</t:Bias>" +
+                "<t:StandardTime>" +
+                "<t:Bias>0</t:Bias>" +
+                "<t:Time>02:00:00</t:Time>" +
+                "<t:DayOrder>1</t:DayOrder>" +
+                "<t:Month>3</t:Month>" +
+                "<t:DayOfWeek>Sunday</t:DayOfWeek>" +
+                "</t:StandardTime>" +
+                "<t:DaylightTime>" +
+                "<t:Bias>0</t:Bias>" +
+                "<t:Time>02:00:00</t:Time>" +
+                "<t:DayOrder>1</t:DayOrder>" +
+                "<t:Month>10</t:Month>" +
+                "<t:DayOfWeek>Sunday</t:DayOfWeek>" +
+                "</t:DaylightTime>" +
+                "</t:TimeZone>");
+        // write attendee address
+        writer.write("<m:MailboxDataArray>" +
+                "<t:MailboxData>" +
+                "<t:Email>" +
+                "<t:Address>");
+        writer.write(attendee);
+        writer.write("</t:Address>" +
+                "</t:Email>" +
+                "<t:AttendeeType>Required</t:AttendeeType>" +
+                "</t:MailboxData>" +
+                "</m:MailboxDataArray>");
+        // freebusy range
+        writer.write("<t:FreeBusyViewOptions>" +
+                "<t:TimeWindow>" +
+                "<t:StartTime>");
+        writer.write(start);
+        writer.write("</t:StartTime>" +
+                "<t:EndTime>");
+        writer.write(end);
+        writer.write("</t:EndTime>" +
+                "</t:TimeWindow>" +
+                "<t:MergedFreeBusyIntervalInMinutes>" + interval + "</t:MergedFreeBusyIntervalInMinutes>" +
+                "<t:RequestedView>MergedOnly</t:RequestedView>" +
+                "</t:FreeBusyViewOptions>");
+    }
+
+    @Override
+    protected void handleCustom(XMLStreamReader reader) throws XMLStreamException {
+        if (XMLStreamUtil.isStartTag(reader, "MergedFreeBusy")) {
+            this.mergedFreeBusy = XMLStreamUtil.getElementText(reader);
+        }
+    }
+
+    /**
+     * Get merged freebusy string.
+     *
+     * @return freebusy string
+     */
+    public String getMergedFreeBusy() {
+        return mergedFreeBusy;
+    }
+}
diff --git a/src/java/davmail/exchange/ews/GetUserConfigurationMethod.java b/src/java/davmail/exchange/ews/GetUserConfigurationMethod.java
new file mode 100644
index 0000000..273f20e
--- /dev/null
+++ b/src/java/davmail/exchange/ews/GetUserConfigurationMethod.java
@@ -0,0 +1,87 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+import davmail.exchange.XMLStreamUtil;
+
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Get User Configuration method.
+ */
+public class GetUserConfigurationMethod extends EWSMethod {
+
+    /**
+     * Get User Configuration method.
+     */
+    public GetUserConfigurationMethod() {
+        super("UserConfiguration", "GetUserConfiguration");
+        folderId = DistinguishedFolderId.getInstance(null, DistinguishedFolderId.Name.root);
+    }
+
+    @Override
+    protected void writeSoapBody(Writer writer) throws IOException {
+        writer.write("<m:UserConfigurationName Name=\"OWA.UserOptions\">");
+        folderId.write(writer);
+        writer.write("</m:UserConfigurationName>");
+        writer.write("<m:UserConfigurationProperties>All</m:UserConfigurationProperties>");
+    }
+
+    @Override
+    protected void handleCustom(XMLStreamReader reader) throws XMLStreamException {
+        if (XMLStreamUtil.isStartTag(reader, "UserConfiguration")) {
+            responseItems.add(handleUserConfiguration(reader));
+        }
+    }
+
+    private Item handleUserConfiguration(XMLStreamReader reader) throws XMLStreamException {
+        Item responseItem = new Item();
+        while (reader.hasNext() && !(XMLStreamUtil.isEndTag(reader, "UserConfiguration"))) {
+            reader.next();
+            if (XMLStreamUtil.isStartTag(reader)) {
+                String tagLocalName = reader.getLocalName();
+                if ("DictionaryEntry".equals(tagLocalName)) {
+                    handleDictionaryEntry(reader, responseItem);
+                }
+            }
+        }
+        return responseItem;
+    }
+
+    private void handleDictionaryEntry(XMLStreamReader reader, Item responseItem) throws XMLStreamException {
+        String key = null;
+        while (reader.hasNext() && !(XMLStreamUtil.isEndTag(reader, "DictionaryEntry"))) {
+            reader.next();
+            if (XMLStreamUtil.isStartTag(reader)) {
+                String tagLocalName = reader.getLocalName();
+                if ("Value".equals(tagLocalName)) {
+                    if (key == null) {
+                        key = reader.getElementText();
+                    } else {
+                        responseItem.put(key, XMLStreamUtil.getElementText(reader));
+                    }
+                }
+            }
+        }
+    }
+
+}
diff --git a/src/java/davmail/exchange/ews/IndexedFieldURI.java b/src/java/davmail/exchange/ews/IndexedFieldURI.java
new file mode 100644
index 0000000..a30b3d5
--- /dev/null
+++ b/src/java/davmail/exchange/ews/IndexedFieldURI.java
@@ -0,0 +1,72 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+import davmail.util.StringUtil;
+
+/**
+ * Indexed FieldURI
+ */
+public class IndexedFieldURI implements FieldURI {
+    protected final String fieldURI;
+    protected final String fieldIndex;
+    protected final String fieldItemType;
+    protected final String collectionName;
+
+    /**
+     * Create indexed field uri.
+     *
+     * @param fieldURI       base field uri
+     * @param fieldIndex     field name
+     * @param fieldItemType  field item type
+     * @param collectionName collection name
+     */
+    public IndexedFieldURI(String fieldURI, String fieldIndex, String fieldItemType, String collectionName) {
+        this.fieldURI = fieldURI;
+        this.fieldIndex = fieldIndex;
+        this.fieldItemType = fieldItemType;
+        this.collectionName = collectionName;
+    }
+
+    public void appendTo(StringBuilder buffer) {
+        buffer.append("<t:IndexedFieldURI FieldURI=\"").append(fieldURI);
+        buffer.append("\" FieldIndex=\"").append(fieldIndex);
+        buffer.append("\"/>");
+    }
+
+    public void appendValue(StringBuilder buffer, String itemType, String value) {
+        if (itemType != null) {
+            // append IndexedFieldURI
+            appendTo(buffer);
+            buffer.append("<t:").append(fieldItemType).append('>');
+            buffer.append("<t:").append(collectionName).append('>');
+        }
+        buffer.append("<t:Entry Key=\"").append(fieldIndex).append("\">");
+        buffer.append(StringUtil.xmlEncodeAttribute(value));
+        buffer.append("</t:Entry>");
+        if (itemType != null) {
+            buffer.append("</t:").append(collectionName).append('>');
+            buffer.append("</t:").append(fieldItemType).append('>');
+        }
+    }
+
+    public String getResponseName() {
+        return fieldIndex;
+    }
+}
diff --git a/src/java/davmail/exchange/ews/IndexedFieldUpdate.java b/src/java/davmail/exchange/ews/IndexedFieldUpdate.java
new file mode 100644
index 0000000..b340830
--- /dev/null
+++ b/src/java/davmail/exchange/ews/IndexedFieldUpdate.java
@@ -0,0 +1,83 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Field update with multiple values.
+ */
+public class IndexedFieldUpdate extends FieldUpdate {
+    final Set<FieldUpdate> updates = new HashSet<FieldUpdate>();
+    protected final String collectionName;
+
+    /**
+     * Create indexed field update object.
+     *
+     * @param collectionName values collection name e.g. EmailAddresses
+     */
+    public IndexedFieldUpdate(String collectionName) {
+        this.collectionName = collectionName;
+    }
+
+    /**
+     * Add indexed field value.
+     *
+     * @param fieldUpdate field update object
+     */
+    public void addFieldValue(FieldUpdate fieldUpdate) {
+        updates.add(fieldUpdate);
+    }
+
+    /**
+     * Write field to request writer.
+     *
+     * @param itemType item type
+     * @param writer   request writer
+     * @throws IOException on error
+     */
+    @Override
+    public void write(String itemType, Writer writer) throws IOException {
+        if (itemType == null) {
+            // use collection name on create
+            writer.write("<t:");
+            writer.write(collectionName);
+            writer.write(">");
+
+            StringBuilder buffer = new StringBuilder();
+            for (FieldUpdate fieldUpdate : updates) {
+                fieldUpdate.fieldURI.appendValue(buffer, null, fieldUpdate.value);
+            }
+            writer.write(buffer.toString());
+
+            writer.write("</t:");
+            writer.write(collectionName);
+            writer.write(">");
+        } else {
+            // on update, write each fieldupdate
+            for (FieldUpdate fieldUpdate : updates) {
+                fieldUpdate.write(itemType, writer);
+            }
+        }
+    }
+
+}
diff --git a/src/java/davmail/exchange/ews/ItemId.java b/src/java/davmail/exchange/ews/ItemId.java
new file mode 100644
index 0000000..86c3d9a
--- /dev/null
+++ b/src/java/davmail/exchange/ews/ItemId.java
@@ -0,0 +1,89 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Item id.
+ */
+public class ItemId {
+    protected final String name;
+    protected final String id;
+    protected final String changeKey;
+
+    /**
+     * Build Item id from response item.
+     *
+     * @param item response item
+     */
+    public ItemId(EWSMethod.Item item) {
+        this("ItemId", item);
+    }
+
+    /**
+     * Build Item id object from item id.
+     *
+     * @param itemId item id
+     */
+    public ItemId(String itemId) {
+        this("ItemId", itemId);
+    }
+
+    /**
+     * Build Item id from response item.
+     *
+     * @param item response item
+     */
+    public ItemId(String name, EWSMethod.Item item) {
+        this.name = name;
+        this.id = item.get("ItemId");
+        this.changeKey = item.get("ChangeKey");
+    }
+
+    /**
+     * Build Item id object from item id.
+     *
+     * @param itemId item id
+     */
+    public ItemId(String name, String itemId) {
+        this.name = name;
+        this.id = itemId;
+        this.changeKey = null;
+    }
+
+    /**
+     * Write item id as XML.
+     *
+     * @param writer request writer
+     * @throws IOException on error
+     */
+    public void write(Writer writer) throws IOException {
+        writer.write("<t:");
+        writer.write(name);
+        writer.write(" Id=\"");
+        writer.write(id);
+        if (changeKey != null) {
+            writer.write("\" ChangeKey=\"");
+            writer.write(changeKey);
+        }
+        writer.write("\"/>");
+    }
+}
diff --git a/src/java/davmail/exchange/ews/MessageDisposition.java b/src/java/davmail/exchange/ews/MessageDisposition.java
new file mode 100644
index 0000000..651b041
--- /dev/null
+++ b/src/java/davmail/exchange/ews/MessageDisposition.java
@@ -0,0 +1,34 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+/**
+ * MessageDisposition flag.
+ */
+ at SuppressWarnings({"JavaDoc", "UnusedDeclaration"})
+public final class MessageDisposition extends AttributeOption {
+
+    private MessageDisposition(String value) {
+        super("MessageDisposition", value);
+    }
+
+    public static final MessageDisposition SaveOnly = new MessageDisposition("SaveOnly");
+    public static final MessageDisposition SendOnly = new MessageDisposition("SendOnly");
+    public static final MessageDisposition SendAndSaveCopy = new MessageDisposition("SendAndSaveCopy");
+}
diff --git a/src/java/davmail/exchange/ews/MoveFolderMethod.java b/src/java/davmail/exchange/ews/MoveFolderMethod.java
new file mode 100644
index 0000000..19bc4db
--- /dev/null
+++ b/src/java/davmail/exchange/ews/MoveFolderMethod.java
@@ -0,0 +1,36 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+/**
+ * Create Folder method.
+ */
+public class MoveFolderMethod extends EWSMethod {
+    /**
+     * Move folder to target folder.
+     *
+     * @param folderId   folder id
+     * @param toFolderId target folder id
+     */
+    public MoveFolderMethod(FolderId folderId, FolderId toFolderId) {
+        super("Folder", "MoveFolder");
+        this.folderId = folderId;
+        this.toFolderId = toFolderId;
+    }
+}
diff --git a/src/java/davmail/exchange/ews/MoveItemMethod.java b/src/java/davmail/exchange/ews/MoveItemMethod.java
new file mode 100644
index 0000000..e22d135
--- /dev/null
+++ b/src/java/davmail/exchange/ews/MoveItemMethod.java
@@ -0,0 +1,36 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+/**
+ * Move Item method.
+ */
+public class MoveItemMethod extends EWSMethod {
+    /**
+     * Move item to target folder.
+     *
+     * @param itemId     item id
+     * @param toFolderId target folder id
+     */
+    public MoveItemMethod(ItemId itemId, FolderId toFolderId) {
+        super("Item", "MoveItem");
+        this.itemId = itemId;
+        this.toFolderId = toFolderId;
+    }
+}
diff --git a/src/java/davmail/exchange/ews/Option.java b/src/java/davmail/exchange/ews/Option.java
new file mode 100644
index 0000000..53cfdfa
--- /dev/null
+++ b/src/java/davmail/exchange/ews/Option.java
@@ -0,0 +1,44 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Generic option.
+ */
+public abstract class Option {
+    protected final String name;
+    protected final String value;
+
+    protected Option(String name, String value) {
+        this.name = name;
+        this.value = value;
+    }
+
+    /**
+     * Write XML content to writer.
+     *
+     * @param writer writer
+     * @throws IOException on error
+     */
+    public abstract void write(Writer writer) throws IOException;
+
+}
diff --git a/src/java/davmail/exchange/ews/ResolveNamesMethod.java b/src/java/davmail/exchange/ews/ResolveNamesMethod.java
new file mode 100644
index 0000000..812da74
--- /dev/null
+++ b/src/java/davmail/exchange/ews/ResolveNamesMethod.java
@@ -0,0 +1,158 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+import davmail.exchange.XMLStreamUtil;
+
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+
+/**
+ * Resolve Names method.
+ */
+public class ResolveNamesMethod extends EWSMethod {
+    protected static final AttributeOption RETURN_FULL_CONTACT_DATA = new AttributeOption("ReturnFullContactData", "true");
+
+    /**
+     * Build Resolve Names method
+     *
+     * @param value search value
+     */
+    public ResolveNamesMethod(String value) {
+        super("Contact", "ResolveNames", "ResolutionSet");
+        addMethodOption(SearchScope.ActiveDirectory);
+        addMethodOption(RETURN_FULL_CONTACT_DATA);
+        unresolvedEntry = new ElementOption("m:UnresolvedEntry", value);
+    }
+
+    @Override
+    protected Item handleItem(XMLStreamReader reader) throws XMLStreamException {
+        Item responseItem = new Item();
+        responseItem.type = "Contact";
+        // skip to Contact
+        while (reader.hasNext() && !XMLStreamUtil.isStartTag(reader, "Resolution")) {
+            reader.next();
+        }
+        while (reader.hasNext() && !XMLStreamUtil.isEndTag(reader, "Resolution")) {
+            reader.next();
+            if (XMLStreamUtil.isStartTag(reader)) {
+                String tagLocalName = reader.getLocalName();
+                if ("Mailbox".equals(tagLocalName)) {
+                    handleMailbox(reader, responseItem);
+                } else if ("Contact".equals(tagLocalName)) {
+                    handleContact(reader, responseItem);
+                }
+            }
+        }
+        return responseItem;
+    }
+
+    protected void handleMailbox(XMLStreamReader reader, Item responseItem) throws XMLStreamException {
+        while (reader.hasNext() && !XMLStreamUtil.isEndTag(reader, "Mailbox")) {
+            reader.next();
+            if (XMLStreamUtil.isStartTag(reader)) {
+                String tagLocalName = reader.getLocalName();
+                if ("Name".equals(tagLocalName)) {
+                    responseItem.put(tagLocalName, XMLStreamUtil.getElementText(reader));
+                } else if ("EmailAddress".equals(tagLocalName)) {
+                    responseItem.put(tagLocalName, XMLStreamUtil.getElementText(reader));
+                }
+            }
+        }
+    }
+
+    protected void handleContact(XMLStreamReader reader, Item responseItem) throws XMLStreamException {
+        while (reader.hasNext() && !XMLStreamUtil.isEndTag(reader, "Contact")) {
+            reader.next();
+            if (XMLStreamUtil.isStartTag(reader)) {
+                String tagLocalName = reader.getLocalName();
+                if ("EmailAddresses".equals(tagLocalName)) {
+                    handleEmailAddresses(reader, responseItem);
+                } else if ("PhysicalAddresses".equals(tagLocalName)) {
+                    handlePhysicalAddresses(reader, responseItem);
+                } else if ("PhoneNumbers".equals(tagLocalName)) {
+                    handlePhoneNumbers(reader, responseItem);
+                } else {
+                    responseItem.put(tagLocalName, XMLStreamUtil.getElementText(reader));
+                }
+            }
+        }
+    }
+
+    protected void handlePhysicalAddress(XMLStreamReader reader, Item responseItem, String addressType) throws XMLStreamException {
+        while (reader.hasNext() && !XMLStreamUtil.isEndTag(reader, "Entry")) {
+            reader.next();
+            if (XMLStreamUtil.isStartTag(reader)) {
+                String tagLocalName = reader.getLocalName();
+                String value = XMLStreamUtil.getElementText(reader);
+                responseItem.put(addressType + tagLocalName, value);
+            }
+        }
+    }
+
+    protected void handlePhysicalAddresses(XMLStreamReader reader, Item responseItem) throws XMLStreamException {
+        while (reader.hasNext() && !XMLStreamUtil.isEndTag(reader, "PhysicalAddresses")) {
+            reader.next();
+            if (XMLStreamUtil.isStartTag(reader)) {
+                String tagLocalName = reader.getLocalName();
+                if ("Entry".equals(tagLocalName)) {
+                    String key = getAttributeValue(reader, "Key");
+                    handlePhysicalAddress(reader, responseItem, key);
+                }
+            }
+        }
+    }
+
+    protected void handlePhoneNumbers(XMLStreamReader reader, Item responseItem) throws XMLStreamException {
+        while (reader.hasNext() && !XMLStreamUtil.isEndTag(reader, "PhoneNumbers")) {
+            reader.next();
+            if (XMLStreamUtil.isStartTag(reader)) {
+                String tagLocalName = reader.getLocalName();
+                if ("Entry".equals(tagLocalName)) {
+                    String key = getAttributeValue(reader, "Key");
+                    String value = XMLStreamUtil.getElementText(reader);
+                    responseItem.put(key, value);
+                }
+            }
+        }
+    }
+
+    @Override
+    protected void handleEmailAddresses(XMLStreamReader reader, Item responseItem) throws XMLStreamException {
+        while (reader.hasNext() && !XMLStreamUtil.isEndTag(reader, "EmailAddresses")) {
+            reader.next();
+            if (XMLStreamUtil.isStartTag(reader)) {
+                String tagLocalName = reader.getLocalName();
+                if ("Entry".equals(tagLocalName)) {
+                    String value = XMLStreamUtil.getElementText(reader);
+                    if (value != null) {
+                        if (value.startsWith("smtp:") || value.startsWith("SMTP:")) {
+                            value = value.substring(5);
+                            // get smtp address only if not already available through Mailbox info
+                            if (responseItem.get("EmailAddress") == null) {
+                                responseItem.put("EmailAddress", value);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+}
diff --git a/src/java/davmail/exchange/ews/SearchExpression.java b/src/java/davmail/exchange/ews/SearchExpression.java
new file mode 100644
index 0000000..f42e1ec
--- /dev/null
+++ b/src/java/davmail/exchange/ews/SearchExpression.java
@@ -0,0 +1,31 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+/**
+ * EWS Search Expression.
+ */
+public interface SearchExpression {
+    /**
+     * Append search expression to buffer.
+     *
+     * @param buffer search buffer
+     */
+    void appendTo(StringBuilder buffer);
+}
diff --git a/src/java/davmail/exchange/ews/SearchScope.java b/src/java/davmail/exchange/ews/SearchScope.java
new file mode 100644
index 0000000..6be2c33
--- /dev/null
+++ b/src/java/davmail/exchange/ews/SearchScope.java
@@ -0,0 +1,34 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+/**
+ * ResolveNames search scope.
+ */
+ at SuppressWarnings({"JavaDoc", "UnusedDeclaration"})
+public final class SearchScope extends AttributeOption {
+    private SearchScope(String value) {
+        super("SearchScope", value);
+    }
+
+    public static final SearchScope ActiveDirectory = new SearchScope("ActiveDirectory");
+    public static final SearchScope ActiveDirectoryContacts = new SearchScope("ActiveDirectoryContacts");
+    public static final SearchScope Contacts = new SearchScope("Contacts");
+    public static final SearchScope ContactsActiveDirectory = new SearchScope("ContactsActiveDirectory");
+}
diff --git a/src/java/davmail/exchange/ews/SendMeetingCancellations.java b/src/java/davmail/exchange/ews/SendMeetingCancellations.java
new file mode 100644
index 0000000..359826f
--- /dev/null
+++ b/src/java/davmail/exchange/ews/SendMeetingCancellations.java
@@ -0,0 +1,33 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+/**
+ * Item update option.
+ */
+ at SuppressWarnings({"JavaDoc", "UnusedDeclaration"})
+public final class SendMeetingCancellations extends AttributeOption {
+    private SendMeetingCancellations(String value) {
+        super("SendMeetingCancellations", value);
+    }
+
+    public static final SendMeetingCancellations SendToNone = new SendMeetingCancellations("SendToNone");
+    public static final SendMeetingCancellations SendOnlyToAll = new SendMeetingCancellations("SendOnlyToAll");
+    public static final SendMeetingCancellations SendToAllAndSaveCopy = new SendMeetingCancellations("SendToAllAndSaveCopy");
+}
diff --git a/src/java/davmail/exchange/ews/SendMeetingInvitations.java b/src/java/davmail/exchange/ews/SendMeetingInvitations.java
new file mode 100644
index 0000000..6b8d966
--- /dev/null
+++ b/src/java/davmail/exchange/ews/SendMeetingInvitations.java
@@ -0,0 +1,33 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+/**
+ * Item update option.
+ */
+ at SuppressWarnings({"UnusedDeclaration", "JavaDoc"})
+public final class SendMeetingInvitations extends AttributeOption {
+    private SendMeetingInvitations(String value) {
+        super("SendMeetingInvitations", value);
+    }
+
+    public static final SendMeetingInvitations SendToNone = new SendMeetingInvitations("SendToNone");
+    public static final SendMeetingInvitations SendOnlyToAll = new SendMeetingInvitations("SendOnlyToAll");
+    public static final SendMeetingInvitations SendToAllAndSaveCopy = new SendMeetingInvitations("SendToAllAndSaveCopy");
+}
diff --git a/src/java/davmail/exchange/ews/SendMeetingInvitationsOrCancellations.java b/src/java/davmail/exchange/ews/SendMeetingInvitationsOrCancellations.java
new file mode 100644
index 0000000..55973b9
--- /dev/null
+++ b/src/java/davmail/exchange/ews/SendMeetingInvitationsOrCancellations.java
@@ -0,0 +1,33 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+/**
+ * Item update option.
+ */
+ at SuppressWarnings({"JavaDoc", "UnusedDeclaration"})
+public final class SendMeetingInvitationsOrCancellations extends AttributeOption {
+    private SendMeetingInvitationsOrCancellations(String value) {
+        super("SendMeetingInvitationsOrCancellations", value);
+    }
+
+    public static final SendMeetingInvitationsOrCancellations SendToNone = new SendMeetingInvitationsOrCancellations("SendToNone");
+    public static final SendMeetingInvitationsOrCancellations SendOnlyToAll = new SendMeetingInvitationsOrCancellations("SendOnlyToAll");
+    public static final SendMeetingInvitationsOrCancellations SendToAllAndSaveCopy = new SendMeetingInvitationsOrCancellations("SendToAllAndSaveCopy");
+}
diff --git a/src/java/davmail/exchange/ews/TwoOperandExpression.java b/src/java/davmail/exchange/ews/TwoOperandExpression.java
new file mode 100644
index 0000000..6d9ef7c
--- /dev/null
+++ b/src/java/davmail/exchange/ews/TwoOperandExpression.java
@@ -0,0 +1,65 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+import davmail.util.StringUtil;
+
+/**
+ * Two operand expression.
+ */
+public class TwoOperandExpression implements SearchExpression {
+    @SuppressWarnings({"UnusedDeclaration"})
+    protected enum Operator {
+        IsEqualTo, IsNotEqualTo, IsGreaterThan, IsGreaterThanOrEqualTo, IsLessThan, IsLessThanOrEqualTo
+    }
+
+    protected final Operator operator;
+    protected final FieldURI fieldURI;
+    protected final String value;
+
+    /**
+     * Create two operand expression.
+     *
+     * @param operator operator
+     * @param fieldURI field operand
+     * @param value    value operand
+     */
+    public TwoOperandExpression(Operator operator, FieldURI fieldURI, String value) {
+        this.operator = operator;
+        this.fieldURI = fieldURI;
+        this.value = value;
+    }
+
+    public void appendTo(StringBuilder buffer) {
+        buffer.append("<t:").append(operator.toString()).append('>');
+        fieldURI.appendTo(buffer);
+
+        buffer.append("<t:FieldURIOrConstant><t:Constant Value=\"");
+        // encode urlcompname
+        if (fieldURI instanceof ExtendedFieldURI && "0x10f3".equals(((ExtendedFieldURI) fieldURI).propertyTag)) {
+            buffer.append(StringUtil.xmlEncodeAttribute(StringUtil.encodeUrlcompname(value)));
+        } else {
+            buffer.append(StringUtil.xmlEncodeAttribute(value));
+        }
+        buffer.append("\"/></t:FieldURIOrConstant>");
+
+        buffer.append("</t:").append(operator.toString()).append('>');
+    }
+
+}
diff --git a/src/java/davmail/exchange/ews/UnindexedFieldURI.java b/src/java/davmail/exchange/ews/UnindexedFieldURI.java
new file mode 100644
index 0000000..0a2e852
--- /dev/null
+++ b/src/java/davmail/exchange/ews/UnindexedFieldURI.java
@@ -0,0 +1,93 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+import davmail.util.StringUtil;
+
+/**
+ * Unindexed Field URI
+ */
+public class UnindexedFieldURI implements FieldURI {
+    protected final String fieldURI;
+    protected final String fieldName;
+
+    /**
+     * Create unindexed field URI.
+     *
+     * @param fieldURI field name
+     */
+    public UnindexedFieldURI(String fieldURI) {
+        this.fieldURI = fieldURI;
+        int colonIndex = fieldURI.indexOf(':');
+        if (colonIndex < 0) {
+            fieldName = fieldURI;
+        } else {
+            fieldName = fieldURI.substring(colonIndex + 1);
+        }
+    }
+
+    public void appendTo(StringBuilder buffer) {
+        buffer.append("<t:FieldURI FieldURI=\"").append(fieldURI).append("\"/>");
+    }
+
+    public void appendValue(StringBuilder buffer, String itemType, String value) {
+        if (fieldURI.startsWith("message") && itemType != null) {
+            itemType = "Message";
+        } else if (fieldURI.startsWith("calendar") && itemType != null) {
+            itemType = "CalendarItem";
+        } else if (fieldURI.startsWith("task") && itemType != null) {
+            itemType = "Task";
+        } else if (fieldURI.startsWith("contacts") && itemType != null) {
+            itemType = "Contact";
+        }
+        if (itemType != null) {
+            appendTo(buffer);
+            buffer.append("<t:");
+            buffer.append(itemType);
+            buffer.append('>');
+        }
+        if ("MeetingTimeZone".equals(fieldName)) {
+            buffer.append("<t:MeetingTimeZone TimeZoneName=\"");
+            buffer.append(StringUtil.xmlEncodeAttribute(value));
+            buffer.append("\"></t:MeetingTimeZone>");
+        } else if ("StartTimeZone".equals(fieldName)) {
+            buffer.append("<t:StartTimeZone Id=\"");
+            buffer.append(StringUtil.xmlEncodeAttribute(value));
+            buffer.append("\"></t:StartTimeZone>");
+        } else {
+            buffer.append("<t:");
+            buffer.append(fieldName);
+            buffer.append('>');
+            buffer.append(StringUtil.xmlEncodeAttribute(value));
+            buffer.append("</t:");
+            buffer.append(fieldName);
+            buffer.append('>');
+        }
+        if (itemType != null) {
+            buffer.append("</t:");
+            buffer.append(itemType);
+            buffer.append('>');
+        }
+    }
+
+    public String getResponseName() {
+        return fieldName;
+    }
+
+}
diff --git a/src/java/davmail/exchange/ews/UpdateFolderMethod.java b/src/java/davmail/exchange/ews/UpdateFolderMethod.java
new file mode 100644
index 0000000..18982b4
--- /dev/null
+++ b/src/java/davmail/exchange/ews/UpdateFolderMethod.java
@@ -0,0 +1,38 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+import java.util.List;
+
+/**
+ * Update Folder method.
+ */
+public class UpdateFolderMethod extends EWSMethod {
+    /**
+     * Update folder options.
+     *
+     * @param folderId folder id
+     * @param updates  folder properties updates
+     */
+    public UpdateFolderMethod(FolderId folderId, List<FieldUpdate> updates) {
+        super("Folder", "UpdateFolder");
+        this.folderId = folderId;
+        this.updates = updates;
+    }
+}
\ No newline at end of file
diff --git a/src/java/davmail/exchange/ews/UpdateItemMethod.java b/src/java/davmail/exchange/ews/UpdateItemMethod.java
new file mode 100644
index 0000000..71e4d1d
--- /dev/null
+++ b/src/java/davmail/exchange/ews/UpdateItemMethod.java
@@ -0,0 +1,47 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+import java.util.List;
+
+/**
+ * Update Item method.
+ */
+public class UpdateItemMethod extends EWSMethod {
+    /**
+     * Update exchange item.
+     *
+     * @param messageDisposition save or send option
+     * @param conflictResolution overwrite option
+     * @param sendMeetingInvitationsOrCancellations
+     *                           send invitations option
+     * @param itemId             item id with change key
+     * @param updates            field updates
+     */
+    public UpdateItemMethod(MessageDisposition messageDisposition, ConflictResolution conflictResolution,
+                            SendMeetingInvitationsOrCancellations sendMeetingInvitationsOrCancellations,
+                            ItemId itemId, List<FieldUpdate> updates) {
+        super("Item", "UpdateItem");
+        this.itemId = itemId;
+        this.updates = updates;
+        addMethodOption(messageDisposition);
+        addMethodOption(conflictResolution);
+        addMethodOption(sendMeetingInvitationsOrCancellations);
+    }
+}
\ No newline at end of file
diff --git a/src/java/davmail/http/DavGatewayHttpClientFacade.java b/src/java/davmail/http/DavGatewayHttpClientFacade.java
new file mode 100644
index 0000000..0475b34
--- /dev/null
+++ b/src/java/davmail/http/DavGatewayHttpClientFacade.java
@@ -0,0 +1,751 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.http;
+
+import davmail.BundleMessage;
+import davmail.Settings;
+import davmail.exception.*;
+import davmail.exchange.dav.ExchangeDavMethod;
+import davmail.exchange.dav.ExchangeSearchMethod;
+import davmail.ui.tray.DavGatewayTray;
+import org.apache.commons.httpclient.*;
+import org.apache.commons.httpclient.auth.AuthPolicy;
+import org.apache.commons.httpclient.auth.AuthScope;
+import org.apache.commons.httpclient.cookie.CookiePolicy;
+import org.apache.commons.httpclient.methods.DeleteMethod;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.httpclient.params.HttpClientParams;
+import org.apache.commons.httpclient.params.HttpMethodParams;
+import org.apache.commons.httpclient.util.IdleConnectionTimeoutThread;
+import org.apache.commons.httpclient.util.URIUtil;
+import org.apache.jackrabbit.webdav.DavException;
+import org.apache.jackrabbit.webdav.MultiStatusResponse;
+import org.apache.jackrabbit.webdav.client.methods.CopyMethod;
+import org.apache.jackrabbit.webdav.client.methods.DavMethodBase;
+import org.apache.jackrabbit.webdav.client.methods.MoveMethod;
+import org.apache.jackrabbit.webdav.client.methods.PropFindMethod;
+import org.apache.jackrabbit.webdav.property.DavPropertyNameSet;
+import org.apache.log4j.Logger;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Create HttpClient instance according to DavGateway Settings
+ */
+public final class DavGatewayHttpClientFacade {
+    static final Logger LOGGER = Logger.getLogger("davmail.http.DavGatewayHttpClientFacade");
+
+    static final String IE_USER_AGENT = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)";
+    static final int MAX_REDIRECTS = 10;
+    static final Object LOCK = new Object();
+    private static boolean needNTLM;
+
+    static final long ONE_MINUTE = 60000;
+
+    private static IdleConnectionTimeoutThread httpConnectionManagerThread;
+
+    static {
+        // workaround for TLS Renegotiation issue see http://java.sun.com/javase/javaseforbusiness/docs/TLSReadme.html    
+        System.setProperty("sun.security.ssl.allowUnsafeRenegotiation", "true");
+
+        DavGatewayHttpClientFacade.start();
+
+        // register custom cookie policy
+        CookiePolicy.registerCookieSpec("DavMailCookieSpec", DavMailCookieSpec.class);
+
+        AuthPolicy.registerAuthScheme(AuthPolicy.BASIC, LenientBasicScheme.class);
+
+    }
+
+
+    private DavGatewayHttpClientFacade() {
+    }
+
+    /**
+     * Create basic http client with default params.
+     *
+     * @return HttpClient instance
+     */
+    private static HttpClient getBaseInstance() {
+        HttpClient httpClient = new HttpClient();
+        httpClient.getParams().setParameter(HttpMethodParams.USER_AGENT, IE_USER_AGENT);
+        httpClient.getParams().setParameter(HttpClientParams.MAX_REDIRECTS, MAX_REDIRECTS);
+        httpClient.getParams().setCookiePolicy("DavMailCookieSpec");
+        return httpClient;
+    }
+
+    /**
+     * Create a configured HttpClient instance.
+     *
+     * @param url target url
+     * @return httpClient
+     * @throws DavMailException on error
+     */
+    public static HttpClient getInstance(String url) throws DavMailException {
+        // create an HttpClient instance
+        HttpClient httpClient = getBaseInstance();
+        configureClient(httpClient, url);
+        return httpClient;
+    }
+
+    /**
+     * Set credentials on HttpClient instance.
+     *
+     * @param httpClient httpClient instance
+     * @param userName   user name
+     * @param password   user password
+     */
+    public static void setCredentials(HttpClient httpClient, String userName, String password) {
+        // some Exchange servers redirect to a different host for freebusy, use wide auth scope
+        AuthScope authScope = new AuthScope(null, -1);
+        httpClient.getState().setCredentials(authScope, new NTCredentials(userName, password, "UNKNOWN", ""));
+    }
+
+    /**
+     * Set http client current host configuration.
+     *
+     * @param httpClient current Http client
+     * @param url        target url
+     * @throws DavMailException on error
+     */
+    public static void setClientHost(HttpClient httpClient, String url) throws DavMailException {
+        try {
+            HostConfiguration hostConfig = httpClient.getHostConfiguration();
+            URI httpURI = new URI(url, true);
+            hostConfig.setHost(httpURI);
+        } catch (URIException e) {
+            throw new DavMailException("LOG_INVALID_URL", url);
+        }
+    }
+
+    /**
+     * Update http client configuration (proxy)
+     *
+     * @param httpClient current Http client
+     * @param url        target url
+     * @throws DavMailException on error
+     */
+    public static void configureClient(HttpClient httpClient, String url) throws DavMailException {
+        setClientHost(httpClient, url);
+
+        if (!needNTLM) {
+            ArrayList<String> authPrefs = new ArrayList<String>();
+            authPrefs.add(AuthPolicy.DIGEST);
+            authPrefs.add(AuthPolicy.BASIC);
+            // exclude NTLM authentication scheme
+            httpClient.getParams().setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs);
+        }
+
+        boolean enableProxy = Settings.getBooleanProperty("davmail.enableProxy");
+        boolean useSystemProxies = Settings.getBooleanProperty("davmail.useSystemProxies", Boolean.FALSE);
+        String proxyHost = null;
+        int proxyPort = 0;
+        String proxyUser = null;
+        String proxyPassword = null;
+
+        if (useSystemProxies) {
+            // get proxy for url from system settings
+            System.setProperty("java.net.useSystemProxies", "true");
+            try {
+                List<Proxy> proxyList = getProxyForURI(new java.net.URI(url));
+                if (!proxyList.isEmpty() && proxyList.get(0).address() != null) {
+                    InetSocketAddress inetSocketAddress = (InetSocketAddress) proxyList.get(0).address();
+                    proxyHost = inetSocketAddress.getHostName();
+                    proxyPort = inetSocketAddress.getPort();
+
+                    // we may still need authentication credentials
+                    proxyUser = Settings.getProperty("davmail.proxyUser");
+                    proxyPassword = Settings.getProperty("davmail.proxyPassword");
+                }
+            } catch (URISyntaxException e) {
+                throw new DavMailException("LOG_INVALID_URL", url);
+            }
+        } else if (enableProxy) {
+            proxyHost = Settings.getProperty("davmail.proxyHost");
+            proxyPort = Settings.getIntProperty("davmail.proxyPort");
+            proxyUser = Settings.getProperty("davmail.proxyUser");
+            proxyPassword = Settings.getProperty("davmail.proxyPassword");
+        }
+
+        // configure proxy
+        if (proxyHost != null && proxyHost.length() > 0) {
+            httpClient.getHostConfiguration().setProxy(proxyHost, proxyPort);
+            if (proxyUser != null && proxyUser.length() > 0) {
+
+                AuthScope authScope = new AuthScope(proxyHost, proxyPort, AuthScope.ANY_REALM);
+
+                // detect ntlm authentication (windows domain name in user name)
+                int backslashindex = proxyUser.indexOf('\\');
+                if (backslashindex > 0) {
+                    httpClient.getState().setProxyCredentials(authScope,
+                            new NTCredentials(proxyUser.substring(backslashindex + 1),
+                                    proxyPassword, "UNKNOWN",
+                                    proxyUser.substring(0, backslashindex)));
+                } else {
+                    httpClient.getState().setProxyCredentials(authScope,
+                            new NTCredentials(proxyUser, proxyPassword, "UNKNOWN", ""));
+                }
+            }
+        }
+
+    }
+
+    /**
+     * Retrieve Proxy Selector
+     *
+     * @param uri target uri
+     * @return proxy selector
+     */
+    private static List<Proxy> getProxyForURI(java.net.URI uri) {
+        LOGGER.debug("get Default proxy selector");
+        ProxySelector proxySelector = ProxySelector.getDefault();
+        LOGGER.debug("getProxyForURI(" + uri + ')');
+        List<Proxy> proxies = proxySelector.select(uri);
+        LOGGER.debug("got system proxies:" + proxies);
+        return proxies;
+    }
+
+
+    /**
+     * Get Http Status code for the given URL
+     *
+     * @param httpClient httpClient instance
+     * @param url        url string
+     * @return HttpStatus code
+     */
+    public static int getHttpStatus(HttpClient httpClient, String url) {
+        int status = 0;
+        HttpMethod testMethod = new GetMethod(url);
+        testMethod.setDoAuthentication(false);
+        try {
+            status = httpClient.executeMethod(testMethod);
+        } catch (IOException e) {
+            LOGGER.warn(e.getMessage(), e);
+        } finally {
+            testMethod.releaseConnection();
+        }
+        return status;
+    }
+
+    /**
+     * Check if status is a redirect (various 30x values).
+     *
+     * @param status Http status
+     * @return true if status is a redirect
+     */
+    public static boolean isRedirect(int status) {
+        return status == HttpStatus.SC_MOVED_PERMANENTLY
+                || status == HttpStatus.SC_MOVED_TEMPORARILY
+                || status == HttpStatus.SC_SEE_OTHER
+                || status == HttpStatus.SC_TEMPORARY_REDIRECT;
+    }
+
+    /**
+     * Execute given url, manually follow redirects.
+     * Workaround for HttpClient bug (GET full URL over HTTPS and proxy)
+     *
+     * @param httpClient HttpClient instance
+     * @param url        url string
+     * @return executed method
+     * @throws IOException on error
+     */
+    public static HttpMethod executeFollowRedirects(HttpClient httpClient, String url) throws IOException {
+        HttpMethod method = new GetMethod(url);
+        method.setFollowRedirects(false);
+        return executeFollowRedirects(httpClient, method);
+    }
+
+    private static int checkNTLM(HttpClient httpClient, HttpMethod currentMethod) throws IOException {
+        int status = currentMethod.getStatusCode();
+        if ((status == HttpStatus.SC_UNAUTHORIZED || status == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED)
+                && acceptsNTLMOnly(currentMethod) && !hasNTLM(httpClient)) {
+            LOGGER.debug("Received " + status + " unauthorized at " + currentMethod.getURI() + ", retrying with NTLM");
+            resetMethod(currentMethod);
+            addNTLM(httpClient);
+            status = httpClient.executeMethod(currentMethod);
+        }
+        return status;
+    }
+
+    /**
+     * Execute method with httpClient, follow 30x redirects.
+     *
+     * @param httpClient Http client instance
+     * @param method     Http method
+     * @return last http method after redirects
+     * @throws IOException on error
+     */
+    public static HttpMethod executeFollowRedirects(HttpClient httpClient, HttpMethod method) throws IOException {
+        HttpMethod currentMethod = method;
+        try {
+            DavGatewayTray.debug(new BundleMessage("LOG_EXECUTE_FOLLOW_REDIRECTS", currentMethod.getURI()));
+            httpClient.executeMethod(currentMethod);
+            int status = checkNTLM(httpClient, currentMethod);
+
+            Header location = currentMethod.getResponseHeader("Location");
+            int redirectCount = 0;
+            while (redirectCount++ < 10
+                    && location != null
+                    && isRedirect(status)) {
+                // Novell iChain workaround
+                String locationValue = location.getValue();
+                if (locationValue.indexOf('"') >= 0) {
+                    locationValue = URIUtil.encodePath(locationValue);
+                }
+                // workaround for invalid relative location
+                if (locationValue.startsWith("./")) {
+                    locationValue = locationValue.substring(1);
+                }
+                currentMethod.releaseConnection();
+                currentMethod = new GetMethod(locationValue);
+                currentMethod.setFollowRedirects(false);
+                DavGatewayTray.debug(new BundleMessage("LOG_EXECUTE_FOLLOW_REDIRECTS_COUNT", currentMethod.getURI(), redirectCount));
+                httpClient.executeMethod(currentMethod);
+                checkNTLM(httpClient, currentMethod);
+                location = currentMethod.getResponseHeader("Location");
+            }
+            if (location != null && isRedirect(currentMethod.getStatusCode())) {
+                currentMethod.releaseConnection();
+                throw new HttpException("Maximum redirections reached");
+            }
+        } catch (IOException e) {
+            currentMethod.releaseConnection();
+            throw e;
+        }
+        // caller will need to release connection
+        return currentMethod;
+    }
+
+    /**
+     * Execute method with httpClient, do not follow 30x redirects.
+     *
+     * @param httpClient Http client instance
+     * @param method     Http method
+     * @return status
+     * @throws IOException on error
+     */
+    public static int executeNoRedirect(HttpClient httpClient, HttpMethod method) throws IOException {
+        int status;
+        try {
+            status = httpClient.executeMethod(method);
+            // check NTLM
+            if ((status == HttpStatus.SC_UNAUTHORIZED || status == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED)
+                    && acceptsNTLMOnly(method) && !hasNTLM(httpClient)) {
+                LOGGER.debug("Received " + status + " unauthorized at " + method.getURI() + ", retrying with NTLM");
+                resetMethod(method);
+                addNTLM(httpClient);
+                status = httpClient.executeMethod(method);
+            }
+        } finally {
+            method.releaseConnection();
+        }
+        // caller will need to release connection
+        return status;
+    }
+
+    /**
+     * Execute webdav search method.
+     *
+     * @param httpClient    http client instance
+     * @param path          <i>encoded</i> searched folder path
+     * @param searchRequest (SQL like) search request
+     * @param maxCount      max item count
+     * @return Responses enumeration
+     * @throws IOException on error
+     */
+    public static MultiStatusResponse[] executeSearchMethod(HttpClient httpClient, String path, String searchRequest,
+                                                            int maxCount) throws IOException {
+        ExchangeSearchMethod searchMethod = new ExchangeSearchMethod(path, searchRequest);
+        if (maxCount > 0) {
+            searchMethod.addRequestHeader("Range", "rows=0-" + (maxCount - 1));
+        }
+        return executeMethod(httpClient, searchMethod);
+    }
+
+    /**
+     * Execute webdav propfind method.
+     *
+     * @param httpClient http client instance
+     * @param path       <i>encoded</i> searched folder path
+     * @param depth      propfind request depth
+     * @param properties propfind requested properties
+     * @return Responses enumeration
+     * @throws IOException on error
+     */
+    public static MultiStatusResponse[] executePropFindMethod(HttpClient httpClient, String path, int depth, DavPropertyNameSet properties) throws IOException {
+        PropFindMethod propFindMethod = new PropFindMethod(path, properties, depth);
+        return executeMethod(httpClient, propFindMethod);
+    }
+
+    /**
+     * Execute a delete method on the given path with httpClient.
+     *
+     * @param httpClient Http client instance
+     * @param path       Path to be deleted
+     * @throws IOException on error
+     * @return http status
+     */
+    public static int executeDeleteMethod(HttpClient httpClient, String path) throws IOException {
+        DeleteMethod deleteMethod = new DeleteMethod(path);
+        deleteMethod.setFollowRedirects(false);
+
+        int status = executeHttpMethod(httpClient, deleteMethod);
+        // do not throw error if already deleted
+        if (status != HttpStatus.SC_OK && status != HttpStatus.SC_NOT_FOUND) {
+            throw DavGatewayHttpClientFacade.buildHttpException(deleteMethod);
+        }
+        return status;
+    }
+
+    /**
+     * Execute webdav request.
+     *
+     * @param httpClient http client instance
+     * @param method     webdav method
+     * @return Responses enumeration
+     * @throws IOException on error
+     */
+    public static MultiStatusResponse[] executeMethod(HttpClient httpClient, DavMethodBase method) throws IOException {
+        MultiStatusResponse[] responses = null;
+        try {
+            int status = httpClient.executeMethod(method);
+
+            // need to follow redirects (once) on public folders
+            if (isRedirect(status)) {
+                method.releaseConnection();
+                URI targetUri = new URI(method.getResponseHeader("Location").getValue(), true);
+                checkExpiredSession(targetUri.getQuery());
+                method.setURI(targetUri);
+                status = httpClient.executeMethod(method);
+            }
+
+            if (status != HttpStatus.SC_MULTI_STATUS) {
+                throw buildHttpException(method);
+            }
+            responses = method.getResponseBodyAsMultiStatus().getResponses();
+
+        } catch (DavException e) {
+            throw new IOException(e.getMessage());
+        } finally {
+            method.releaseConnection();
+        }
+        return responses;
+    }
+
+    /**
+     * Execute webdav request.
+     *
+     * @param httpClient http client instance
+     * @param method     webdav method
+     * @return Responses enumeration
+     * @throws IOException on error
+     */
+    public static MultiStatusResponse[] executeMethod(HttpClient httpClient, ExchangeDavMethod method) throws IOException {
+        MultiStatusResponse[] responses = null;
+        try {
+            int status = httpClient.executeMethod(method);
+
+            // need to follow redirects (once) on public folders
+            if (isRedirect(status)) {
+                method.releaseConnection();
+                URI targetUri = new URI(method.getResponseHeader("Location").getValue(), true);
+                checkExpiredSession(targetUri.getQuery());
+                method.setURI(targetUri);
+                status = httpClient.executeMethod(method);
+            }
+
+            if (status != HttpStatus.SC_MULTI_STATUS) {
+                throw buildHttpException(method);
+            }
+            responses = method.getResponses();
+
+        } finally {
+            method.releaseConnection();
+        }
+        return responses;
+    }
+
+    /**
+     * Execute method with httpClient.
+     *
+     * @param httpClient Http client instance
+     * @param method     Http method
+     * @return Http status
+     * @throws IOException on error
+     */
+    public static int executeHttpMethod(HttpClient httpClient, HttpMethod method) throws IOException {
+        int status = 0;
+        try {
+            status = httpClient.executeMethod(method);
+        } finally {
+            method.releaseConnection();
+        }
+        return status;
+    }
+
+    /**
+     * Test if NTLM auth scheme is enabled.
+     *
+     * @param httpClient HttpClient instance
+     * @return true if NTLM is enabled
+     */
+    public static boolean hasNTLM(HttpClient httpClient) {
+        Object authPrefs = httpClient.getParams().getParameter(AuthPolicy.AUTH_SCHEME_PRIORITY);
+        return authPrefs == null || (authPrefs instanceof List<?> && ((Collection) authPrefs).contains(AuthPolicy.NTLM));
+    }
+
+    /**
+     * Enable NTLM authentication on http client
+     *
+     * @param httpClient HttpClient instance
+     */
+    public static void addNTLM(HttpClient httpClient) {
+        // disable preemptive authentication
+        httpClient.getParams().setParameter(HttpClientParams.PREEMPTIVE_AUTHENTICATION, false);
+
+        // register the jcifs based NTLMv2 implementation
+        AuthPolicy.registerAuthScheme(AuthPolicy.NTLM, NTLMv2Scheme.class);
+
+        ArrayList<String> authPrefs = new ArrayList<String>();
+        authPrefs.add(AuthPolicy.NTLM);
+        authPrefs.add(AuthPolicy.DIGEST);
+        authPrefs.add(AuthPolicy.BASIC);
+        httpClient.getParams().setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs);
+
+        // separate domain from username in credentials
+        AuthScope authScope = new AuthScope(null, -1);
+        NTCredentials credentials = (NTCredentials) httpClient.getState().getCredentials(authScope);
+        String userName = credentials.getUserName();
+        int backSlashIndex = userName.indexOf('\\');
+        if (backSlashIndex >= 0) {
+            String domain = userName.substring(0, backSlashIndex);
+            userName = userName.substring(backSlashIndex + 1);
+            credentials = new NTCredentials(userName, credentials.getPassword(), "UNKNOWN", domain);
+            httpClient.getState().setCredentials(authScope, credentials);
+        }
+
+        // make sure NTLM is always active
+        needNTLM = true;
+    }
+
+    /**
+     * Test method header for supported authentication mode,
+     * return true if Basic authentication is not available
+     *
+     * @param getMethod http method
+     * @return true if only NTLM is enabled
+     */
+    public static boolean acceptsNTLMOnly(HttpMethod getMethod) {
+        Header authenticateHeader = null;
+        if (getMethod.getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
+            authenticateHeader = getMethod.getResponseHeader("WWW-Authenticate");
+        } else if (getMethod.getStatusCode() == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED) {
+            authenticateHeader = getMethod.getResponseHeader("Proxy-Authenticate");
+        }
+        if (authenticateHeader == null) {
+            return false;
+        } else {
+            boolean acceptBasic = false;
+            boolean acceptNTLM = false;
+            HeaderElement[] headerElements = authenticateHeader.getElements();
+            for (HeaderElement headerElement : headerElements) {
+                if ("NTLM".equalsIgnoreCase(headerElement.getName())) {
+                    acceptNTLM = true;
+                }
+                if ("Basic realm".equalsIgnoreCase(headerElement.getName())) {
+                    acceptBasic = true;
+                }
+            }
+            return acceptNTLM && !acceptBasic;
+
+        }
+    }
+
+    /**
+     * Execute test method from checkConfig, with proxy credentials, but without Exchange credentials.
+     *
+     * @param httpClient Http client instance
+     * @param method     Http method
+     * @return Http status
+     * @throws IOException on error
+     */
+    public static int executeTestMethod(HttpClient httpClient, GetMethod method) throws IOException {
+        // do not follow redirects in expired sessions
+        method.setFollowRedirects(false);
+        int status = httpClient.executeMethod(method);
+        if (status == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED
+                && acceptsNTLMOnly(method) && !hasNTLM(httpClient)) {
+            resetMethod(method);
+            LOGGER.debug("Received " + status + " unauthorized at " + method.getURI() + ", retrying with NTLM");
+            addNTLM(httpClient);
+            status = httpClient.executeMethod(method);
+        }
+
+        return status;
+    }
+
+    /**
+     * Execute Get method, do not follow redirects.
+     *
+     * @param httpClient      Http client instance
+     * @param method          Http method
+     * @param followRedirects Follow redirects flag
+     * @throws IOException on error
+     */
+    public static void executeGetMethod(HttpClient httpClient, GetMethod method, boolean followRedirects) throws IOException {
+        // do not follow redirects in expired sessions
+        method.setFollowRedirects(followRedirects);
+        int status = httpClient.executeMethod(method);
+        if ((status == HttpStatus.SC_UNAUTHORIZED || status == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED)
+                && acceptsNTLMOnly(method) && !hasNTLM(httpClient)) {
+            resetMethod(method);
+            LOGGER.debug("Received " + status + " unauthorized at " + method.getURI() + ", retrying with NTLM");
+            addNTLM(httpClient);
+            status = httpClient.executeMethod(method);
+        }
+        if (status != HttpStatus.SC_OK && (followRedirects || !isRedirect(status))) {
+            LOGGER.warn("GET failed with status " + status + " at " + method.getURI());
+            if (status != HttpStatus.SC_NOT_FOUND && status != HttpStatus.SC_FORBIDDEN) {
+                LOGGER.warn(method.getResponseBodyAsString());
+            }
+            throw DavGatewayHttpClientFacade.buildHttpException(method);
+        }
+        // check for expired session
+        if (followRedirects) {
+            String queryString = method.getQueryString();
+            checkExpiredSession(queryString);
+        }
+    }
+
+    private static void resetMethod(HttpMethod method) {
+        // reset method state
+        method.releaseConnection();
+        method.getHostAuthState().invalidate();
+        method.getProxyAuthState().invalidate();
+    }
+
+    private static void checkExpiredSession(String queryString) throws DavMailAuthenticationException {
+        if (queryString != null && (queryString.contains("reason=2") || queryString.contains("reason=0"))) {
+            LOGGER.warn("Request failed, session expired");
+            throw new DavMailAuthenticationException("EXCEPTION_SESSION_EXPIRED");
+        }
+    }
+
+    /**
+     * Build Http Exception from methode status
+     *
+     * @param method Http Method
+     * @return Http Exception
+     */
+    public static HttpException buildHttpException(HttpMethod method) {
+        int status = method.getStatusCode();
+        StringBuilder message = new StringBuilder();
+        message.append(status).append(' ').append(method.getStatusText());
+        try {
+            message.append(" at ").append(method.getURI().getURI());
+            if (method instanceof CopyMethod || method instanceof MoveMethod) {
+                message.append(" to ").append(method.getRequestHeader("Destination"));
+            }
+        } catch (URIException e) {
+            message.append(method.getPath());
+        }
+        // 440 means forbidden on Exchange
+        if (status == 440) {
+            return new LoginTimeoutException(message.toString());
+        } else if (status == HttpStatus.SC_FORBIDDEN) {
+            return new HttpForbiddenException(message.toString());
+        } else if (status == HttpStatus.SC_NOT_FOUND) {
+            return new HttpNotFoundException(message.toString());
+        } else if (status == HttpStatus.SC_PRECONDITION_FAILED) {
+            return new HttpPreconditionFailedException(message.toString());
+        } else if (status == HttpStatus.SC_INTERNAL_SERVER_ERROR) {
+            return new HttpServerErrorException(message.toString());
+        } else {
+            return new HttpException(message.toString());
+        }
+    }
+
+    /**
+     * Test if the method response is gzip encoded
+     *
+     * @param method http method
+     * @return true if response is gzip encoded
+     */
+    public static boolean isGzipEncoded(HttpMethod method) {
+        Header[] contentEncodingHeaders = method.getResponseHeaders("Content-Encoding");
+        if (contentEncodingHeaders != null) {
+            for (Header header : contentEncodingHeaders) {
+                if ("gzip".equals(header.getValue())) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Stop HttpConnectionManager.
+     */
+    public static void stop() {
+        synchronized (LOCK) {
+            if (httpConnectionManagerThread != null) {
+                httpConnectionManagerThread.interrupt();
+                httpConnectionManagerThread = null;
+            }
+            MultiThreadedHttpConnectionManager.shutdownAll();
+        }
+    }
+
+    /**
+     * Create and set connection pool.
+     *
+     * @param httpClient httpClient instance
+     */
+    public static void createMultiThreadedHttpConnectionManager(HttpClient httpClient) {
+        MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
+        connectionManager.getParams().setDefaultMaxConnectionsPerHost(100);
+        connectionManager.getParams().setConnectionTimeout(10000);
+        synchronized (LOCK) {
+            httpConnectionManagerThread.addConnectionManager(connectionManager);
+        }
+        httpClient.setHttpConnectionManager(connectionManager);
+    }
+
+    /**
+     * Create and start a new HttpConnectionManager, close idle connections every minute.
+     */
+    public static void start() {
+        synchronized (LOCK) {
+            if (httpConnectionManagerThread == null) {
+                httpConnectionManagerThread = new IdleConnectionTimeoutThread();
+                httpConnectionManagerThread.setName(IdleConnectionTimeoutThread.class.getSimpleName());
+                httpConnectionManagerThread.setConnectionTimeout(ONE_MINUTE);
+                httpConnectionManagerThread.setTimeoutInterval(ONE_MINUTE);
+                httpConnectionManagerThread.start();
+            }
+        }
+    }
+}
diff --git a/src/java/davmail/http/DavGatewayOTPPrompt.java b/src/java/davmail/http/DavGatewayOTPPrompt.java
new file mode 100644
index 0000000..0473380
--- /dev/null
+++ b/src/java/davmail/http/DavGatewayOTPPrompt.java
@@ -0,0 +1,53 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.http;
+
+import davmail.BundleMessage;
+import davmail.ui.PasswordPromptDialog;
+
+import java.awt.*;
+
+/**
+ * Ask user one time password.
+ */
+public final class DavGatewayOTPPrompt {
+    private DavGatewayOTPPrompt() {
+    }
+
+    /**
+     * Ask user token password
+     *
+     * @return user provided one time password
+     */
+    public static String getOneTimePassword() {
+        PasswordPromptDialog passwordPromptDialog = new PasswordPromptDialog(BundleMessage.format("UI_OTP_PASSWORD_PROMPT"));
+        return String.valueOf(passwordPromptDialog.getPassword());
+    }
+
+    /**
+     * Ask user captcha value
+     *
+     * @param captchaImage captcha image
+     * @return user provided one time password
+     */
+    public static String getCaptchaValue(Image captchaImage) {
+        PasswordPromptDialog passwordPromptDialog = new PasswordPromptDialog(BundleMessage.format("UI_CAPTCHA_PROMPT"), captchaImage);
+        return String.valueOf(passwordPromptDialog.getPassword());
+    }
+}
diff --git a/src/java/davmail/http/DavGatewaySSLProtocolSocketFactory.java b/src/java/davmail/http/DavGatewaySSLProtocolSocketFactory.java
new file mode 100644
index 0000000..6aa0ab7
--- /dev/null
+++ b/src/java/davmail/http/DavGatewaySSLProtocolSocketFactory.java
@@ -0,0 +1,226 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.http;
+
+import davmail.BundleMessage;
+import davmail.Settings;
+import davmail.ui.PasswordPromptDialog;
+import davmail.ui.tray.DavGatewayTray;
+import org.apache.commons.httpclient.HttpsURL;
+import org.apache.commons.httpclient.params.HttpConnectionParams;
+import org.apache.commons.httpclient.protocol.Protocol;
+import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
+import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;
+
+import javax.net.ssl.*;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import java.io.File;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.MalformedURLException;
+import java.net.Socket;
+import java.net.URL;
+import java.security.*;
+import java.util.ArrayList;
+
+/**
+ * Manual Socket Factory.
+ * Let user choose to accept or reject certificate
+ */
+public class DavGatewaySSLProtocolSocketFactory implements SecureProtocolSocketFactory {
+    /**
+     * Register custom Socket Factory to let user accept or reject certificate
+     */
+    public static void register() {
+        String urlString = Settings.getProperty("davmail.url");
+        try {
+            URL url = new URL(urlString);
+            String protocol = url.getProtocol();
+            if ("https".equals(protocol)) {
+                int port = url.getPort();
+                if (port < 0) {
+                    port = HttpsURL.DEFAULT_PORT;
+                }
+                Protocol.registerProtocol(url.getProtocol(),
+                        new Protocol(protocol, (ProtocolSocketFactory) new DavGatewaySSLProtocolSocketFactory(), port));
+            }
+        } catch (MalformedURLException e) {
+            DavGatewayTray.error(new BundleMessage("LOG_INVALID_URL", urlString));
+        }
+    }
+
+    private KeyStore.ProtectionParameter getProtectionParameter(String password) {
+        if (password != null && password.length() > 0) {
+            // password provided: create a PasswordProtection
+            return new KeyStore.PasswordProtection(password.toCharArray());
+        } else {
+            // request password at runtime through a callback
+            return new KeyStore.CallbackHandlerProtection(new CallbackHandler() {
+                public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+                    if (callbacks.length > 0 && callbacks[0] instanceof PasswordCallback) {
+                        PasswordPromptDialog passwordPromptDialog = new PasswordPromptDialog(((PasswordCallback) callbacks[0]).getPrompt());
+                        ((PasswordCallback) callbacks[0]).setPassword(passwordPromptDialog.getPassword());
+                    }
+                }
+            });
+        }
+    }
+
+    private SSLContext sslcontext;
+
+    private SSLContext createSSLContext() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, KeyManagementException, KeyStoreException {
+        // PKCS11 client certificate settings
+        String pkcs11Library = Settings.getProperty("davmail.ssl.pkcs11Library");
+
+        String clientKeystoreType = Settings.getProperty("davmail.ssl.clientKeystoreType");
+        // set default keystore type
+        if (clientKeystoreType == null || clientKeystoreType.length() == 0) {
+            clientKeystoreType = "PKCS11";
+        }
+
+        if (pkcs11Library != null && pkcs11Library.length() > 0 && "PKCS11".equals(clientKeystoreType)) {
+            StringBuilder pkcs11Buffer = new StringBuilder();
+            pkcs11Buffer.append("name=DavMail\n");
+            pkcs11Buffer.append("library=").append(pkcs11Library).append('\n');
+            String pkcs11Config = Settings.getProperty("davmail.ssl.pkcs11Config");
+            if (pkcs11Config != null && pkcs11Config.length() > 0) {
+                pkcs11Buffer.append(pkcs11Config).append('\n');
+            }
+            SunPKCS11ProviderHandler.registerProvider(pkcs11Buffer.toString());
+        }
+
+        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(/*KeyManagerFactory.getDefaultAlgorithm()*/"NewSunX509");
+
+        ArrayList<KeyStore.Builder> keyStoreBuilders = new ArrayList<KeyStore.Builder>();
+        // PKCS11 (smartcard) keystore with password callback
+        KeyStore.Builder scBuilder = KeyStore.Builder.newInstance("PKCS11", null, getProtectionParameter(null));
+        keyStoreBuilders.add(scBuilder);
+
+        String clientKeystoreFile = Settings.getProperty("davmail.ssl.clientKeystoreFile");
+        String clientKeystorePass = Settings.getProperty("davmail.ssl.clientKeystorePass");
+        if (clientKeystoreFile != null && clientKeystoreFile.length() > 0
+                && ("PKCS12".equals(clientKeystoreType) || "JKS".equals(clientKeystoreType))) {
+            // PKCS12 file based keystore
+            KeyStore.Builder fsBuilder = KeyStore.Builder.newInstance(clientKeystoreType, null,
+                    new File(clientKeystoreFile), getProtectionParameter(clientKeystorePass));
+            keyStoreBuilders.add(fsBuilder);
+        }
+
+        ManagerFactoryParameters keyStoreBuilderParameters = new KeyStoreBuilderParameters(keyStoreBuilders);
+        keyManagerFactory.init(keyStoreBuilderParameters);
+
+        // Get a list of key managers
+        KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
+
+        // Walk through the key managers and replace all X509 Key Managers with
+        // a specialized wrapped DavMail X509 Key Manager
+        for (int i = 0; i < keyManagers.length; i++) {
+            KeyManager keyManager = keyManagers[i];
+            if (keyManager instanceof X509KeyManager) {
+                keyManagers[i] = new DavMailX509KeyManager((X509KeyManager) keyManager);
+            }
+        }
+
+        SSLContext context = SSLContext.getInstance("SSL");
+        context.init(keyManagers, new TrustManager[]{new DavGatewayX509TrustManager()}, null);
+        return context;
+    }
+
+    private SSLContext getSSLContext() throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, InvalidAlgorithmParameterException {
+        if (this.sslcontext == null) {
+            this.sslcontext = createSSLContext();
+        }
+        return this.sslcontext;
+    }
+
+    public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException {
+        try {
+            return getSSLContext().getSocketFactory().createSocket(host, port, clientHost, clientPort);
+        } catch (NoSuchAlgorithmException e) {
+            throw new IOException(e + " " + e.getMessage());
+        } catch (KeyManagementException e) {
+            throw new IOException(e + " " + e.getMessage());
+        } catch (KeyStoreException e) {
+            throw new IOException(e + " " + e.getMessage());
+        } catch (InvalidAlgorithmParameterException e) {
+            throw new IOException(e + " " + e.getMessage());
+        }
+    }
+
+    public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort, HttpConnectionParams params) throws IOException {
+        try {
+            return getSSLContext().getSocketFactory().createSocket(host, port, clientHost, clientPort);
+        } catch (NoSuchAlgorithmException e) {
+            throw new IOException(e + " " + e.getMessage());
+        } catch (KeyManagementException e) {
+            throw new IOException(e + " " + e.getMessage());
+        } catch (KeyStoreException e) {
+            throw new IOException(e + " " + e.getMessage());
+        } catch (InvalidAlgorithmParameterException e) {
+            throw new IOException(e + " " + e.getMessage());
+        }
+    }
+
+    public Socket createSocket(String host, int port) throws IOException {
+        try {
+            return getSSLContext().getSocketFactory().createSocket(host, port);
+        } catch (NoSuchAlgorithmException e) {
+            throw new IOException(e + " " + e.getMessage());
+        } catch (KeyManagementException e) {
+            throw new IOException(e + " " + e.getMessage());
+        } catch (KeyStoreException e) {
+            throw new IOException(e + " " + e.getMessage());
+        } catch (InvalidAlgorithmParameterException e) {
+            throw new IOException(e + " " + e.getMessage());
+        }
+    }
+
+    public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
+        try {
+            return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);
+        } catch (NoSuchAlgorithmException e) {
+            throw new IOException(e + " " + e.getMessage());
+        } catch (KeyManagementException e) {
+            throw new IOException(e + " " + e.getMessage());
+        } catch (KeyStoreException e) {
+            throw new IOException(e + " " + e.getMessage());
+        } catch (InvalidAlgorithmParameterException e) {
+            throw new IOException(e + " " + e.getMessage());
+        }
+    }
+
+    /**
+     * All instances of SSLProtocolSocketFactory are the same.
+     */
+    @Override
+    public boolean equals(Object obj) {
+        return ((obj != null) && obj.getClass().equals(this.getClass()));
+    }
+
+    /**
+     * All instances of SSLProtocolSocketFactory have the same hash code.
+     */
+    @Override
+    public int hashCode() {
+        return this.getClass().hashCode();
+    }
+}
diff --git a/src/java/davmail/http/DavGatewayX509TrustManager.java b/src/java/davmail/http/DavGatewayX509TrustManager.java
new file mode 100644
index 0000000..b60509e
--- /dev/null
+++ b/src/java/davmail/http/DavGatewayX509TrustManager.java
@@ -0,0 +1,214 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.http;
+
+import davmail.BundleMessage;
+import davmail.Settings;
+import davmail.ui.AcceptCertificateDialog;
+import davmail.ui.tray.DavGatewayTray;
+
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+import java.awt.*;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.security.*;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.text.SimpleDateFormat;
+
+/**
+ * Custom Trust Manager, let user accept or deny.
+ */
+public class DavGatewayX509TrustManager implements X509TrustManager {
+    private final X509TrustManager standardTrustManager;
+
+    /**
+     * Create a new custom X509 trust manager.
+     *
+     * @throws NoSuchAlgorithmException on error
+     * @throws KeyStoreException        on error
+     */
+    public DavGatewayX509TrustManager() throws NoSuchAlgorithmException, KeyStoreException {
+        TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+        factory.init((KeyStore) null);
+        TrustManager[] trustManagers = factory.getTrustManagers();
+        if (trustManagers.length == 0) {
+            throw new NoSuchAlgorithmException("No trust manager found");
+        }
+        this.standardTrustManager = (X509TrustManager) trustManagers[0];
+    }
+
+    public void checkServerTrusted(X509Certificate[] x509Certificates, String authType) throws CertificateException {
+        try {
+            // first try standard Trust Manager
+            this.standardTrustManager.checkServerTrusted(x509Certificates, authType);
+        } catch (CertificateException e) {
+            if ((x509Certificates != null) && (x509Certificates.length > 0)) {
+                userCheckServerTrusted(x509Certificates);
+            } else {
+                throw e;
+            }
+
+        }
+    }
+
+    public void checkClientTrusted(X509Certificate[] x509Certificates, String authType) throws CertificateException {
+        this.standardTrustManager.checkClientTrusted(x509Certificates, authType);
+    }
+
+    public X509Certificate[] getAcceptedIssuers() {
+        return this.standardTrustManager.getAcceptedIssuers();
+    }
+
+    protected void userCheckServerTrusted(final X509Certificate[] x509Certificates) throws CertificateException {
+        String acceptedCertificateHash = Settings.getProperty("davmail.server.certificate.hash");
+        String certificateHash = getFormattedHash(x509Certificates[0]);
+        // if user already accepted a certificate,
+        if (acceptedCertificateHash != null && acceptedCertificateHash.length() > 0
+                && acceptedCertificateHash.equals(certificateHash)) {
+            DavGatewayTray.debug(new BundleMessage("LOG_FOUND_ACCEPTED_CERTIFICATE", acceptedCertificateHash));
+        } else {
+            boolean isCertificateTrusted;
+            if (Settings.getBooleanProperty("davmail.server") || GraphicsEnvironment.isHeadless()) {
+                // headless or server mode
+                isCertificateTrusted = isCertificateTrusted(x509Certificates[0]);
+            } else {
+                isCertificateTrusted = AcceptCertificateDialog.isCertificateTrusted(x509Certificates[0]);
+            }
+            if (!isCertificateTrusted) {
+                throw new CertificateException("User rejected certificate");
+            }
+            // certificate accepted, store in settings
+            Settings.saveProperty("davmail.server.certificate.hash", certificateHash);
+        }
+    }
+
+    @SuppressWarnings({"UseOfSystemOutOrSystemErr"})
+    protected boolean isCertificateTrusted(X509Certificate certificate) {
+        BufferedReader inReader = new BufferedReader(new InputStreamReader(System.in));
+        String answer = null;
+        String yes = BundleMessage.format("UI_ANSWER_YES");
+        String no = BundleMessage.format("UI_ANSWER_NO");
+        StringBuilder buffer = new StringBuilder();
+        buffer.append(BundleMessage.format("UI_SERVER_CERTIFICATE")).append(":\n");
+        buffer.append(BundleMessage.format("UI_ISSUED_TO")).append(": ")
+                .append(DavGatewayX509TrustManager.getRDN(certificate.getSubjectDN())).append('\n');
+        buffer.append(BundleMessage.format("UI_ISSUED_BY")).append(": ")
+                .append(getRDN(certificate.getIssuerDN())).append('\n');
+        SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy");
+        String notBefore = formatter.format(certificate.getNotBefore());
+        buffer.append(BundleMessage.format("UI_VALID_FROM")).append(": ").append(notBefore).append('\n');
+        String notAfter = formatter.format(certificate.getNotAfter());
+        buffer.append(BundleMessage.format("UI_VALID_UNTIL")).append(": ").append(notAfter).append('\n');
+        buffer.append(BundleMessage.format("UI_SERIAL")).append(": ").append(getFormattedSerial(certificate)).append('\n');
+        String sha1Hash = DavGatewayX509TrustManager.getFormattedHash(certificate);
+        buffer.append(BundleMessage.format("UI_FINGERPRINT")).append(": ").append(sha1Hash).append('\n');
+        buffer.append('\n');
+        buffer.append(BundleMessage.format("UI_UNTRUSTED_CERTIFICATE")).append('\n');
+        try {
+            while (!yes.equals(answer) && !no.equals(answer)) {
+                System.out.println(buffer.toString());
+                answer = inReader.readLine();
+                if (answer == null) {
+                    answer = no;
+                }
+                answer = answer.toLowerCase();
+            }
+        } catch (IOException e) {
+            System.err.println(e);
+        }
+        return yes.equals(answer);
+    }
+
+    /**
+     * Get rdn value from principal dn.
+     *
+     * @param principal security principal
+     * @return rdn
+     */
+    public static String getRDN(Principal principal) {
+        String dn = principal.getName();
+        int start = dn.indexOf('=');
+        int end = dn.indexOf(',');
+        if (start >= 0 && end >= 0) {
+            return dn.substring(start + 1, end);
+        } else {
+            return dn;
+        }
+    }
+
+    /**
+     * Build a formatted certificate serial string.
+     *
+     * @param certificate X509 certificate
+     * @return formatted serial
+     */
+    public static String getFormattedSerial(X509Certificate certificate) {
+        StringBuilder builder = new StringBuilder();
+        String serial = certificate.getSerialNumber().toString(16);
+        for (int i = 0; i < serial.length(); i++) {
+            if (i > 0 && i % 2 == 0) {
+                builder.append(' ');
+            }
+            builder.append(serial.charAt(i));
+        }
+        return builder.toString().toUpperCase();
+    }
+
+    /**
+     * Build a formatted hash string.
+     *
+     * @param certificate X509 certificate
+     * @return formatted hash
+     */
+    public static String getFormattedHash(X509Certificate certificate) {
+        String sha1Hash;
+        try {
+            MessageDigest md = MessageDigest.getInstance("SHA1");
+            byte[] digest = md.digest(certificate.getEncoded());
+            sha1Hash = formatHash(digest);
+        } catch (NoSuchAlgorithmException nsa) {
+            sha1Hash = nsa.getMessage();
+        } catch (CertificateEncodingException cee) {
+            sha1Hash = cee.getMessage();
+        }
+        return sha1Hash;
+    }
+
+    /**
+     * Format byte buffer to a hexadecimal hash string.
+     *
+     * @param buffer byte array
+     * @return hexadecimal hash string
+     */
+    protected static String formatHash(byte[] buffer) {
+        StringBuilder builder = new StringBuilder();
+        for (int i = 0; i < buffer.length; i++) {
+            if (i > 0) {
+                builder.append(':');
+            }
+            builder.append(Integer.toHexString(buffer[i] & 0xFF));
+        }
+        return builder.toString().toUpperCase();
+    }
+}
diff --git a/src/java/davmail/http/DavMailCookieSpec.java b/src/java/davmail/http/DavMailCookieSpec.java
new file mode 100644
index 0000000..f20537b
--- /dev/null
+++ b/src/java/davmail/http/DavMailCookieSpec.java
@@ -0,0 +1,62 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.http;
+
+import org.apache.commons.httpclient.Cookie;
+import org.apache.commons.httpclient.cookie.MalformedCookieException;
+import org.apache.commons.httpclient.cookie.RFC2109Spec;
+
+/**
+ * Custom CookieSpec to allow extended domain names.
+ */
+public class DavMailCookieSpec extends RFC2109Spec {
+    @Override
+    public void validate(String host, int port, String path,
+                         boolean secure, final Cookie cookie) throws MalformedCookieException {
+        // workaround for space in cookie name
+        String cookieName = cookie.getName();
+        if (cookieName != null && cookieName.indexOf(' ') >= 0) {
+            cookie.setName(cookieName.replaceAll(" ", ""));
+        } else {
+            cookieName = null;
+        }
+        // workaround for invalid cookie path
+        String cookiePath = cookie.getPath();
+        if (cookiePath != null && !path.startsWith(cookiePath)) {
+            cookie.setPath(path);
+        } else {
+            cookiePath = null;
+        }
+        String hostWithoutDomain = host.substring(0, host.length()
+                - cookie.getDomain().length());
+        int dotIndex = hostWithoutDomain.indexOf('.');
+        if (dotIndex != -1) {
+            // discard additional host name part
+            super.validate(host.substring(dotIndex + 1), port, path, secure, cookie);
+        } else {
+            super.validate(host, port, path, secure, cookie);
+        }
+        if (cookieName != null) {
+            cookie.setName(cookieName);
+        }
+        if (cookiePath != null) {
+            cookie.setPath(cookiePath);
+        }
+    }
+}
diff --git a/src/java/davmail/http/DavMailX509KeyManager.java b/src/java/davmail/http/DavMailX509KeyManager.java
new file mode 100644
index 0000000..7c3bffb
--- /dev/null
+++ b/src/java/davmail/http/DavMailX509KeyManager.java
@@ -0,0 +1,165 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2011  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.http;
+
+import davmail.ui.SelectCertificateDialog;
+import org.apache.log4j.Logger;
+
+import javax.net.ssl.X509KeyManager;
+import java.net.Socket;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Special X509 Key Manager that handles cases where more than one private key
+ * is sufficient to establish the HTTPs connection by asking the user to
+ * select one.
+ */
+public class DavMailX509KeyManager implements X509KeyManager {
+
+    protected static final Logger LOGGER = Logger.getLogger(DavMailX509KeyManager.class);
+
+    // Wrap an existing key manager to handle most of the interface as a pass through
+    private final X509KeyManager keyManager;
+
+    // Remember selected alias so we don't continually bug the user
+    private String cachedAlias;
+
+    /**
+     * Build the specialized key manager wrapping the default one
+     *
+     * @param keyManager original key manager
+     */
+    public DavMailX509KeyManager(X509KeyManager keyManager) {
+        this.keyManager = keyManager;
+    }
+
+    /**
+     * Get the client aliases, simply pass this through to wrapped key manager
+     */
+    public String[] getClientAliases(String string, Principal[] principals) {
+        return keyManager.getClientAliases(string, principals);
+    }
+
+    /**
+     * Select a client alias. Some servers are misconfigured and claim to accept
+     * any client certificate during the SSL handshake, however OWA only authenticates
+     * using a single certificate.
+     * <p/>
+     * This method allows the user to select the right client certificate
+     */
+    public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
+        // Build a list of all aliases
+        ArrayList<String> aliases = new ArrayList<String>();
+        for (String keyTypeValue : keyType) {
+            String[] keyAliases = keyManager.getClientAliases(keyTypeValue, null);
+
+            if (keyAliases != null) {
+                aliases.addAll(Arrays.asList(keyAliases));
+            }
+        }
+
+        // If there are more than one show a dialog and return the selected alias
+        if (aliases.size() > 1) {
+
+            //If there's a saved pattern try to match it
+            if (cachedAlias != null) {
+                for (String alias : aliases) {
+                    if (cachedAlias.equals(stripAlias(alias))) {
+                        LOGGER.debug(alias + " matched cached alias: " + cachedAlias);
+                        return alias;
+                    }
+                }
+
+                // pattern didn't match, clear the pattern and ask user to select an alias
+                cachedAlias = null;
+            }
+
+            String[] aliasesArray = aliases.toArray(new String[aliases.size()]);
+            SelectCertificateDialog selectCertificateDialog = new SelectCertificateDialog(aliasesArray);
+
+            LOGGER.debug("User selected Key Alias: " + selectCertificateDialog.getSelectedAlias());
+
+            cachedAlias = stripAlias(selectCertificateDialog.getSelectedAlias());
+            LOGGER.debug("Stored Key Alias Pattern: " + cachedAlias);
+
+            return selectCertificateDialog.getSelectedAlias();
+
+            // exactly one, simply return that and don't bother the user
+        } else if (aliases.size() == 1) {
+            LOGGER.debug("One Private Key found, returning that");
+            return aliases.get(0);
+
+            // none, return null
+        } else {
+            LOGGER.debug("No Private Keys found");
+            return null;
+        }
+    }
+
+    /**
+     * PKCS11 aliases are in the format: dd.0, dd is incremented
+     * every time the SSL connection is re-negotiated
+     *
+     * @param alias original alias
+     * @return alias without prefix
+     */
+    protected String stripAlias(String alias) {
+        String value = alias;
+        if (value != null && value.length() > 1) {
+            char firstChar = value.charAt(0);
+            int dotIndex = value.indexOf('.'); 
+            if (firstChar >= '0' && firstChar <= '9' && dotIndex >= 0) {
+                value = value.substring(dotIndex+1);
+            }
+        }
+        return value;
+    }
+
+    /**
+     * Passthrough to wrapped keymanager
+     */
+    public String[] getServerAliases(String string, Principal[] prncpls) {
+        return keyManager.getServerAliases(string, prncpls);
+    }
+
+    /**
+     * Passthrough to wrapped keymanager
+     */
+    public String chooseServerAlias(String string, Principal[] prncpls, Socket socket) {
+        return keyManager.chooseServerAlias(string, prncpls, socket);
+    }
+
+    /**
+     * Passthrough to wrapped keymanager
+     */
+    public X509Certificate[] getCertificateChain(String string) {
+        return keyManager.getCertificateChain(string);
+    }
+
+    /**
+     * Passthrough to wrapped keymanager
+     */
+    public PrivateKey getPrivateKey(String string) {
+        return keyManager.getPrivateKey(string);
+    }
+}
diff --git a/src/java/davmail/http/LenientBasicScheme.java b/src/java/davmail/http/LenientBasicScheme.java
new file mode 100644
index 0000000..95bc204
--- /dev/null
+++ b/src/java/davmail/http/LenientBasicScheme.java
@@ -0,0 +1,36 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2011  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.http;
+
+import org.apache.commons.httpclient.auth.BasicScheme;
+import org.apache.commons.httpclient.auth.MalformedChallengeException;
+
+/**
+ * Workaround for broken servers that send invalid Basic authentication challenge.
+ */
+public class LenientBasicScheme extends BasicScheme {
+    public void processChallenge(String challenge)
+            throws MalformedChallengeException {
+        if ("Basic".equalsIgnoreCase(challenge)) {
+            super.processChallenge("Basic \"default\"");
+        } else {
+            super.processChallenge(challenge);
+        }
+    }
+}
diff --git a/src/java/davmail/http/NTLMv2Scheme.java b/src/java/davmail/http/NTLMv2Scheme.java
new file mode 100644
index 0000000..7193396
--- /dev/null
+++ b/src/java/davmail/http/NTLMv2Scheme.java
@@ -0,0 +1,200 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.http;
+
+import jcifs.ntlmssp.NtlmFlags;
+import jcifs.ntlmssp.Type1Message;
+import jcifs.ntlmssp.Type2Message;
+import jcifs.ntlmssp.Type3Message;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.httpclient.Credentials;
+import org.apache.commons.httpclient.HttpMethod;
+import org.apache.commons.httpclient.NTCredentials;
+import org.apache.commons.httpclient.auth.*;
+import org.apache.commons.httpclient.util.EncodingUtil;
+
+import java.io.IOException;
+
+/**
+ * NTLMv2 scheme implementation.
+ */
+public class NTLMv2Scheme implements AuthScheme {
+    private static final int UNINITIATED = 0;
+    private static final int INITIATED = 1;
+    private static final int TYPE1_MSG_GENERATED = 2;
+    private static final int TYPE2_MSG_RECEIVED = 3;
+    private static final int TYPE3_MSG_GENERATED = 4;
+    private static final int FAILED = Integer.MAX_VALUE;
+
+    private Type2Message type2Message;
+    /**
+     * Authentication process state
+     */
+    private int state;
+
+    /**
+     * Processes the NTLM challenge.
+     *
+     * @param challenge the challenge string
+     * @throws MalformedChallengeException is thrown if the authentication challenge
+     *                                     is malformed
+     */
+    public void processChallenge(final String challenge) throws MalformedChallengeException {
+        String authScheme = AuthChallengeParser.extractScheme(challenge);
+        if (!authScheme.equalsIgnoreCase(getSchemeName())) {
+            throw new MalformedChallengeException("Invalid NTLM challenge: " + challenge);
+        }
+        int spaceIndex = challenge.indexOf(' ');
+        if (spaceIndex != -1) {
+            try {
+                type2Message = new Type2Message(Base64.decodeBase64(EncodingUtil.getBytes(
+                        challenge.substring(spaceIndex, challenge.length()).trim(), "ASCII")));
+            } catch (IOException e) {
+                throw new MalformedChallengeException("Invalid NTLM challenge: " + challenge, e);
+            }
+            this.state = TYPE2_MSG_RECEIVED;
+        } else {
+            this.type2Message = null;
+            if (this.state == UNINITIATED) {
+                this.state = INITIATED;
+            } else {
+                this.state = FAILED;
+            }
+        }
+    }
+
+
+    /**
+     * Returns textual designation of the NTLM authentication scheme.
+     *
+     * @return <code>ntlm</code>
+     */
+    public String getSchemeName() {
+        return "ntlm";
+    }
+
+    /**
+     * Not used with NTLM.
+     *
+     * @return null
+     */
+    public String getParameter(String s) {
+        return null;
+    }
+
+    /**
+     * Not used with NTLM.
+     *
+     * @return null
+     */
+    public String getRealm() {
+        return null;
+    }
+
+    /**
+     * Deprecated.
+     */
+    @Deprecated
+    public String getID() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * NTLM is connection based.
+     *
+     * @return true
+     */
+    public boolean isConnectionBased() {
+        return true;
+    }
+
+    /**
+     * Tests if the NTLM authentication process has been completed.
+     *
+     * @return <tt>true</tt> if authorization has been processed
+     */
+    public boolean isComplete() {
+        return state == TYPE3_MSG_GENERATED || state == FAILED;
+    }
+
+    /**
+     * Not implemented.
+     *
+     * @param credentials user credentials
+     * @param method      method name
+     * @param uri         URI
+     * @return an NTLM authorization string
+     * @throws InvalidCredentialsException if authentication credentials
+     *                                     are not valid or not applicable for this authentication scheme
+     * @throws AuthenticationException     if authorization string cannot
+     *                                     be generated due to an authentication failure
+     */
+    @Deprecated
+    public String authenticate(final Credentials credentials, String method, String uri) throws AuthenticationException {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Produces NTLM authorization string for the given set of
+     * {@link Credentials}.
+     *
+     * @param credentials The set of credentials to be used for authentication
+     * @param httpMethod  The method being authenticated
+     * @return an NTLM authorization string
+     * @throws InvalidCredentialsException if authentication credentials
+     *                                     are not valid or not applicable for this authentication scheme
+     * @throws AuthenticationException     if authorization string cannot
+     *                                     be generated due to an authentication failure
+     */
+    public String authenticate(Credentials credentials, HttpMethod httpMethod) throws AuthenticationException {
+        if (this.state == UNINITIATED) {
+            throw new IllegalStateException("NTLM authentication process has not been initiated");
+        }
+
+        NTCredentials ntcredentials;
+        try {
+            ntcredentials = (NTCredentials) credentials;
+        } catch (ClassCastException e) {
+            throw new InvalidCredentialsException(
+                    "Credentials cannot be used for NTLM authentication: "
+                            + credentials.getClass().getName());
+        }
+        String response;
+        if (this.state == INITIATED || this.state == FAILED) {
+            int flags = NtlmFlags.NTLMSSP_NEGOTIATE_NTLM2 | NtlmFlags.NTLMSSP_NEGOTIATE_ALWAYS_SIGN |
+                    NtlmFlags.NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED | NtlmFlags.NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED |
+                    NtlmFlags.NTLMSSP_NEGOTIATE_NTLM | NtlmFlags.NTLMSSP_REQUEST_TARGET |
+                    NtlmFlags.NTLMSSP_NEGOTIATE_OEM | NtlmFlags.NTLMSSP_NEGOTIATE_UNICODE |
+                    NtlmFlags.NTLMSSP_NEGOTIATE_56 | NtlmFlags.NTLMSSP_NEGOTIATE_128;
+            Type1Message type1Message = new Type1Message(flags, ntcredentials.getDomain(), ntcredentials.getHost());
+            response = EncodingUtil.getAsciiString(Base64.encodeBase64(type1Message.toByteArray()));
+            this.state = TYPE1_MSG_GENERATED;
+        } else {
+            int flags = NtlmFlags.NTLMSSP_NEGOTIATE_NTLM2 | NtlmFlags.NTLMSSP_NEGOTIATE_ALWAYS_SIGN |
+                    NtlmFlags.NTLMSSP_NEGOTIATE_NTLM | NtlmFlags.NTLMSSP_NEGOTIATE_UNICODE;
+            Type3Message type3Message = new Type3Message(type2Message, ntcredentials.getPassword(),
+                    ntcredentials.getDomain(), ntcredentials.getUserName(), ntcredentials.getHost(), flags);
+            response = EncodingUtil.getAsciiString(Base64.encodeBase64(type3Message.toByteArray()));
+            this.state = TYPE3_MSG_GENERATED;
+        }
+        return "NTLM " + response;
+    }
+
+
+}
diff --git a/src/java/davmail/http/SunPKCS11ProviderHandler.java b/src/java/davmail/http/SunPKCS11ProviderHandler.java
new file mode 100644
index 0000000..c84d644
--- /dev/null
+++ b/src/java/davmail/http/SunPKCS11ProviderHandler.java
@@ -0,0 +1,45 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.http;
+
+import sun.security.pkcs11.SunPKCS11;
+
+import java.io.ByteArrayInputStream;
+import java.security.Provider;
+import java.security.Security;
+
+/**
+ * Add the SunPKCS11 Provider.
+ */
+public final class SunPKCS11ProviderHandler {
+
+    private SunPKCS11ProviderHandler() {
+    }
+
+    /**
+     * Register PKCS11 provider.
+     *
+     * @param pkcs11Config PKCS11 config string
+     */
+    public static void registerProvider(String pkcs11Config) {
+        Provider p = new SunPKCS11(new ByteArrayInputStream(pkcs11Config.getBytes()));
+        Security.addProvider(p);
+    }
+
+}
diff --git a/src/java/davmail/imap/ImapConnection.java b/src/java/davmail/imap/ImapConnection.java
new file mode 100644
index 0000000..d54ba81
--- /dev/null
+++ b/src/java/davmail/imap/ImapConnection.java
@@ -0,0 +1,1816 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.imap;
+
+import com.sun.mail.imap.protocol.BASE64MailboxDecoder;
+import com.sun.mail.imap.protocol.BASE64MailboxEncoder;
+import davmail.AbstractConnection;
+import davmail.BundleMessage;
+import davmail.DavGateway;
+import davmail.Settings;
+import davmail.exception.DavMailException;
+import davmail.exception.HttpForbiddenException;
+import davmail.exception.HttpNotFoundException;
+import davmail.exception.InsufficientStorageException;
+import davmail.exchange.ExchangeSession;
+import davmail.exchange.ExchangeSessionFactory;
+import davmail.ui.tray.DavGatewayTray;
+import davmail.util.IOUtil;
+import davmail.util.StringUtil;
+import org.apache.commons.httpclient.HttpException;
+import org.apache.log4j.Logger;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.*;
+import javax.mail.util.SharedByteArrayInputStream;
+import java.io.*;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.SocketTimeoutException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * Dav Gateway smtp connection implementation.
+ * Still alpha code : need to find a way to handle message ids
+ */
+public class ImapConnection extends AbstractConnection {
+    private static final Logger LOGGER = Logger.getLogger(ImapConnection.class);
+
+    protected String baseMailboxPath;
+    ExchangeSession.Folder currentFolder;
+
+    /**
+     * Initialize the streams and start the thread.
+     *
+     * @param clientSocket IMAP client socket
+     */
+    public ImapConnection(Socket clientSocket) {
+        super(ImapConnection.class.getSimpleName(), clientSocket, "UTF-8");
+    }
+
+    @Override
+    public void run() {
+        final String capabilities;
+        int imapIdleDelay = Settings.getIntProperty("davmail.imapIdleDelay") * 60;
+        if (imapIdleDelay > 0) {
+            capabilities = "CAPABILITY IMAP4REV1 AUTH=LOGIN IDLE MOVE";
+        } else {
+            capabilities = "CAPABILITY IMAP4REV1 AUTH=LOGIN MOVE";
+        }
+
+        String line;
+        String commandId = null;
+        IMAPTokenizer tokens;
+        try {
+            ExchangeSessionFactory.checkConfig();
+            sendClient("* OK [" + capabilities + "] IMAP4rev1 DavMail " + DavGateway.getCurrentVersion() + " server ready");
+            for (; ; ) {
+                line = readClient();
+                // unable to read line, connection closed ?
+                if (line == null) {
+                    break;
+                }
+
+                tokens = new IMAPTokenizer(line);
+                if (tokens.hasMoreTokens()) {
+                    commandId = tokens.nextToken();
+
+                    checkInfiniteLoop(line);
+
+                    if (tokens.hasMoreTokens()) {
+                        String command = tokens.nextToken();
+
+                        if ("LOGOUT".equalsIgnoreCase(command)) {
+                            sendClient("* BYE Closing connection");
+                            sendClient(commandId + " OK LOGOUT completed");
+                            break;
+                        }
+                        if ("capability".equalsIgnoreCase(command)) {
+                            sendClient("* " + capabilities);
+                            sendClient(commandId + " OK CAPABILITY completed");
+                        } else if ("login".equalsIgnoreCase(command)) {
+                            parseCredentials(tokens);
+                            // detect shared mailbox access
+                            splitUserName();
+                            try {
+                                session = ExchangeSessionFactory.getInstance(userName, password);
+                                sendClient(commandId + " OK Authenticated");
+                                state = State.AUTHENTICATED;
+                            } catch (Exception e) {
+                                DavGatewayTray.error(e);
+                                sendClient(commandId + " NO LOGIN failed");
+                                state = State.INITIAL;
+                            }
+                        } else if ("AUTHENTICATE".equalsIgnoreCase(command)) {
+                            if (tokens.hasMoreTokens()) {
+                                String authenticationMethod = tokens.nextToken();
+                                if ("LOGIN".equalsIgnoreCase(authenticationMethod)) {
+                                    try {
+                                        sendClient("+ " + base64Encode("Username:"));
+                                        state = State.LOGIN;
+                                        userName = base64Decode(readClient());
+                                        // detect shared mailbox access
+                                        splitUserName();
+                                        sendClient("+ " + base64Encode("Password:"));
+                                        state = State.PASSWORD;
+                                        password = base64Decode(readClient());
+                                        session = ExchangeSessionFactory.getInstance(userName, password);
+                                        sendClient(commandId + " OK Authenticated");
+                                        state = State.AUTHENTICATED;
+                                    } catch (Exception e) {
+                                        DavGatewayTray.error(e);
+                                        sendClient(commandId + " NO LOGIN failed");
+                                        state = State.INITIAL;
+                                    }
+                                } else {
+                                    sendClient(commandId + " NO unsupported authentication method");
+                                }
+                            } else {
+                                sendClient(commandId + " BAD authentication method required");
+                            }
+                        } else {
+                            if (state != State.AUTHENTICATED) {
+                                sendClient(commandId + " BAD command authentication required");
+                            } else {
+                                // check for expired session
+                                session = ExchangeSessionFactory.getInstance(session, userName, password);
+                                if ("lsub".equalsIgnoreCase(command) || "list".equalsIgnoreCase(command)) {
+                                    if (tokens.hasMoreTokens()) {
+                                        String folderContext;
+                                        if (baseMailboxPath == null) {
+                                            folderContext = BASE64MailboxDecoder.decode(tokens.nextToken());
+                                        } else {
+                                            folderContext = baseMailboxPath + BASE64MailboxDecoder.decode(tokens.nextToken());
+                                        }
+                                        if (tokens.hasMoreTokens()) {
+                                            String folderQuery = folderContext + BASE64MailboxDecoder.decode(tokens.nextToken());
+                                            if (folderQuery.endsWith("%/%") && !"/%/%".equals(folderQuery)) {
+                                                List<ExchangeSession.Folder> folders = session.getSubFolders(folderQuery.substring(0, folderQuery.length() - 3), false);
+                                                for (ExchangeSession.Folder folder : folders) {
+                                                    sendClient("* " + command + " (" + folder.getFlags() + ") \"/\" \"" + BASE64MailboxEncoder.encode(folder.folderPath) + '\"');
+                                                    sendSubFolders(command, folder.folderPath, false);
+                                                }
+                                                sendClient(commandId + " OK " + command + " completed");
+                                            } else if (folderQuery.endsWith("%") || folderQuery.endsWith("*")) {
+                                                if ("/*".equals(folderQuery) || "/%".equals(folderQuery) || "/%/%".equals(folderQuery)) {
+                                                    folderQuery = folderQuery.substring(1);
+                                                    if ("%/%".equals(folderQuery)) {
+                                                        folderQuery = folderQuery.substring(0, folderQuery.length() - 2);
+                                                    }
+                                                    sendClient("* " + command + " (\\HasChildren) \"/\" \"/public\"");
+                                                }
+                                                if ("*%".equals(folderQuery)) {
+                                                    folderQuery = "*";
+                                                }
+                                                boolean recursive = folderQuery.endsWith("*") && !folderQuery.startsWith("/public");
+                                                sendSubFolders(command, folderQuery.substring(0, folderQuery.length() - 1), recursive);
+                                                sendClient(commandId + " OK " + command + " completed");
+                                            } else {
+                                                ExchangeSession.Folder folder = null;
+                                                try {
+                                                    folder = session.getFolder(folderQuery);
+                                                } catch (HttpForbiddenException e) {
+                                                    // access forbidden, ignore
+                                                    DavGatewayTray.debug(new BundleMessage("LOG_FOLDER_ACCESS_FORBIDDEN", folderQuery));
+                                                } catch (HttpNotFoundException e) {
+                                                    // not found, ignore
+                                                    DavGatewayTray.debug(new BundleMessage("LOG_FOLDER_NOT_FOUND", folderQuery));
+                                                } catch (HttpException e) {
+                                                    // other errors, ignore
+                                                    DavGatewayTray.debug(new BundleMessage("LOG_FOLDER_ACCESS_ERROR", folderQuery, e.getMessage()));
+                                                }
+                                                if (folder != null) {
+                                                    sendClient("* " + command + " (" + folder.getFlags() + ") \"/\" \"" + BASE64MailboxEncoder.encode(folder.folderPath) + '\"');
+                                                    sendClient(commandId + " OK " + command + " completed");
+                                                } else {
+                                                    sendClient(commandId + " NO Folder not found");
+                                                }
+                                            }
+                                        } else {
+                                            sendClient(commandId + " BAD missing folder argument");
+                                        }
+                                    } else {
+                                        sendClient(commandId + " BAD missing folder argument");
+                                    }
+                                } else if ("select".equalsIgnoreCase(command) || "examine".equalsIgnoreCase(command)) {
+                                    if (tokens.hasMoreTokens()) {
+                                        @SuppressWarnings({"NonConstantStringShouldBeStringBuffer"})
+                                        String folderName = BASE64MailboxDecoder.decode(tokens.nextToken());
+                                        if (baseMailboxPath != null && !folderName.startsWith("/")) {
+                                            folderName = baseMailboxPath + folderName;
+                                        }
+                                        try {
+                                            currentFolder = session.getFolder(folderName);
+                                            currentFolder.loadMessages();
+                                            sendClient("* " + currentFolder.count() + " EXISTS");
+                                            sendClient("* " + currentFolder.recent + " RECENT");
+                                            sendClient("* OK [UIDVALIDITY 1]");
+                                            if (currentFolder.count() == 0) {
+                                                sendClient("* OK [UIDNEXT 1]");
+                                            } else {
+                                                sendClient("* OK [UIDNEXT " + currentFolder.getUidNext() + ']');
+                                            }
+                                            sendClient("* FLAGS (\\Answered \\Deleted \\Draft \\Flagged \\Seen $Forwarded Junk)");
+                                            sendClient("* OK [PERMANENTFLAGS (\\Answered \\Deleted \\Draft \\Flagged \\Seen $Forwarded Junk)]");
+                                            if ("select".equalsIgnoreCase(command)) {
+                                                sendClient(commandId + " OK [READ-WRITE] " + command + " completed");
+                                            } else {
+                                                sendClient(commandId + " OK [READ-ONLY] " + command + " completed");
+                                            }
+                                        } catch (HttpNotFoundException e) {
+                                            sendClient(commandId + " NO Not found");
+                                        } catch (HttpForbiddenException e) {
+                                            sendClient(commandId + " NO Forbidden");
+                                        }
+                                    } else {
+                                        sendClient(commandId + " BAD command unrecognized");
+                                    }
+                                } else if ("expunge".equalsIgnoreCase(command)) {
+                                    if (expunge(false)) {
+                                        // need to refresh folder to avoid 404 errors
+                                        session.refreshFolder(currentFolder);
+                                    }
+                                    sendClient(commandId + " OK " + command + " completed");
+                                } else if ("close".equalsIgnoreCase(command)) {
+                                    expunge(true);
+                                    // deselect folder
+                                    currentFolder = null;
+                                    sendClient(commandId + " OK " + command + " completed");
+                                } else if ("create".equalsIgnoreCase(command)) {
+                                    if (tokens.hasMoreTokens()) {
+                                        String folderName = BASE64MailboxDecoder.decode(tokens.nextToken());
+                                        session.createMessageFolder(folderName);
+                                        sendClient(commandId + " OK folder created");
+                                    } else {
+                                        sendClient(commandId + " BAD missing create argument");
+                                    }
+                                } else if ("rename".equalsIgnoreCase(command)) {
+                                    String folderName = BASE64MailboxDecoder.decode(tokens.nextToken());
+                                    String targetName = BASE64MailboxDecoder.decode(tokens.nextToken());
+                                    try {
+                                        session.moveFolder(folderName, targetName);
+                                        sendClient(commandId + " OK rename completed");
+                                    } catch (HttpException e) {
+                                        sendClient(commandId + " NO " + e.getMessage());
+                                    }
+                                } else if ("delete".equalsIgnoreCase(command)) {
+                                    String folderName = BASE64MailboxDecoder.decode(tokens.nextToken());
+                                    try {
+                                        session.deleteFolder(folderName);
+                                        sendClient(commandId + " OK folder deleted");
+                                    } catch (HttpException e) {
+                                        sendClient(commandId + " NO " + e.getMessage());
+                                    }
+                                } else if ("uid".equalsIgnoreCase(command)) {
+                                    if (tokens.hasMoreTokens()) {
+                                        String subcommand = tokens.nextToken();
+                                        if ("fetch".equalsIgnoreCase(subcommand)) {
+                                            if (currentFolder == null) {
+                                                sendClient(commandId + " NO no folder selected");
+                                            } else {
+                                                String ranges = tokens.nextToken();
+                                                if (ranges == null) {
+                                                    sendClient(commandId + " BAD missing range parameter");
+                                                } else {
+                                                    String parameters = null;
+                                                    if (tokens.hasMoreTokens()) {
+                                                        parameters = tokens.nextToken();
+                                                    }
+                                                    UIDRangeIterator uidRangeIterator = new UIDRangeIterator(currentFolder.messages, ranges);
+                                                    while (uidRangeIterator.hasNext()) {
+                                                        DavGatewayTray.switchIcon();
+                                                        ExchangeSession.Message message = uidRangeIterator.next();
+                                                        try {
+                                                            handleFetch(message, uidRangeIterator.currentIndex, parameters);
+                                                        } catch (SocketException e) {
+                                                            // client closed connection
+                                                            throw e;
+                                                        } catch (IOException e) {
+                                                            DavGatewayTray.log(e);
+                                                            sendClient(commandId + " NO Unable to retrieve message: " + e.getMessage());
+                                                        }
+                                                    }
+                                                    sendClient(commandId + " OK UID FETCH completed");
+                                                }
+                                            }
+
+                                        } else if ("search".equalsIgnoreCase(subcommand)) {
+                                            List<Long> uidList = handleSearch(tokens);
+                                            StringBuilder buffer = new StringBuilder("* SEARCH");
+                                            for (long uid : uidList) {
+                                                buffer.append(' ');
+                                                buffer.append(uid);
+                                            }
+                                            sendClient(buffer.toString());
+                                            sendClient(commandId + " OK SEARCH completed");
+
+                                        } else if ("store".equalsIgnoreCase(subcommand)) {
+                                            UIDRangeIterator uidRangeIterator = new UIDRangeIterator(currentFolder.messages, tokens.nextToken());
+                                            String action = tokens.nextToken();
+                                            String flags = tokens.nextToken();
+                                            handleStore(commandId, uidRangeIterator, action, flags);
+                                        } else if ("copy".equalsIgnoreCase(subcommand) || "move".equalsIgnoreCase(subcommand)) {
+                                            try {
+                                                UIDRangeIterator uidRangeIterator = new UIDRangeIterator(currentFolder.messages, tokens.nextToken());
+                                                String targetName = BASE64MailboxDecoder.decode(tokens.nextToken());
+                                                if (!uidRangeIterator.hasNext()) {
+                                                    sendClient(commandId + " NO " + "No message found");
+                                                } else {
+                                                    while (uidRangeIterator.hasNext()) {
+                                                        DavGatewayTray.switchIcon();
+                                                        ExchangeSession.Message message = uidRangeIterator.next();
+                                                        if ("copy".equalsIgnoreCase(subcommand)) {
+                                                            session.copyMessage(message, targetName);
+                                                        } else {
+                                                            session.moveMessage(message, targetName);
+                                                        }
+                                                    }
+                                                    sendClient(commandId + " OK " + subcommand + " completed");
+                                                }
+                                            } catch (HttpException e) {
+                                                sendClient(commandId + " NO " + e.getMessage());
+                                            }
+                                        }
+                                    } else {
+                                        sendClient(commandId + " BAD command unrecognized");
+                                    }
+                                } else if ("search".equalsIgnoreCase(command)) {
+                                    if (currentFolder == null) {
+                                        sendClient(commandId + " NO no folder selected");
+                                    } else {
+                                        List<Long> uidList = handleSearch(tokens);
+                                        if (uidList.isEmpty()) {
+                                            sendClient("* SEARCH");
+                                        } else {
+                                            int currentIndex = 0;
+                                            for (ExchangeSession.Message message : currentFolder.messages) {
+                                                currentIndex++;
+                                                if (uidList.contains(message.getImapUid())) {
+                                                    sendClient("* SEARCH " + currentIndex);
+                                                }
+                                            }
+                                        }
+                                        sendClient(commandId + " OK SEARCH completed");
+                                    }
+                                } else if ("fetch".equalsIgnoreCase(command)) {
+                                    if (currentFolder == null) {
+                                        sendClient(commandId + " NO no folder selected");
+                                    } else {
+                                        RangeIterator rangeIterator = new RangeIterator(currentFolder.messages, tokens.nextToken());
+                                        String parameters = null;
+                                        if (tokens.hasMoreTokens()) {
+                                            parameters = tokens.nextToken();
+                                        }
+                                        while (rangeIterator.hasNext()) {
+                                            DavGatewayTray.switchIcon();
+                                            ExchangeSession.Message message = rangeIterator.next();
+                                            try {
+                                                handleFetch(message, rangeIterator.currentIndex, parameters);
+                                            } catch (SocketException e) {
+                                                // client closed connection, rethrow exception
+                                                throw e;
+                                            } catch (IOException e) {
+                                                DavGatewayTray.log(e);
+                                                sendClient(commandId + " NO Unable to retrieve message: " + e.getMessage());
+                                            }
+
+                                        }
+                                        sendClient(commandId + " OK FETCH completed");
+                                    }
+
+                                } else if ("store".equalsIgnoreCase(command)) {
+                                    RangeIterator rangeIterator = new RangeIterator(currentFolder.messages, tokens.nextToken());
+                                    String action = tokens.nextToken();
+                                    String flags = tokens.nextToken();
+                                    handleStore(commandId, rangeIterator, action, flags);
+
+                                } else if ("copy".equalsIgnoreCase(command) || "move".equalsIgnoreCase(command)) {
+                                    try {
+                                        RangeIterator rangeIterator = new RangeIterator(currentFolder.messages, tokens.nextToken());
+                                        String targetName = BASE64MailboxDecoder.decode(tokens.nextToken());
+                                        if (!rangeIterator.hasNext()) {
+                                            sendClient(commandId + " NO " + "No message found");
+                                        } else {
+                                            while (rangeIterator.hasNext()) {
+                                                DavGatewayTray.switchIcon();
+                                                ExchangeSession.Message message = rangeIterator.next();
+                                                if ("copy".equalsIgnoreCase(command)) {
+                                                    session.copyMessage(message, targetName);
+                                                } else {
+                                                    session.moveMessage(message, targetName);
+                                                }
+                                            }
+                                            sendClient(commandId + " OK " + command + " completed");
+                                        }
+                                    } catch (HttpException e) {
+                                        sendClient(commandId + " NO " + e.getMessage());
+                                    }
+                                } else if ("append".equalsIgnoreCase(command)) {
+                                    String folderName = BASE64MailboxDecoder.decode(tokens.nextToken());
+                                    HashMap<String, String> properties = new HashMap<String, String>();
+                                    String flags = null;
+                                    String date = null;
+                                    // handle optional flags
+                                    String nextToken = tokens.nextQuotedToken();
+                                    if (nextToken.startsWith("(")) {
+                                        flags = removeQuotes(nextToken);
+                                        if (tokens.hasMoreTokens()) {
+                                            nextToken = tokens.nextToken();
+                                            if (tokens.hasMoreTokens()) {
+                                                date = nextToken;
+                                                nextToken = tokens.nextToken();
+                                            }
+                                        }
+                                    } else if (tokens.hasMoreTokens()) {
+                                        date = removeQuotes(nextToken);
+                                        nextToken = tokens.nextToken();
+                                    }
+
+                                    if (flags != null) {
+                                        // parse flags, on create read and draft flags are on the
+                                        // same messageFlags property, 8 means draft and 1 means read
+                                        StringTokenizer flagtokenizer = new StringTokenizer(flags);
+                                        while (flagtokenizer.hasMoreTokens()) {
+                                            String flag = flagtokenizer.nextToken();
+                                            if ("\\Seen".equals(flag)) {
+                                                if (properties.containsKey("draft")) {
+                                                    // draft message, add read flag
+                                                    properties.put("draft", "9");
+                                                } else {
+                                                    // not (yet) draft, set read flag
+                                                    properties.put("draft", "1");
+                                                }
+                                            } else if ("\\Flagged".equals(flag)) {
+                                                properties.put("flagged", "2");
+                                            } else if ("\\Answered".equals(flag)) {
+                                                properties.put("answered", "102");
+                                            } else if ("$Forwarded".equals(flag)) {
+                                                properties.put("forwarded", "104");
+                                            } else if ("\\Draft".equals(flag)) {
+                                                if (properties.containsKey("draft")) {
+                                                    // read message, add draft flag
+                                                    properties.put("draft", "9");
+                                                } else {
+                                                    // not (yet) read, set draft flag
+                                                    properties.put("draft", "8");
+                                                }
+                                            } else if ("Junk".equals(flag)) {
+                                                properties.put("junk", "1");
+                                            }
+                                        }
+                                    } else {
+                                        // no flags, force not draft and unread
+                                        properties.put("draft", "0");
+                                    }
+                                    // handle optional date
+                                    if (date != null) {
+                                        SimpleDateFormat dateParser = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss Z", Locale.ENGLISH);
+                                        Date dateReceived = dateParser.parse(date);
+                                        SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
+                                        dateFormatter.setTimeZone(ExchangeSession.GMT_TIMEZONE);
+
+                                        properties.put("datereceived", dateFormatter.format(dateReceived));
+                                    }
+                                    int size = Integer.parseInt(removeQuotes(nextToken));
+                                    sendClient("+ send literal data");
+                                    byte[] buffer = in.readContent(size);
+                                    // empty line
+                                    readClient();
+                                    MimeMessage mimeMessage = new MimeMessage(null, new SharedByteArrayInputStream(buffer));
+
+                                    String messageName = UUID.randomUUID().toString() + ".EML";
+                                    try {
+                                        session.createMessage(folderName, messageName, properties, mimeMessage);
+                                        sendClient(commandId + " OK APPEND completed");
+                                    } catch (InsufficientStorageException e) {
+                                        sendClient(commandId + " NO " + e.getMessage());
+                                    }
+                                } else if ("idle".equalsIgnoreCase(command) && imapIdleDelay > 0) {
+                                    if (currentFolder != null) {
+                                        sendClient("+ idling ");
+                                        // clear cache before going to idle mode
+                                        currentFolder.clearCache();
+                                        DavGatewayTray.resetIcon();
+                                        try {
+                                            int count = 0;
+                                            while (in.available() == 0) {
+                                                if (++count >= imapIdleDelay) {
+                                                    count = 0;
+                                                    List<Long> previousImapUidList = currentFolder.getImapUidList();
+                                                    if (session.refreshFolder(currentFolder)) {
+                                                        handleRefresh(previousImapUidList, currentFolder.getImapUidList());
+                                                    }
+                                                }
+                                                // sleep 1 second
+                                                Thread.sleep(1000);
+                                            }
+                                            // read DONE line
+                                            line = readClient();
+                                            if ("DONE".equals(line)) {
+                                                sendClient(commandId + " OK " + command + " terminated");
+                                            } else {
+                                                sendClient(commandId + " BAD command unrecognized");
+                                            }
+                                        } catch (IOException e) {
+                                            // client connection closed
+                                            throw new SocketException(e.getMessage());
+                                        }
+                                    } else {
+                                        sendClient(commandId + " NO no folder selected");
+                                    }
+                                } else if ("noop".equalsIgnoreCase(command) || "check".equalsIgnoreCase(command)) {
+                                    if (currentFolder != null) {
+                                        DavGatewayTray.debug(new BundleMessage("LOG_IMAP_COMMAND", command, currentFolder.folderPath));
+                                        List<Long> previousImapUidList = currentFolder.getImapUidList();
+                                        if (session.refreshFolder(currentFolder)) {
+                                            handleRefresh(previousImapUidList, currentFolder.getImapUidList());
+                                        }
+                                    }
+                                    sendClient(commandId + " OK " + command + " completed");
+                                } else if ("subscribe".equalsIgnoreCase(command) || "unsubscribe".equalsIgnoreCase(command)) {
+                                    sendClient(commandId + " OK " + command + " completed");
+                                } else if ("status".equalsIgnoreCase(command)) {
+                                    try {
+                                        String encodedFolderName = tokens.nextToken();
+                                        String folderName = BASE64MailboxDecoder.decode(encodedFolderName);
+                                        ExchangeSession.Folder folder = session.getFolder(folderName);
+                                        // must retrieve messages
+                                        folder.loadMessages();
+                                        String parameters = tokens.nextToken();
+                                        StringBuilder answer = new StringBuilder();
+                                        StringTokenizer parametersTokens = new StringTokenizer(parameters);
+                                        while (parametersTokens.hasMoreTokens()) {
+                                            String token = parametersTokens.nextToken();
+                                            if ("MESSAGES".equalsIgnoreCase(token)) {
+                                                answer.append("MESSAGES ").append(folder.count()).append(' ');
+                                            }
+                                            if ("RECENT".equalsIgnoreCase(token)) {
+                                                answer.append("RECENT ").append(folder.recent).append(' ');
+                                            }
+                                            if ("UIDNEXT".equalsIgnoreCase(token)) {
+                                                if (folder.count() == 0) {
+                                                    answer.append("UIDNEXT 1 ");
+                                                } else {
+                                                    if (folder.count() == 0) {
+                                                        answer.append("UIDNEXT 1 ");
+                                                    } else {
+                                                        answer.append("UIDNEXT ").append(folder.getUidNext()).append(' ');
+                                                    }
+                                                }
+
+                                            }
+                                            if ("UIDVALIDITY".equalsIgnoreCase(token)) {
+                                                answer.append("UIDVALIDITY 1 ");
+                                            }
+                                            if ("UNSEEN".equalsIgnoreCase(token)) {
+                                                answer.append("UNSEEN ").append(folder.unreadCount).append(' ');
+                                            }
+                                        }
+                                        sendClient("* STATUS \"" + encodedFolderName + "\" (" + answer.toString().trim() + ')');
+                                        sendClient(commandId + " OK " + command + " completed");
+                                    } catch (HttpException e) {
+                                        sendClient(commandId + " NO folder not found");
+                                    }
+                                } else {
+                                    sendClient(commandId + " BAD command unrecognized");
+                                }
+                            }
+                        }
+
+                    } else {
+                        sendClient(commandId + " BAD missing command");
+                    }
+                } else {
+                    sendClient("BAD Null command");
+                }
+                DavGatewayTray.resetIcon();
+            }
+
+            os.flush();
+        } catch (SocketTimeoutException e) {
+            DavGatewayTray.debug(new BundleMessage("LOG_CLOSE_CONNECTION_ON_TIMEOUT"));
+            try {
+                sendClient("* BYE Closing connection");
+            } catch (IOException e1) {
+                DavGatewayTray.debug(new BundleMessage("LOG_EXCEPTION_CLOSING_CONNECTION_ON_TIMEOUT"));
+            }
+        } catch (SocketException e) {
+            LOGGER.warn(BundleMessage.formatLog("LOG_CLIENT_CLOSED_CONNECTION"));
+        } catch (Exception e) {
+            DavGatewayTray.log(e);
+            try {
+                String message = ((e.getMessage() == null) ? e.toString() : e.getMessage()).replaceAll("\\n", " ");
+                if (commandId != null) {
+                    sendClient(commandId + " BAD unable to handle request: " + message);
+                } else {
+                    sendClient("* BAD unable to handle request: " + message);
+                }
+            } catch (IOException e2) {
+                DavGatewayTray.warn(new BundleMessage("LOG_EXCEPTION_SENDING_ERROR_TO_CLIENT"), e2);
+            }
+        } finally {
+            close();
+        }
+        DavGatewayTray.resetIcon();
+    }
+
+    protected String lastCommand;
+    protected int lastCommandCount;
+
+    /**
+     * Detect infinite loop on the client side.
+     *
+     * @param line IMAP command line
+     * @throws IOException on infinite loop
+     */
+    protected void checkInfiniteLoop(String line) throws IOException {
+        int spaceIndex = line.indexOf(' ');
+        if (spaceIndex < 0) {
+            // invalid command line, reset
+            lastCommand = null;
+            lastCommandCount = 0;
+        } else {
+            String command = line.substring(spaceIndex + 1);
+            if (command.equals(lastCommand)) {
+                lastCommandCount++;
+                if (lastCommandCount > 100 && !"NOOP".equalsIgnoreCase(lastCommand) && !"IDLE".equalsIgnoreCase(lastCommand)) {
+                    // more than a hundred times the same command => this is a client infinite loop, close connection
+                    throw new IOException("Infinite loop on command " + command + " detected");
+                }
+            } else {
+                // new command, reset
+                lastCommand = command;
+                lastCommandCount = 0;
+            }
+        }
+    }
+
+    /**
+     * Detect shared mailbox access.
+     * see http://msexchangeteam.com/archive/2004/03/31/105275.aspx
+     */
+    protected void splitUserName() {
+        String[] tokens = null;
+        if (userName.indexOf('/') >= 0) {
+            tokens = userName.split("/");
+        } else if (userName.indexOf('\\') >= 0) {
+            tokens = userName.split("\\\\");
+        }
+
+        if (tokens != null && tokens.length == 3) {
+            userName = tokens[0] + '\\' + tokens[1];
+            baseMailboxPath = "/users/" + tokens[2] + '/';
+        }
+    }
+
+    /**
+     * Send expunge untagged response for removed IMAP message uids.
+     *
+     * @param previousImapUidList uid list before refresh
+     * @param imapUidList         uid list after refresh
+     * @throws IOException on error
+     */
+    private void handleRefresh(List<Long> previousImapUidList, List<Long> imapUidList) throws IOException {
+        //
+        int index = 1;
+        for (long previousImapUid : previousImapUidList) {
+            if (!imapUidList.contains(previousImapUid)) {
+                sendClient("* " + index + " EXPUNGE");
+            } else {
+                index++;
+            }
+        }
+        sendClient("* " + currentFolder.count() + " EXISTS");
+        sendClient("* " + currentFolder.recent + " RECENT");
+    }
+
+    private void handleFetch(ExchangeSession.Message message, int currentIndex, String parameters) throws IOException, MessagingException {
+        StringBuilder buffer = new StringBuilder();
+        buffer.append("* ").append(currentIndex).append(" FETCH (UID ").append(message.getImapUid());
+        if (parameters != null) {
+            StringTokenizer paramTokens = new StringTokenizer(parameters);
+            while (paramTokens.hasMoreTokens()) {
+                @SuppressWarnings({"NonConstantStringShouldBeStringBuffer"})
+                String param = paramTokens.nextToken().toUpperCase();
+                if ("FLAGS".equals(param)) {
+                    buffer.append(" FLAGS (").append(message.getImapFlags()).append(')');
+                } else if ("RFC822.SIZE".equals(param)) {
+                    int size;
+                    if (parameters.indexOf("BODY.PEEK[HEADER.FIELDS (") >= 0) {
+                        // Header request, send approximate size
+                        size = message.size;
+                    } else {
+                        size = message.getMimeMessageSize();
+                    }
+                    buffer.append(" RFC822.SIZE ").append(size);
+                } else if ("ENVELOPE".equals(param)) {
+                    appendEnvelope(buffer, message);
+                } else if ("BODYSTRUCTURE".equals(param)) {
+                    appendBodyStructure(buffer, message);
+                } else if ("INTERNALDATE".equals(param) && message.date != null && message.date.length() > 0) {
+                    try {
+                        SimpleDateFormat dateParser = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
+                        dateParser.setTimeZone(ExchangeSession.GMT_TIMEZONE);
+                        Date date = ExchangeSession.getZuluDateFormat().parse(message.date);
+                        SimpleDateFormat dateFormatter = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss Z", Locale.ENGLISH);
+                        buffer.append(" INTERNALDATE \"").append(dateFormatter.format(date)).append('\"');
+                    } catch (ParseException e) {
+                        throw new DavMailException("EXCEPTION_INVALID_DATE", message.date);
+                    }
+                } else if (param.equals("RFC822") || param.startsWith("BODY[") || param.startsWith("BODY.PEEK[") || "RFC822.HEADER".equals(param)) {
+                    // get full param
+                    if (param.indexOf('[') >= 0) {
+                        StringBuilder paramBuffer = new StringBuilder(param);
+                        while (paramTokens.hasMoreTokens() && paramBuffer.indexOf("]") < 0) {
+                            paramBuffer.append(' ').append(paramTokens.nextToken());
+                        }
+                        param = paramBuffer.toString();
+                    }
+                    // parse buffer size
+                    int startIndex = 0;
+                    int maxSize = Integer.MAX_VALUE;
+                    int ltIndex = param.indexOf('<');
+                    if (ltIndex >= 0) {
+                        int dotIndex = param.indexOf('.', ltIndex);
+                        if (dotIndex >= 0) {
+                            startIndex = Integer.parseInt(param.substring(ltIndex + 1, dotIndex));
+                            maxSize = Integer.parseInt(param.substring(dotIndex + 1, param.indexOf('>')));
+                        }
+                    }
+
+                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                    InputStream partInputStream = null;
+                    OutputStream partOutputStream = null;
+
+                    // try to parse message part index
+                    String partIndexString = StringUtil.getToken(param, "[", "]");
+                    if ("".equals(partIndexString) || partIndexString == null) {
+                        // write message with headers
+                        partOutputStream = new PartialOutputStream(baos, startIndex, maxSize);
+                        partInputStream = message.getRawInputStream();
+                    } else if ("TEXT".equals(partIndexString)) {
+                        // write message without headers
+                        partOutputStream = new PartialOutputStream(baos, startIndex, maxSize);
+                        partInputStream = message.getMimeMessage().getRawInputStream();
+                    } else if ("RFC822.HEADER".equals(param) || partIndexString.startsWith("HEADER")) {
+                        // Header requested fetch  headers
+                        String[] requestedHeaders = getRequestedHeaders(partIndexString);
+                        if (requestedHeaders != null) {
+                            // OSX Lion special flags request
+                            if (requestedHeaders.length == 1 && "content-class".equals(requestedHeaders[0]) && message.contentClass != null) {
+                                baos.write("Content-class: ".getBytes("UTF-8"));
+                                baos.write(message.contentClass.getBytes("UTF-8"));
+                                baos.write(13);
+                                baos.write(10);
+                            } else {
+                                Enumeration headerEnumeration = message.getMatchingHeaderLines(requestedHeaders);
+                                while (headerEnumeration.hasMoreElements()) {
+                                    baos.write(((String) headerEnumeration.nextElement()).getBytes("UTF-8"));
+                                    baos.write(13);
+                                    baos.write(10);
+                                }
+                            }
+                        } else {
+                            // write headers only
+                            partOutputStream = new PartOutputStream(baos, true, false, startIndex, maxSize);
+                            partInputStream = message.getRawInputStream();
+                        }
+                    } else {
+                        MimePart bodyPart = message.getMimeMessage();
+                        String[] partIndexStrings = partIndexString.split("\\.");
+                        for (String subPartIndexString : partIndexStrings) {
+                            // ignore MIME subpart index, will return full part
+                            if ("MIME".equals(subPartIndexString)) {
+                                break;
+                            }
+                            int subPartIndex;
+                            // try to parse part index
+                            try {
+                                subPartIndex = Integer.parseInt(subPartIndexString);
+                            } catch (NumberFormatException e) {
+                                throw new DavMailException("EXCEPTION_INVALID_PARAMETER", param);
+                            }
+
+                            Object mimeBody = bodyPart.getContent();
+                            if (mimeBody instanceof MimeMultipart) {
+                                MimeMultipart multiPart = (MimeMultipart) mimeBody;
+                                if (subPartIndex - 1 < multiPart.getCount()) {
+                                    bodyPart = (MimePart) multiPart.getBodyPart(subPartIndex - 1);
+                                } else {
+                                    throw new DavMailException("EXCEPTION_INVALID_PARAMETER", param);
+                                }
+                            } else if (subPartIndex != 1) {
+                                throw new DavMailException("EXCEPTION_INVALID_PARAMETER", param);
+                            }
+                        }
+
+                        // write selected part, without headers
+                        partOutputStream = new PartialOutputStream(baos, startIndex, maxSize);
+                        if (bodyPart instanceof MimeMessage) {
+                            partInputStream = ((MimeMessage) bodyPart).getRawInputStream();
+                        } else {
+                            partInputStream = ((MimeBodyPart) bodyPart).getRawInputStream();
+                        }
+                    }
+
+                    // copy selected content to baos
+                    if (partInputStream != null && partOutputStream != null) {
+                        IOUtil.write(partInputStream, partOutputStream);
+                        partInputStream.close();
+                        partOutputStream.close();
+                    }
+                    baos.close();
+
+                    if ("RFC822.HEADER".equals(param)) {
+                        buffer.append(" RFC822.HEADER ");
+                    } else {
+                        buffer.append(" BODY[").append(partIndexString).append(']');
+                    }
+                    // partial
+                    if (startIndex > 0 || maxSize != Integer.MAX_VALUE) {
+                        buffer.append('<').append(startIndex).append('>');
+                    }
+                    buffer.append(" {").append(baos.size()).append('}');
+                    sendClient(buffer.toString());
+                    os.write(baos.toByteArray());
+                    os.flush();
+                    buffer.setLength(0);
+                }
+            }
+        }
+        buffer.append(')');
+        sendClient(buffer.toString());
+        // do not keep message content in memory
+        message.dropMimeMessage();
+    }
+
+    protected String[] getRequestedHeaders(String partIndexString) {
+        if (partIndexString == null) {
+            return null;
+        } else {
+            int startIndex = partIndexString.indexOf('(');
+            int endIndex = partIndexString.indexOf(')');
+            if (startIndex >= 0 && endIndex >= 0) {
+                return partIndexString.substring(startIndex + 1, endIndex).split(" ");
+            } else {
+                return null;
+            }
+        }
+    }
+
+    protected void handleStore(String commandId, AbstractRangeIterator rangeIterator, String action, String flags) throws IOException {
+        while (rangeIterator.hasNext()) {
+            DavGatewayTray.switchIcon();
+            ExchangeSession.Message message = rangeIterator.next();
+            updateFlags(message, action, flags);
+            sendClient("* " + (rangeIterator.getCurrentIndex()) + " FETCH (UID " + message.getImapUid() + " FLAGS (" + (message.getImapFlags()) + "))");
+        }
+        // auto expunge
+        if (Settings.getBooleanProperty("davmail.imapAutoExpunge")) {
+            if (expunge(false)) {
+                session.refreshFolder(currentFolder);
+            }
+        }
+        sendClient(commandId + " OK STORE completed");
+    }
+
+    protected ExchangeSession.Condition buildConditions(SearchConditions conditions, IMAPTokenizer tokens) throws IOException {
+        ExchangeSession.MultiCondition condition = null;
+        while (tokens.hasMoreTokens()) {
+            String token = tokens.nextQuotedToken().toUpperCase();
+            if (token.startsWith("(") && token.endsWith(")")) {
+                // quoted search param
+                if (condition == null) {
+                    condition = session.and();
+                }
+                condition.add(buildConditions(conditions, new IMAPTokenizer(token.substring(1, token.length() - 1))));
+            } else if ("OR".equals(token)) {
+                condition = session.or();
+            } else if (token.startsWith("OR ")) {
+                condition = appendOrSearchParams(token, conditions);
+            } else if ("CHARSET".equals(token)) {
+                String charset = tokens.nextQuotedToken().toUpperCase();
+                if (!("ASCII".equals(charset) || "UTF-8".equals(charset))) {
+                    throw new IOException("Unsupported charset " + charset);
+                }
+            } else {
+                if (condition == null) {
+                    condition = session.and();
+                }
+                condition.add(appendSearchParam(tokens, token, conditions));
+            }
+        }
+        return condition;
+    }
+
+
+    protected List<Long> handleSearch(IMAPTokenizer tokens) throws IOException {
+        List<Long> uidList = new ArrayList<Long>();
+        SearchConditions conditions = new SearchConditions();
+        ExchangeSession.Condition condition = buildConditions(conditions, tokens);
+        ExchangeSession.MessageList localMessages = currentFolder.searchMessages(condition);
+        Iterator<ExchangeSession.Message> iterator;
+        if (conditions.uidRange != null) {
+            iterator = new UIDRangeIterator(localMessages, conditions.uidRange);
+        } else if (conditions.indexRange != null) {
+            iterator = new RangeIterator(localMessages, conditions.indexRange);
+        } else {
+            iterator = localMessages.iterator();
+        }
+        while (iterator.hasNext()) {
+            ExchangeSession.Message message = iterator.next();
+            if ((conditions.flagged == null || message.flagged == conditions.flagged)
+                    && (conditions.answered == null || message.answered == conditions.answered)
+                    && (conditions.draft == null || message.draft == conditions.draft)) {
+                uidList.add(message.getImapUid());
+            }
+        }
+        return uidList;
+    }
+
+    protected void appendEnvelope(StringBuilder buffer, ExchangeSession.Message message) throws IOException {
+        buffer.append(" ENVELOPE (");
+
+        try {
+            MimeMessage mimeMessage = message.getMimeMessage();
+            // Envelope for date, subject, from, sender, reply-to, to, cc, bcc,in-reply-to, message-id
+            appendEnvelopeHeader(buffer, mimeMessage.getHeader("Date"));
+            appendEnvelopeHeader(buffer, mimeMessage.getHeader("Subject"));
+            appendMailEnvelopeHeader(buffer, mimeMessage.getHeader("From"));
+            appendMailEnvelopeHeader(buffer, mimeMessage.getHeader("Sender"));
+            appendMailEnvelopeHeader(buffer, mimeMessage.getHeader("Reply-To"));
+            appendMailEnvelopeHeader(buffer, mimeMessage.getHeader("To"));
+            appendMailEnvelopeHeader(buffer, mimeMessage.getHeader("CC"));
+            appendMailEnvelopeHeader(buffer, mimeMessage.getHeader("BCC"));
+            appendEnvelopeHeader(buffer, mimeMessage.getHeader("In-Reply-To"));
+            appendEnvelopeHeader(buffer, mimeMessage.getHeader("Message-Id"));
+
+        } catch (MessagingException me) {
+            DavGatewayTray.warn(me);
+            // send fake envelope
+            buffer.append(" NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL");
+        }
+        buffer.append(')');
+    }
+
+    protected void appendEnvelopeHeader(StringBuilder buffer, String[] value) throws UnsupportedEncodingException {
+        if (buffer.charAt(buffer.length() - 1) != '(') {
+            buffer.append(' ');
+        }
+        if (value != null && value.length > 0) {
+            appendEnvelopeHeaderValue(buffer, MimeUtility.unfold(value[0]));
+        } else {
+            buffer.append("NIL");
+        }
+    }
+
+    protected void appendMailEnvelopeHeader(StringBuilder buffer, String[] value) {
+        buffer.append(' ');
+        if (value != null && value.length > 0) {
+            try {
+                String unfoldedValue = MimeUtility.unfold(value[0]);
+                InternetAddress[] addresses = InternetAddress.parseHeader(unfoldedValue, false);
+                if (addresses != null && addresses.length > 1) {
+                    buffer.append('(');
+                    for (InternetAddress address : addresses) {
+                        buffer.append('(');
+                        String personal = address.getPersonal();
+                        if (personal != null) {
+                            appendEnvelopeHeaderValue(buffer, personal);
+                        } else {
+                            buffer.append("NIL");
+                        }
+                        buffer.append(" NIL ");
+                        String mail = address.getAddress();
+                        int atIndex = mail.indexOf('@');
+                        if (atIndex >= 0) {
+                            buffer.append('"').append(mail.substring(0, atIndex)).append('"');
+                            buffer.append(' ');
+                            buffer.append('"').append(mail.substring(atIndex + 1)).append('"');
+                        } else {
+                            buffer.append("NIL NIL");
+                        }
+                        buffer.append(')');
+                    }
+                    buffer.append(')');
+                } else {
+                    buffer.append("NIL");
+                }
+            } catch (AddressException e) {
+                DavGatewayTray.warn(e);
+                buffer.append("NIL");
+            } catch (UnsupportedEncodingException e) {
+                DavGatewayTray.warn(e);
+                buffer.append("NIL");
+            }
+        } else {
+            buffer.append("NIL");
+        }
+    }
+
+    protected void appendEnvelopeHeaderValue(StringBuilder buffer, String value) throws UnsupportedEncodingException {
+        if (value.indexOf('"') >= 0 || value.indexOf('\\') >= 0) {
+            buffer.append('{');
+            buffer.append(value.length());
+            buffer.append("}\r\n");
+            buffer.append(value);
+        } else {
+            buffer.append('"');
+            buffer.append(MimeUtility.encodeText(value, "UTF-8", null));
+            buffer.append('"');
+        }
+
+    }
+
+    protected void appendBodyStructure(StringBuilder buffer, ExchangeSession.Message message) throws IOException {
+
+        buffer.append(" BODYSTRUCTURE ");
+        try {
+            MimeMessage mimeMessage = message.getMimeMessage();
+            Object mimeBody = mimeMessage.getContent();
+            if (mimeBody instanceof MimeMultipart) {
+                appendBodyStructure(buffer, (MimeMultipart) mimeBody);
+            } else {
+                // no multipart, single body
+                appendBodyStructure(buffer, mimeMessage);
+            }
+        } catch (UnsupportedEncodingException e) {
+            DavGatewayTray.warn(e);
+            // failover: send default bodystructure
+            buffer.append("(\"TEXT\" \"PLAIN\" (\"CHARSET\" \"US-ASCII\") NIL NIL NIL NIL NIL)");
+        } catch (MessagingException me) {
+            DavGatewayTray.warn(me);
+            // failover: send default bodystructure
+            buffer.append("(\"TEXT\" \"PLAIN\" (\"CHARSET\" \"US-ASCII\") NIL NIL NIL NIL NIL)");
+        }
+    }
+
+    protected void appendBodyStructure(StringBuilder buffer, MimeMultipart multiPart) throws IOException, MessagingException {
+        buffer.append('(');
+
+        for (int i = 0; i < multiPart.getCount(); i++) {
+            MimeBodyPart bodyPart = (MimeBodyPart) multiPart.getBodyPart(i);
+            Object mimeBody = bodyPart.getContent();
+            if (mimeBody instanceof MimeMultipart) {
+                appendBodyStructure(buffer, (MimeMultipart) mimeBody);
+            } else {
+                // no multipart, single body
+                appendBodyStructure(buffer, bodyPart);
+            }
+        }
+        int slashIndex = multiPart.getContentType().indexOf('/');
+        if (slashIndex < 0) {
+            throw new DavMailException("EXCEPTION_INVALID_CONTENT_TYPE", multiPart.getContentType());
+        }
+        int semiColonIndex = multiPart.getContentType().indexOf(';');
+        if (semiColonIndex < 0) {
+            buffer.append(" \"").append(multiPart.getContentType().substring(slashIndex + 1).toUpperCase()).append("\")");
+        } else {
+            buffer.append(" \"").append(multiPart.getContentType().substring(slashIndex + 1, semiColonIndex).trim().toUpperCase()).append("\")");
+        }
+    }
+
+    protected void appendBodyStructure(StringBuilder buffer, MimePart bodyPart) throws IOException, MessagingException {
+        String contentType = MimeUtility.unfold(bodyPart.getContentType());
+        int slashIndex = contentType.indexOf('/');
+        if (slashIndex < 0) {
+            throw new DavMailException("EXCEPTION_INVALID_CONTENT_TYPE", contentType);
+        }
+        String type = contentType.substring(0, slashIndex).toUpperCase();
+        buffer.append("(\"").append(type).append("\" \"");
+        int semiColonIndex = contentType.indexOf(';');
+        if (semiColonIndex < 0) {
+            buffer.append(contentType.substring(slashIndex + 1).toUpperCase()).append("\" NIL");
+        } else {
+            // extended content type
+            buffer.append(contentType.substring(slashIndex + 1, semiColonIndex).trim().toUpperCase()).append('\"');
+            int charsetindex = contentType.indexOf("charset=");
+            int nameindex = contentType.indexOf("name=");
+            if (charsetindex >= 0 || nameindex >= 0) {
+                buffer.append(" (");
+
+                if (charsetindex >= 0) {
+                    buffer.append("\"CHARSET\" ");
+                    int charsetSemiColonIndex = contentType.indexOf(';', charsetindex);
+                    int charsetEndIndex;
+                    if (charsetSemiColonIndex > 0) {
+                        charsetEndIndex = charsetSemiColonIndex;
+                    } else {
+                        charsetEndIndex = contentType.length();
+                    }
+                    String charSet = contentType.substring(charsetindex + "charset=".length(), charsetEndIndex);
+                    if (!charSet.startsWith("\"")) {
+                        buffer.append('"');
+                    }
+                    buffer.append(charSet.trim().toUpperCase());
+                    if (!charSet.endsWith("\"")) {
+                        buffer.append('"');
+                    }
+                }
+
+                if (nameindex >= 0) {
+                    if (charsetindex >= 0) {
+                        buffer.append(' ');
+                    }
+
+                    buffer.append("\"NAME\" ");
+                    int nameSemiColonIndex = contentType.indexOf(';', nameindex);
+                    int nameEndIndex;
+                    if (nameSemiColonIndex > 0) {
+                        nameEndIndex = nameSemiColonIndex;
+                    } else {
+                        nameEndIndex = contentType.length();
+                    }
+                    String name = contentType.substring(nameindex + "name=".length(), nameEndIndex).trim();
+                    if (!name.startsWith("\"")) {
+                        buffer.append('"');
+                    }
+                    buffer.append(name.trim());
+                    if (!name.endsWith("\"")) {
+                        buffer.append('"');
+                    }
+                }
+                buffer.append(')');
+            } else {
+                buffer.append(" NIL");
+            }
+        }
+        appendBodyStructureValue(buffer, bodyPart.getContentID());
+        appendBodyStructureValue(buffer, bodyPart.getDescription());
+        appendBodyStructureValue(buffer, bodyPart.getEncoding());
+        appendBodyStructureValue(buffer, bodyPart.getSize());
+        if ("MESSAGE".equals(type) || "TEXT".equals(type)) {
+            // line count not implemented in JavaMail, return fake line count
+            appendBodyStructureValue(buffer, bodyPart.getSize() / 80);
+        } else {
+            // do not send line count for non text bodyparts
+            appendBodyStructureValue(buffer, -1);
+        }
+        buffer.append(')');
+    }
+
+    protected void appendBodyStructureValue(StringBuilder buffer, String value) {
+        if (value == null) {
+            buffer.append(" NIL");
+        } else {
+            buffer.append(" \"").append(value.toUpperCase()).append('\"');
+        }
+    }
+
+    protected void appendBodyStructureValue(StringBuilder buffer, int value) {
+        if (value < 0) {
+            buffer.append(" NIL");
+        } else {
+            buffer.append(' ').append(value);
+        }
+    }
+
+    protected void sendSubFolders(String command, String folderPath, boolean recursive) throws IOException {
+        try {
+            List<ExchangeSession.Folder> folders = session.getSubFolders(folderPath, recursive);
+            for (ExchangeSession.Folder folder : folders) {
+                sendClient("* " + command + " (" + folder.getFlags() + ") \"/\" \"" + BASE64MailboxEncoder.encode(folder.folderPath) + '\"');
+            }
+        } catch (HttpForbiddenException e) {
+            // access forbidden, ignore
+            DavGatewayTray.debug(new BundleMessage("LOG_SUBFOLDER_ACCESS_FORBIDDEN", folderPath));
+        } catch (HttpNotFoundException e) {
+            // not found, ignore
+            DavGatewayTray.debug(new BundleMessage("LOG_FOLDER_NOT_FOUND", folderPath));
+        } catch (HttpException e) {
+            // other errors, ignore
+            DavGatewayTray.debug(new BundleMessage("LOG_FOLDER_ACCESS_ERROR", folderPath, e.getMessage()));
+        }
+    }
+
+    /**
+     * client side search conditions
+     */
+    static final class SearchConditions {
+        Boolean flagged;
+        Boolean answered;
+        Boolean draft;
+        String indexRange;
+        String uidRange;
+    }
+
+    protected ExchangeSession.MultiCondition appendOrSearchParams(String token, SearchConditions conditions) throws IOException {
+        ExchangeSession.MultiCondition orCondition = session.or();
+        IMAPTokenizer innerTokens = new IMAPTokenizer(token);
+        innerTokens.nextToken();
+        while (innerTokens.hasMoreTokens()) {
+            String innerToken = innerTokens.nextToken();
+            orCondition.add(appendSearchParam(innerTokens, innerToken, conditions));
+        }
+        return orCondition;
+    }
+
+    protected ExchangeSession.Condition appendSearchParam(StringTokenizer tokens, String token, SearchConditions conditions) throws IOException {
+        if ("NOT".equals(token)) {
+            String nextToken = tokens.nextToken();
+            if ("DELETED".equals(nextToken)) {
+                // conditions.deleted = Boolean.FALSE;
+                return session.isNull("deleted");
+            } else {
+                return session.not(appendSearchParam(tokens, nextToken, conditions));
+            }
+        } else if (token.startsWith("OR ")) {
+            return appendOrSearchParams(token, conditions);
+        } else if ("SUBJECT".equals(token)) {
+            return session.contains("subject", tokens.nextToken());
+        } else if ("BODY".equals(token)) {
+            return session.contains("body", tokens.nextToken());
+        } else if ("TEXT".equals(token)) {
+            String value = tokens.nextToken();
+            return session.or(session.contains("body", value),
+                    session.contains("subject", value),
+                    session.contains("from", value),
+                    session.contains("to", value),
+                    session.contains("cc", value));
+        } else if ("FROM".equals(token)) {
+            return session.contains("from", tokens.nextToken());
+        } else if ("TO".equals(token)) {
+            return session.contains("to", tokens.nextToken());
+        } else if ("CC".equals(token)) {
+            return session.contains("cc", tokens.nextToken());
+        } else if ("LARGER".equals(token)) {
+            return session.gte("messageSize", tokens.nextToken());
+        } else if ("SMALLER".equals(token)) {
+            return session.lt("messageSize", tokens.nextToken());
+        } else if (token.startsWith("SENT") || "SINCE".equals(token) || "BEFORE".equals(token)) {
+            return appendDateSearchParam(tokens, token);
+        } else if ("SEEN".equals(token)) {
+            return session.isTrue("read");
+        } else if ("UNSEEN".equals(token) || "NEW".equals(token)) {
+            return session.isFalse("read");
+        } else if ("DRAFT".equals(token)) {
+            conditions.draft = Boolean.TRUE;
+        } else if ("UNDRAFT".equals(token)) {
+            conditions.draft = Boolean.FALSE;
+        } else if ("DELETED".equals(token)) {
+            // conditions.deleted = Boolean.TRUE;
+            return session.isEqualTo("deleted", "1");
+        } else if ("UNDELETED".equals(token) || "NOT DELETED".equals(token)) {
+            // conditions.deleted = Boolean.FALSE;
+            return session.isNull("deleted");
+        } else if ("FLAGGED".equals(token)) {
+            conditions.flagged = Boolean.TRUE;
+        } else if ("UNFLAGGED".equals(token) || "NEW".equals(token)) {
+            conditions.flagged = Boolean.FALSE;
+        } else if ("ANSWERED".equals(token)) {
+            conditions.answered = Boolean.TRUE;
+        } else if ("UNANSWERED".equals(token)) {
+            conditions.answered = Boolean.FALSE;
+        } else if ("HEADER".equals(token)) {
+            String headerName = tokens.nextToken().toLowerCase();
+            String value = tokens.nextToken();
+            if ("message-id".equals(headerName) && !value.startsWith("<")) {
+                value = '<' + value + '>';
+            }
+            return session.headerIsEqualTo(headerName, value);
+        } else if ("UID".equals(token)) {
+            String range = tokens.nextToken();
+            if ("1:*".equals(range)) {
+                // ignore: this is a noop filter
+            } else {
+                conditions.uidRange = range;
+            }
+        } else if ("OLD".equals(token) || "RECENT".equals(token) || "ALL".equals(token)) {
+            // ignore
+        } else if (token.indexOf(':') >= 0 || token.matches("\\d+")) {
+            // range search
+            conditions.indexRange = token;
+        } else {
+            throw new DavMailException("EXCEPTION_INVALID_SEARCH_PARAMETERS", token);
+        }
+        // client side search token
+        return null;
+    }
+
+    protected ExchangeSession.Condition appendDateSearchParam(StringTokenizer tokens, String token) throws IOException {
+        Date startDate;
+        Date endDate;
+        SimpleDateFormat parser = new SimpleDateFormat("dd-MMM-yyyy", Locale.ENGLISH);
+        parser.setTimeZone(ExchangeSession.GMT_TIMEZONE);
+        String dateToken = tokens.nextToken();
+        try {
+            startDate = parser.parse(dateToken);
+            Calendar calendar = Calendar.getInstance();
+            calendar.setTime(startDate);
+            calendar.add(Calendar.DAY_OF_MONTH, 1);
+            endDate = calendar.getTime();
+        } catch (ParseException e) {
+            throw new DavMailException("EXCEPTION_INVALID_SEARCH_PARAMETERS", dateToken);
+        }
+        String searchAttribute;
+        if (token.startsWith("SENT")) {
+            searchAttribute = "date";
+        } else {
+            searchAttribute = "lastmodified";
+        }
+
+        if (token.endsWith("ON")) {
+            return session.and(session.gt(searchAttribute, session.formatSearchDate(startDate)),
+                    session.lt(searchAttribute, session.formatSearchDate(endDate)));
+        } else if (token.endsWith("BEFORE")) {
+            return session.lt(searchAttribute, session.formatSearchDate(startDate));
+        } else if (token.endsWith("SINCE")) {
+            return session.gte(searchAttribute, session.formatSearchDate(startDate));
+        } else {
+            throw new DavMailException("EXCEPTION_INVALID_SEARCH_PARAMETERS", dateToken);
+        }
+    }
+
+    protected boolean expunge(boolean silent) throws IOException {
+        boolean hasDeleted = false;
+        if (currentFolder.messages != null) {
+            int index = 1;
+            for (ExchangeSession.Message message : currentFolder.messages) {
+                if (message.deleted) {
+                    message.delete();
+                    hasDeleted = true;
+                    if (!silent) {
+                        sendClient("* " + index + " EXPUNGE");
+                    }
+                } else {
+                    index++;
+                }
+            }
+        }
+        return hasDeleted;
+    }
+
+    protected void updateFlags(ExchangeSession.Message message, String action, String flags) throws IOException {
+        HashMap<String, String> properties = new HashMap<String, String>();
+        if ("-Flags".equalsIgnoreCase(action) || "-FLAGS.SILENT".equalsIgnoreCase(action)) {
+            StringTokenizer flagtokenizer = new StringTokenizer(flags);
+            while (flagtokenizer.hasMoreTokens()) {
+                String flag = flagtokenizer.nextToken();
+                if ("\\Seen".equalsIgnoreCase(flag) && message.read) {
+                    properties.put("read", "0");
+                    message.read = false;
+                } else if ("\\Flagged".equalsIgnoreCase(flag) && message.flagged) {
+                    properties.put("flagged", "0");
+                    message.flagged = false;
+                } else if ("\\Deleted".equalsIgnoreCase(flag) && message.deleted) {
+                    properties.put("deleted", null);
+                    message.deleted = false;
+                } else if ("Junk".equalsIgnoreCase(flag) && message.junk) {
+                    properties.put("junk", "0");
+                    message.junk = false;
+                } else if ("$Forwarded".equalsIgnoreCase(flag) && message.forwarded) {
+                    properties.put("forwarded", null);
+                    message.forwarded = false;
+                } else if ("\\Answered".equalsIgnoreCase(flag) && message.answered) {
+                    properties.put("answered", null);
+                    message.answered = false;
+                }
+            }
+        } else if ("+Flags".equalsIgnoreCase(action) || "+FLAGS.SILENT".equalsIgnoreCase(action)) {
+            StringTokenizer flagtokenizer = new StringTokenizer(flags);
+            while (flagtokenizer.hasMoreTokens()) {
+                String flag = flagtokenizer.nextToken();
+                if ("\\Seen".equalsIgnoreCase(flag) && !message.read) {
+                    properties.put("read", "1");
+                    message.read = true;
+                } else if ("\\Deleted".equalsIgnoreCase(flag) && !message.deleted) {
+                    message.deleted = true;
+                    properties.put("deleted", "1");
+                } else if ("\\Flagged".equalsIgnoreCase(flag) && !message.flagged) {
+                    properties.put("flagged", "2");
+                    message.flagged = true;
+                } else if ("\\Answered".equalsIgnoreCase(flag) && !message.answered) {
+                    properties.put("answered", "102");
+                    message.answered = true;
+                } else if ("$Forwarded".equalsIgnoreCase(flag) && !message.forwarded) {
+                    properties.put("forwarded", "104");
+                    message.forwarded = true;
+                } else if ("Junk".equalsIgnoreCase(flag) && !message.junk) {
+                    properties.put("junk", "1");
+                    message.junk = true;
+                }
+            }
+        } else if ("FLAGS".equalsIgnoreCase(action) || "FLAGS.SILENT".equalsIgnoreCase(action)) {
+            // flag list with default values
+            boolean read = false;
+            boolean deleted = false;
+            boolean junk = false;
+            boolean flagged = false;
+            boolean answered = false;
+            boolean forwarded = false;
+            // set flags from new flag list
+            StringTokenizer flagtokenizer = new StringTokenizer(flags);
+            while (flagtokenizer.hasMoreTokens()) {
+                String flag = flagtokenizer.nextToken();
+                if ("\\Seen".equalsIgnoreCase(flag)) {
+                    read = true;
+                } else if ("\\Deleted".equalsIgnoreCase(flag)) {
+                    deleted = true;
+                } else if ("\\Flagged".equalsIgnoreCase(flag)) {
+                    flagged = true;
+                } else if ("\\Answered".equalsIgnoreCase(flag)) {
+                    answered = true;
+                } else if ("$Forwarded".equalsIgnoreCase(flag)) {
+                    forwarded = true;
+                } else if ("Junk".equalsIgnoreCase(flag)) {
+                    junk = true;
+                }
+            }
+            if (read != message.read) {
+                message.read = read;
+                if (message.read) {
+                    properties.put("read", "1");
+                } else {
+                    properties.put("read", "0");
+                }
+            }
+            if (deleted != message.deleted) {
+                message.deleted = deleted;
+                if (message.deleted) {
+                    properties.put("deleted", "1");
+                } else {
+                    properties.put("deleted", null);
+                }
+            }
+            if (flagged != message.flagged) {
+                message.flagged = flagged;
+                if (message.flagged) {
+                    properties.put("flagged", "2");
+                } else {
+                    properties.put("flagged", "0");
+                }
+            }
+            if (answered != message.answered) {
+                message.answered = answered;
+                if (message.answered) {
+                    properties.put("answered", "102");
+                } else if (!forwarded) {
+                    // remove property only if not forwarded
+                    properties.put("answered", null);
+                }
+            }
+            if (forwarded != message.forwarded) {
+                message.forwarded = forwarded;
+                if (message.forwarded) {
+                    properties.put("forwarded", "104");
+                } else if (!answered) {
+                    // remove property only if not answered
+                    properties.put("forwarded", null);
+                }
+            }
+            if (junk != message.junk) {
+                message.junk = junk;
+                if (message.junk) {
+                    properties.put("junk", "1");
+                } else {
+                    properties.put("junk", "0");
+                }
+            }
+        }
+        if (!properties.isEmpty()) {
+            session.updateMessage(message, properties);
+            // message is no longer recent
+            message.recent = false;
+        }
+    }
+
+    /**
+     * Decode IMAP credentials
+     *
+     * @param tokens tokens
+     * @throws IOException on error
+     */
+    protected void parseCredentials(StringTokenizer tokens) throws IOException {
+        if (tokens.hasMoreTokens()) {
+            userName = tokens.nextToken();
+        } else {
+            throw new DavMailException("EXCEPTION_INVALID_CREDENTIALS");
+        }
+
+        if (tokens.hasMoreTokens()) {
+            password = tokens.nextToken();
+        } else {
+            throw new DavMailException("EXCEPTION_INVALID_CREDENTIALS");
+        }
+        int backslashindex = userName.indexOf('\\');
+        if (backslashindex > 0) {
+            userName = userName.substring(0, backslashindex) + userName.substring(backslashindex + 1);
+        }
+    }
+
+    protected String removeQuotes(String value) {
+        String result = value;
+        if (result.startsWith("\"") || result.startsWith("{") || result.startsWith("(")) {
+            result = result.substring(1);
+        }
+        if (result.endsWith("\"") || result.endsWith("}") || result.endsWith(")")) {
+            result = result.substring(0, result.length() - 1);
+        }
+        return result;
+    }
+
+    /**
+     * Filter to output only headers, also count full size
+     */
+    private static final class PartOutputStream extends FilterOutputStream {
+        private static final int START = 0;
+        private static final int CR = 1;
+        private static final int CRLF = 2;
+        private static final int CRLFCR = 3;
+        private static final int BODY = 4;
+
+        private int state = START;
+        private int size;
+        private int bufferSize;
+        private final boolean writeHeaders;
+        private final boolean writeBody;
+        private final int startIndex;
+        private final int maxSize;
+
+        private PartOutputStream(OutputStream os, boolean writeHeaders, boolean writeBody,
+                                 int startIndex, int maxSize) {
+            super(os);
+            this.writeHeaders = writeHeaders;
+            this.writeBody = writeBody;
+            this.startIndex = startIndex;
+            this.maxSize = maxSize;
+        }
+
+        @Override
+        public void write(int b) throws IOException {
+            size++;
+            if (((state != BODY && writeHeaders) || (state == BODY && writeBody)) &&
+                    (size > startIndex) && (bufferSize < maxSize)
+                    ) {
+                super.write(b);
+                bufferSize++;
+            }
+            if (state == START) {
+                if (b == '\r') {
+                    state = CR;
+                }
+            } else if (state == CR) {
+                if (b == '\n') {
+                    state = CRLF;
+                } else {
+                    state = START;
+                }
+            } else if (state == CRLF) {
+                if (b == '\r') {
+                    state = CRLFCR;
+                } else {
+                    state = START;
+                }
+            } else if (state == CRLFCR) {
+                if (b == '\n') {
+                    state = BODY;
+                } else {
+                    state = START;
+                }
+            }
+        }
+    }
+
+    /**
+     * Partial output stream, start at startIndex and write maxSize bytes.
+     */
+    private static final class PartialOutputStream extends FilterOutputStream {
+        private int size;
+        private int bufferSize;
+        private final int startIndex;
+        private final int maxSize;
+
+        private PartialOutputStream(OutputStream os, int startIndex, int maxSize) {
+            super(os);
+            this.startIndex = startIndex;
+            this.maxSize = maxSize;
+        }
+
+        @Override
+        public void write(int b) throws IOException {
+            size++;
+            if ((size > startIndex) && (bufferSize < maxSize)) {
+                super.write(b);
+                bufferSize++;
+            }
+        }
+    }
+
+    protected abstract static class AbstractRangeIterator implements Iterator<ExchangeSession.Message> {
+        ExchangeSession.MessageList messages;
+        int currentIndex;
+
+        protected int getCurrentIndex() {
+            return currentIndex;
+        }
+    }
+
+    protected static class UIDRangeIterator extends AbstractRangeIterator {
+        final String[] ranges;
+        int currentRangeIndex;
+        long startUid;
+        long endUid;
+
+        protected UIDRangeIterator(ExchangeSession.MessageList messages, String value) {
+            this.messages = messages;
+            ranges = value.split(",");
+        }
+
+        protected long convertToLong(String value) {
+            if ("*".equals(value)) {
+                return Long.MAX_VALUE;
+            } else {
+                return Long.parseLong(value);
+            }
+        }
+
+        protected void skipToNextRangeStartUid() {
+            if (currentRangeIndex < ranges.length) {
+                String currentRange = ranges[currentRangeIndex++];
+                int colonIndex = currentRange.indexOf(':');
+                if (colonIndex > 0) {
+                    startUid = convertToLong(currentRange.substring(0, colonIndex));
+                    endUid = convertToLong(currentRange.substring(colonIndex + 1));
+                    if (endUid < startUid) {
+                        long swap = endUid;
+                        endUid = startUid;
+                        startUid = swap;
+                    }
+                } else if ("*".equals(currentRange)) {
+                    startUid = endUid = messages.get(messages.size() - 1).getImapUid();
+                } else {
+                    startUid = endUid = convertToLong(currentRange);
+                }
+                while (currentIndex < messages.size() && messages.get(currentIndex).getImapUid() < startUid) {
+                    currentIndex++;
+                }
+            } else {
+                currentIndex = messages.size();
+            }
+        }
+
+        protected boolean hasNextInRange() {
+            return hasNextIndex() && messages.get(currentIndex).getImapUid() <= endUid;
+        }
+
+        protected boolean hasNextIndex() {
+            return currentIndex < messages.size();
+        }
+
+        protected boolean hasNextRange() {
+            return currentRangeIndex < ranges.length;
+        }
+
+        public boolean hasNext() {
+            boolean hasNextInRange = hasNextInRange();
+            // if has next range and current index after current range end, reset index
+            if (hasNextRange() && !hasNextInRange) {
+                currentIndex = 0;
+            }
+            while (hasNextIndex() && !hasNextInRange) {
+                skipToNextRangeStartUid();
+                hasNextInRange = hasNextInRange();
+            }
+            return hasNextIndex();
+        }
+
+        public ExchangeSession.Message next() {
+            ExchangeSession.Message message = messages.get(currentIndex++);
+            long uid = message.getImapUid();
+            if (uid < startUid || uid > endUid) {
+                throw new RuntimeException("Message uid " + uid + " not in range " + startUid + ':' + endUid);
+            }
+            return message;
+        }
+
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    protected static class RangeIterator extends AbstractRangeIterator {
+        final String[] ranges;
+        int currentRangeIndex;
+        long startUid;
+        long endUid;
+
+        protected RangeIterator(ExchangeSession.MessageList messages, String value) {
+            this.messages = messages;
+            ranges = value.split(",");
+        }
+
+        protected long convertToLong(String value) {
+            if ("*".equals(value)) {
+                return Long.MAX_VALUE;
+            } else {
+                return Long.parseLong(value);
+            }
+        }
+
+        protected void skipToNextRangeStart() {
+            if (currentRangeIndex < ranges.length) {
+                String currentRange = ranges[currentRangeIndex++];
+                int colonIndex = currentRange.indexOf(':');
+                if (colonIndex > 0) {
+                    startUid = convertToLong(currentRange.substring(0, colonIndex));
+                    endUid = convertToLong(currentRange.substring(colonIndex + 1));
+                    if (endUid < startUid) {
+                        long swap = endUid;
+                        endUid = startUid;
+                        startUid = swap;
+                    }
+                } else if ("*".equals(currentRange)) {
+                    startUid = endUid = messages.size();
+                } else {
+                    startUid = endUid = convertToLong(currentRange);
+                }
+                while (currentIndex < messages.size() && (currentIndex + 1) < startUid) {
+                    currentIndex++;
+                }
+            } else {
+                currentIndex = messages.size();
+            }
+        }
+
+        protected boolean hasNextInRange() {
+            return hasNextIndex() && currentIndex < endUid;
+        }
+
+        protected boolean hasNextIndex() {
+            return currentIndex < messages.size();
+        }
+
+        protected boolean hasNextRange() {
+            return currentRangeIndex < ranges.length;
+        }
+
+        public boolean hasNext() {
+            boolean hasNextInRange = hasNextInRange();
+            // if has next range and current index after current range end, reset index
+            if (hasNextRange() && !hasNextInRange) {
+                currentIndex = 0;
+            }
+            while (hasNextIndex() && !hasNextInRange) {
+                skipToNextRangeStart();
+                hasNextInRange = hasNextInRange();
+            }
+            return hasNextIndex();
+        }
+
+        public ExchangeSession.Message next() {
+            return messages.get(currentIndex++);
+        }
+
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    class IMAPTokenizer extends StringTokenizer {
+        IMAPTokenizer(String value) {
+            super(value);
+        }
+
+        @Override
+        public String nextToken() {
+            return removeQuotes(nextQuotedToken());
+        }
+
+        public String nextQuotedToken() {
+            StringBuilder nextToken = new StringBuilder();
+            nextToken.append(super.nextToken());
+            while (hasMoreTokens() && nextToken.length() > 0 && nextToken.charAt(0) == '"'
+                    && (nextToken.charAt(nextToken.length() - 1) != '"' || nextToken.length() == 1)) {
+                nextToken.append(' ').append(super.nextToken());
+            }
+            while (hasMoreTokens() && nextToken.length() > 0 && nextToken.charAt(0) == '('
+                    && nextToken.charAt(nextToken.length() - 1) != ')') {
+                nextToken.append(' ').append(super.nextToken());
+            }
+            while (hasMoreTokens() && nextToken.length() > 0 && nextToken.indexOf("[") != -1
+                    && nextToken.charAt(nextToken.length() - 1) != ']') {
+                nextToken.append(' ').append(super.nextToken());
+            }
+            return nextToken.toString();
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/java/davmail/imap/ImapServer.java b/src/java/davmail/imap/ImapServer.java
new file mode 100644
index 0000000..06e8616
--- /dev/null
+++ b/src/java/davmail/imap/ImapServer.java
@@ -0,0 +1,58 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.imap;
+
+
+import davmail.AbstractConnection;
+import davmail.AbstractServer;
+import davmail.Settings;
+
+import java.net.Socket;
+
+/**
+ * Pop3 server
+ */
+public class ImapServer extends AbstractServer {
+    /**
+     * Default IMAP port
+     */
+    public static final int DEFAULT_PORT = 143;
+
+    /**
+     * Create a ServerSocket to listen for connections.
+     * Start the thread.
+     *
+     * @param port imap listen port, 143 if not defined (0)
+     */
+    public ImapServer(int port) {
+        super(ImapServer.class.getName(), port, ImapServer.DEFAULT_PORT);
+        nosslFlag = Settings.getBooleanProperty("davmail.ssl.nosecureimap");
+    }
+
+    @Override
+    public String getProtocolName() {
+        return "IMAP";
+    }
+
+    @Override
+    public AbstractConnection createConnectionHandler(Socket clientSocket) {
+        return new ImapConnection(clientSocket);
+    }
+
+}
diff --git a/src/java/davmail/ldap/LdapConnection.java b/src/java/davmail/ldap/LdapConnection.java
new file mode 100644
index 0000000..960d77d
--- /dev/null
+++ b/src/java/davmail/ldap/LdapConnection.java
@@ -0,0 +1,1687 @@
+/*
+* DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.ldap;
+
+import com.sun.jndi.ldap.Ber;
+import com.sun.jndi.ldap.BerDecoder;
+import com.sun.jndi.ldap.BerEncoder;
+import davmail.AbstractConnection;
+import davmail.BundleMessage;
+import davmail.Settings;
+import davmail.exception.DavMailException;
+import davmail.exchange.ExchangeSession;
+import davmail.exchange.ExchangeSessionFactory;
+import davmail.exchange.dav.DavExchangeSession;
+import davmail.ui.tray.DavGatewayTray;
+import org.apache.log4j.Logger;
+
+import javax.security.auth.callback.*;
+import javax.security.sasl.AuthorizeCallback;
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslServer;
+import java.io.*;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.*;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * Handle a caldav connection.
+ */
+public class LdapConnection extends AbstractConnection {
+    private static final Logger LOGGER = Logger.getLogger(LdapConnection.class);
+    /**
+     * Davmail base context
+     */
+    static final String BASE_CONTEXT = "ou=people";
+    /**
+     * OSX server (OpenDirectory) base context
+     */
+    static final String OD_BASE_CONTEXT = "o=od";
+    static final String OD_USER_CONTEXT = "cn=users, o=od";
+    static final String OD_CONFIG_CONTEXT = "cn=config, o=od";
+    static final String COMPUTER_CONTEXT = "cn=computers, o=od";
+    static final String OD_GROUP_CONTEXT = "cn=groups, o=od";
+
+    // TODO: adjust Directory Utility settings
+    static final String COMPUTER_CONTEXT_LION = "cn=computers,o=od";
+    static final String OD_USER_CONTEXT_LION = "cn=users, ou=people";
+
+    /**
+     * Root DSE naming contexts (default and OpenDirectory)
+     */
+    static final List<String> NAMING_CONTEXTS = new ArrayList<String>();
+
+    static {
+        NAMING_CONTEXTS.add(BASE_CONTEXT);
+        NAMING_CONTEXTS.add(OD_BASE_CONTEXT);
+    }
+
+    static final List<String> PERSON_OBJECT_CLASSES = new ArrayList<String>();
+
+    static {
+        PERSON_OBJECT_CLASSES.add("top");
+        PERSON_OBJECT_CLASSES.add("person");
+        PERSON_OBJECT_CLASSES.add("organizationalPerson");
+        PERSON_OBJECT_CLASSES.add("inetOrgPerson");
+        // OpenDirectory class for iCal
+        PERSON_OBJECT_CLASSES.add("apple-user");
+    }
+
+    /**
+     * Map Exchange contact attribute names to LDAP attributes.
+     * Used only when returningAttributes is empty in LDAP request (return all available attributes)
+     */
+    static final HashMap<String, String> CONTACT_TO_LDAP_ATTRIBUTE_MAP = new HashMap<String, String>();
+
+    static {
+        CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("imapUid", "uid");
+        CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("co", "countryname");
+        CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("extensionattribute1", "custom1");
+        CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("extensionattribute2", "custom2");
+        CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("extensionattribute3", "custom3");
+        CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("extensionattribute4", "custom4");
+        CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("smtpemail1", "mail");
+        CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("smtpemail2", "xmozillasecondemail");
+        CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("homeCountry", "mozillahomecountryname");
+        CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("homeCity", "mozillahomelocalityname");
+        CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("homePostalCode", "mozillahomepostalcode");
+        CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("homeState", "mozillahomestate");
+        CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("homeStreet", "mozillahomestreet");
+        CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("businesshomepage", "mozillaworkurl");
+        CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("nickname", "mozillanickname");
+    }
+
+    /**
+     * OSX constant computer guid (used by iCal attendee completion)
+     */
+    static final String COMPUTER_GUID = "52486C30-F0AB-48E3-9C37-37E9B28CDD7B";
+    /**
+     * OSX constant virtual host guid (used by iCal attendee completion)
+     */
+    static final String VIRTUALHOST_GUID = "D6DD8A10-1098-11DE-8C30-0800200C9A66";
+
+    /**
+     * OSX constant value for attribute apple-serviceslocator
+     */
+    static final HashMap<String, String> STATIC_ATTRIBUTE_MAP = new HashMap<String, String>();
+
+    static {
+        STATIC_ATTRIBUTE_MAP.put("apple-serviceslocator", COMPUTER_GUID + ':' + VIRTUALHOST_GUID + ":calendar");
+    }
+
+    /**
+     * LDAP to Exchange Criteria Map
+     */
+    // TODO: remove
+    static final HashMap<String, String> CRITERIA_MAP = new HashMap<String, String>();
+
+    static {
+        // assume mail starts with firstname
+        CRITERIA_MAP.put("uid", "AN");
+        CRITERIA_MAP.put("mail", "FN");
+        CRITERIA_MAP.put("displayname", "DN");
+        CRITERIA_MAP.put("cn", "DN");
+        CRITERIA_MAP.put("givenname", "FN");
+        CRITERIA_MAP.put("sn", "LN");
+        CRITERIA_MAP.put("title", "TL");
+        CRITERIA_MAP.put("company", "CP");
+        CRITERIA_MAP.put("o", "CP");
+        CRITERIA_MAP.put("l", "OF");
+        CRITERIA_MAP.put("department", "DP");
+        CRITERIA_MAP.put("apple-group-realname", "DP");
+    }
+
+    /**
+     * LDAP to Exchange contact attribute map.
+     */
+    static final HashMap<String, String> LDAP_TO_CONTACT_ATTRIBUTE_MAP = new HashMap<String, String>();
+
+    static {
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("uid", "imapUid");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mail", "smtpemail1");
+
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("displayname", "cn");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("commonname", "cn");
+
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("givenname", "givenName");
+
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("surname", "sn");
+
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("company", "o");
+
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("apple-group-realname", "department");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mozillahomelocalityname", "homeCity");
+
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("c", "co");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("countryname", "co");
+
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("custom1", "extensionattribute1");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("custom2", "extensionattribute2");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("custom3", "extensionattribute3");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("custom4", "extensionattribute4");
+
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mozillacustom1", "extensionattribute1");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mozillacustom2", "extensionattribute2");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mozillacustom3", "extensionattribute3");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mozillacustom4", "extensionattribute4");
+
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("telephonenumber", "telephoneNumber");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("orgunit", "department");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("departmentnumber", "department");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("ou", "department");
+
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mozillaworkstreet2", null);
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mozillahomestreet", "homeStreet");
+
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("xmozillanickname", "nickname");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mozillanickname", "nickname");
+
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("cellphone", "mobile");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("homeurl", "personalHomePage");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mozillahomeurl", "personalHomePage");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("apple-user-homeurl", "personalHomePage");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mozillahomepostalcode", "homePostalCode");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("fax", "facsimiletelephonenumber");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mozillahomecountryname", "homeCountry");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("streetaddress", "street");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mozillaworkurl", "businesshomepage");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("workurl", "businesshomepage");
+
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("region", "st");
+
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("birthmonth", "bday");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("birthday", "bday");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("birthyear", "bday");
+
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("carphone", "othermobile");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("nsaimid", "im");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("nscpaimscreenname", "im");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("apple-imhandle", "im");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("imhandle", "im");
+
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("xmozillasecondemail", "smtpemail2");
+
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("notes", "description");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("pagerphone", "pager");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("pager", "pager");
+
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("locality", "l");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("homephone", "homePhone");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mozillasecondemail", "smtpemail2");
+
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("zip", "postalcode");
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mozillahomestate", "homeState");
+
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("modifytimestamp", "lastmodified");
+
+        // ignore attribute
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("objectclass", null);
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mozillausehtmlmail", null);
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("xmozillausehtmlmail", null);
+
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mozillahomestreet2", null);
+
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("labeleduri", null);
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("apple-generateduid", null);
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("uidnumber", null);
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("gidnumber", null);
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("jpegphoto", null);
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("apple-emailcontacts", null);
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("apple-user-picture", null);
+
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("_writers_usercertificate", null);
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("_writers_realname", null);
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("_writers_jpegphoto", null);
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("_guest", null);
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("_writers_linkedidentity", null);
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("_defaultlanguage", null);
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("_writers_hint", null);
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("_writers__defaultlanguage", null);
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("_writers_picture", null);
+
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("apple-user-authenticationhint", null);
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("external", null);
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("userpassword", null);
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("linkedidentity", null);
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("homedirectory", null);
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("authauthority", null);
+
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("applefloor", null);
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("buildingname", null);
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("destinationindicator", null);
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("postaladdress", null);
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("homepostaladdress", null);
+
+        // iCal search attribute
+        LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("apple-serviceslocator", "apple-serviceslocator");
+    }
+
+    /**
+     * LDAP filter attributes ignore map
+     */
+    // TODO remove
+    static final HashSet<String> IGNORE_MAP = new HashSet<String>();
+
+    static {
+        IGNORE_MAP.add("objectclass");
+        IGNORE_MAP.add("apple-generateduid");
+        IGNORE_MAP.add("augmentconfiguration");
+        IGNORE_MAP.add("ou");
+        IGNORE_MAP.add("apple-realname");
+        IGNORE_MAP.add("apple-group-nestedgroup");
+        IGNORE_MAP.add("apple-group-memberguid");
+        IGNORE_MAP.add("macaddress");
+        IGNORE_MAP.add("memberuid");
+    }
+
+    // LDAP version
+    // static final int LDAP_VERSION2 = 0x02;
+    static final int LDAP_VERSION3 = 0x03;
+
+    // LDAP request operations
+    static final int LDAP_REQ_BIND = 0x60;
+    static final int LDAP_REQ_SEARCH = 0x63;
+    static final int LDAP_REQ_UNBIND = 0x42;
+    static final int LDAP_REQ_ABANDON = 0x50;
+
+    // LDAP response operations
+    static final int LDAP_REP_BIND = 0x61;
+    static final int LDAP_REP_SEARCH = 0x64;
+    static final int LDAP_REP_RESULT = 0x65;
+
+    static final int LDAP_SASL_BIND_IN_PROGRESS = 0x0E;
+
+    // LDAP return codes
+    static final int LDAP_OTHER = 80;
+    static final int LDAP_SUCCESS = 0;
+    static final int LDAP_SIZE_LIMIT_EXCEEDED = 4;
+    static final int LDAP_INVALID_CREDENTIALS = 49;
+
+    // LDAP filter code
+    static final int LDAP_FILTER_AND = 0xa0;
+    static final int LDAP_FILTER_OR = 0xa1;
+    static final int LDAP_FILTER_NOT = 0xa2;
+
+    // LDAP filter operators
+    static final int LDAP_FILTER_SUBSTRINGS = 0xa4;
+    //static final int LDAP_FILTER_GE = 0xa5;
+    //static final int LDAP_FILTER_LE = 0xa6;
+    static final int LDAP_FILTER_PRESENT = 0x87;
+    //static final int LDAP_FILTER_APPROX = 0xa8;
+    static final int LDAP_FILTER_EQUALITY = 0xa3;
+
+    // LDAP filter mode
+    static final int LDAP_SUBSTRING_INITIAL = 0x80;
+    static final int LDAP_SUBSTRING_ANY = 0x81;
+    static final int LDAP_SUBSTRING_FINAL = 0x82;
+
+    // BER data types
+    static final int LBER_ENUMERATED = 0x0a;
+    static final int LBER_SET = 0x31;
+    static final int LBER_SEQUENCE = 0x30;
+
+    // LDAP search scope
+    static final int SCOPE_BASE_OBJECT = 0;
+    //static final int SCOPE_ONE_LEVEL = 1;
+    //static final int SCOPE_SUBTREE = 2;
+
+    /**
+     * For some unknown reason parseIntWithTag is private !
+     */
+    static final Method PARSE_INT_WITH_TAG_METHOD;
+
+    static {
+        try {
+            PARSE_INT_WITH_TAG_METHOD = BerDecoder.class.getDeclaredMethod("parseIntWithTag", int.class);
+            PARSE_INT_WITH_TAG_METHOD.setAccessible(true);
+        } catch (NoSuchMethodException e) {
+            DavGatewayTray.error(new BundleMessage("LOG_UNABLE_TO_GET_PARSEINTWITHTAG"));
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Sasl server for DIGEST-MD5 authentication
+     */
+    protected SaslServer saslServer;
+
+    /**
+     * raw connection inputStream
+     */
+    protected BufferedInputStream is;
+
+    /**
+     * reusable BER encoder
+     */
+    protected final BerEncoder responseBer = new BerEncoder();
+
+    /**
+     * Current LDAP version (used for String encoding)
+     */
+    int ldapVersion = LDAP_VERSION3;
+
+    /**
+     * Search threads map
+     */
+    protected final HashMap<Integer, SearchRunnable> searchThreadMap = new HashMap<Integer, SearchRunnable>();
+
+    /**
+     * Initialize the streams and start the thread.
+     *
+     * @param clientSocket LDAP client socket
+     */
+    public LdapConnection(Socket clientSocket) {
+        super(LdapConnection.class.getSimpleName(), clientSocket);
+        try {
+            is = new BufferedInputStream(client.getInputStream());
+            os = new BufferedOutputStream(client.getOutputStream());
+        } catch (IOException e) {
+            close();
+            DavGatewayTray.error(new BundleMessage("LOG_EXCEPTION_GETTING_SOCKET_STREAMS"), e);
+        }
+    }
+
+    protected boolean isLdapV3() {
+        return ldapVersion == LDAP_VERSION3;
+    }
+
+    @Override
+    public void run() {
+        byte[] inbuf = new byte[2048];   // Buffer for reading incoming bytes
+        int bytesread;  // Number of bytes in inbuf
+        int bytesleft;  // Number of bytes that need to read for completing resp
+        int br;         // Temp; number of bytes read from stream
+        int offset;     // Offset of where to store bytes in inbuf
+        boolean eos;    // End of stream
+
+        try {
+            ExchangeSessionFactory.checkConfig();
+            while (true) {
+                offset = 0;
+
+                // check that it is the beginning of a sequence
+                bytesread = is.read(inbuf, offset, 1);
+                if (bytesread < 0) {
+                    break; // EOF
+                }
+
+                if (inbuf[offset++] != (Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR)) {
+                    continue;
+                }
+
+                // get length of sequence
+                bytesread = is.read(inbuf, offset, 1);
+                if (bytesread < 0) {
+                    break; // EOF
+                }
+                int seqlen = inbuf[offset++]; // Length of ASN sequence
+
+                // if high bit is on, length is encoded in the
+                // subsequent length bytes and the number of length bytes
+                // is equal to & 0x80 (i.e. length byte with high bit off).
+                if ((seqlen & 0x80) == 0x80) {
+                    int seqlenlen = seqlen & 0x7f;  // number of length bytes
+
+                    bytesread = 0;
+                    eos = false;
+
+                    // Read all length bytes
+                    while (bytesread < seqlenlen) {
+                        br = is.read(inbuf, offset + bytesread,
+                                seqlenlen - bytesread);
+                        if (br < 0) {
+                            eos = true;
+                            break; // EOF
+                        }
+                        bytesread += br;
+                    }
+
+                    // end-of-stream reached before length bytes are read
+                    if (eos) {
+                        break;  // EOF
+                    }
+
+                    // Add contents of length bytes to determine length
+                    seqlen = 0;
+                    for (int i = 0; i < seqlenlen; i++) {
+                        seqlen = (seqlen << 8) + (inbuf[offset + i] & 0xff);
+                    }
+                    offset += bytesread;
+                }
+
+                // read in seqlen bytes
+                bytesleft = seqlen;
+                if ((offset + bytesleft) > inbuf.length) {
+                    byte[] nbuf = new byte[offset + bytesleft];
+                    System.arraycopy(inbuf, 0, nbuf, 0, offset);
+                    inbuf = nbuf;
+                }
+                while (bytesleft > 0) {
+                    bytesread = is.read(inbuf, offset, bytesleft);
+                    if (bytesread < 0) {
+                        break; // EOF
+                    }
+                    offset += bytesread;
+                    bytesleft -= bytesread;
+                }
+
+                DavGatewayTray.switchIcon();
+
+                handleRequest(inbuf, offset);
+            }
+
+        } catch (SocketException e) {
+            DavGatewayTray.debug(new BundleMessage("LOG_CONNECTION_CLOSED"));
+        } catch (SocketTimeoutException e) {
+            DavGatewayTray.debug(new BundleMessage("LOG_CLOSE_CONNECTION_ON_TIMEOUT"));
+        } catch (Exception e) {
+            DavGatewayTray.log(e);
+            try {
+                sendErr(0, LDAP_REP_BIND, e);
+            } catch (IOException e2) {
+                DavGatewayTray.warn(new BundleMessage("LOG_EXCEPTION_SENDING_ERROR_TO_CLIENT"), e2);
+            }
+        } finally {
+            // cancel all search threads
+            synchronized (searchThreadMap) {
+                for (SearchRunnable searchRunnable : searchThreadMap.values()) {
+                    searchRunnable.abandon();
+                }
+            }
+            close();
+        }
+        DavGatewayTray.resetIcon();
+    }
+
+    protected static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+
+    protected void handleRequest(byte[] inbuf, int offset) throws IOException {
+        //dumpBer(inbuf, offset);
+        BerDecoder reqBer = new BerDecoder(inbuf, 0, offset);
+        int currentMessageId = 0;
+        try {
+            reqBer.parseSeq(null);
+            currentMessageId = reqBer.parseInt();
+            int requestOperation = reqBer.peekByte();
+
+            if (requestOperation == LDAP_REQ_BIND) {
+                reqBer.parseSeq(null);
+                ldapVersion = reqBer.parseInt();
+                userName = reqBer.parseString(isLdapV3());
+                if (reqBer.peekByte() == (Ber.ASN_CONTEXT | Ber.ASN_CONSTRUCTOR | 3)) {
+                    // SASL authentication
+                    reqBer.parseSeq(null);
+                    // Get mechanism, usually DIGEST-MD5
+                    String mechanism = reqBer.parseString(isLdapV3());
+
+                    byte[] serverResponse;
+                    CallbackHandler callbackHandler = new CallbackHandler() {
+                        public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+                            // look for username in callbacks
+                            for (Callback callback : callbacks) {
+                                if (callback instanceof NameCallback) {
+                                    userName = ((NameCallback) callback).getDefaultName();
+                                    // get password from session pool
+                                    password = ExchangeSessionFactory.getUserPassword(userName);
+                                }
+                            }
+                            // handle other callbacks
+                            for (Callback callback : callbacks) {
+                                if (callback instanceof AuthorizeCallback) {
+                                    ((AuthorizeCallback) callback).setAuthorized(true);
+                                } else if (callback instanceof PasswordCallback) {
+                                    if (password != null) {
+                                        ((PasswordCallback) callback).setPassword(password.toCharArray());
+                                    }
+                                }
+                            }
+                        }
+                    };
+                    int status;
+                    if (reqBer.bytesLeft() > 0 && saslServer != null) {
+                        byte[] clientResponse = reqBer.parseOctetString(Ber.ASN_OCTET_STR, null);
+                        serverResponse = saslServer.evaluateResponse(clientResponse);
+                        status = LDAP_SUCCESS;
+
+                        DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_BIND_USER", currentMessageId, userName));
+                        try {
+                            session = ExchangeSessionFactory.getInstance(userName, password);
+                            DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_BIND_SUCCESS"));
+                        } catch (IOException e) {
+                            serverResponse = EMPTY_BYTE_ARRAY;
+                            status = LDAP_INVALID_CREDENTIALS;
+                            DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_BIND_INVALID_CREDENTIALS"));
+                        }
+
+                    } else {
+                        Map<String, String> properties = new HashMap<String, String>();
+                        properties.put("javax.security.sasl.qop", "auth,auth-int");
+                        saslServer = Sasl.createSaslServer(mechanism, "ldap", client.getLocalAddress().getHostAddress(), properties, callbackHandler);
+                        serverResponse = saslServer.evaluateResponse(EMPTY_BYTE_ARRAY);
+                        status = LDAP_SASL_BIND_IN_PROGRESS;
+                    }
+
+                    responseBer.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
+                    responseBer.encodeInt(currentMessageId);
+                    responseBer.beginSeq(LDAP_REP_BIND);
+                    responseBer.encodeInt(status, LBER_ENUMERATED);
+                    // server credentials
+                    responseBer.encodeString("", isLdapV3());
+                    responseBer.encodeString("", isLdapV3());
+                    // challenge or response
+                    if (serverResponse != null) {
+                        responseBer.encodeOctetString(serverResponse, 0x87);
+                    }
+                    responseBer.endSeq();
+                    responseBer.endSeq();
+                    sendResponse();
+
+                } else {
+                    password = reqBer.parseStringWithTag(Ber.ASN_CONTEXT, isLdapV3(), null);
+
+                    if (userName.length() > 0 && password.length() > 0) {
+                        DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_BIND_USER", currentMessageId, userName));
+                        try {
+                            session = ExchangeSessionFactory.getInstance(userName, password);
+                            DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_BIND_SUCCESS"));
+                            sendClient(currentMessageId, LDAP_REP_BIND, LDAP_SUCCESS, "");
+                        } catch (IOException e) {
+                            DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_BIND_INVALID_CREDENTIALS"));
+                            sendClient(currentMessageId, LDAP_REP_BIND, LDAP_INVALID_CREDENTIALS, "");
+                        }
+                    } else {
+                        DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_BIND_ANONYMOUS", currentMessageId));
+                        // anonymous bind
+                        sendClient(currentMessageId, LDAP_REP_BIND, LDAP_SUCCESS, "");
+                    }
+                }
+
+            } else if (requestOperation == LDAP_REQ_UNBIND) {
+                DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_UNBIND", currentMessageId));
+                if (session != null) {
+                    session = null;
+                }
+            } else if (requestOperation == LDAP_REQ_SEARCH) {
+                reqBer.parseSeq(null);
+                String dn = reqBer.parseString(isLdapV3());
+                int scope = reqBer.parseEnumeration();
+                /*int derefAliases =*/
+                reqBer.parseEnumeration();
+                int sizeLimit = reqBer.parseInt();
+                if (sizeLimit > 100 || sizeLimit == 0) {
+                    sizeLimit = 100;
+                }
+                int timelimit = reqBer.parseInt();
+                /*boolean typesOnly =*/
+                reqBer.parseBoolean();
+                LdapFilter ldapFilter = parseFilter(reqBer);
+                Set<String> returningAttributes = parseReturningAttributes(reqBer);
+                SearchRunnable searchRunnable = new SearchRunnable(currentMessageId, dn, scope, sizeLimit, timelimit, ldapFilter, returningAttributes);
+                if (BASE_CONTEXT.equalsIgnoreCase(dn) || OD_USER_CONTEXT.equalsIgnoreCase(dn) || OD_USER_CONTEXT_LION.equalsIgnoreCase(dn)) {
+                    // launch search in a separate thread
+                    synchronized (searchThreadMap) {
+                        searchThreadMap.put(currentMessageId, searchRunnable);
+                    }
+                    Thread searchThread = new Thread(searchRunnable);
+                    searchThread.setName(getName() + "-Search-" + currentMessageId);
+                    searchThread.start();
+                } else {
+                    // no need to create a separate thread, just run
+                    searchRunnable.run();
+                }
+
+            } else if (requestOperation == LDAP_REQ_ABANDON) {
+                int abandonMessageId = 0;
+                try {
+                    abandonMessageId = (Integer) PARSE_INT_WITH_TAG_METHOD.invoke(reqBer, LDAP_REQ_ABANDON);
+                    synchronized (searchThreadMap) {
+                        SearchRunnable searchRunnable = searchThreadMap.get(abandonMessageId);
+                        if (searchRunnable != null) {
+                            searchRunnable.abandon();
+                            searchThreadMap.remove(currentMessageId);
+                        }
+                    }
+                } catch (IllegalAccessException e) {
+                    DavGatewayTray.error(e);
+                } catch (InvocationTargetException e) {
+                    DavGatewayTray.error(e);
+                }
+                DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_ABANDON_SEARCH", currentMessageId, abandonMessageId));
+            } else {
+                DavGatewayTray.debug(new BundleMessage("LOG_LDAP_UNSUPPORTED_OPERATION", requestOperation));
+                sendClient(currentMessageId, LDAP_REP_RESULT, LDAP_OTHER, "Unsupported operation");
+            }
+        } catch (IOException e) {
+            dumpBer(inbuf, offset);
+            try {
+                sendErr(currentMessageId, LDAP_REP_RESULT, e);
+            } catch (IOException e2) {
+                DavGatewayTray.debug(new BundleMessage("LOG_EXCEPTION_SENDING_ERROR_TO_CLIENT"), e2);
+            }
+            throw e;
+        }
+    }
+
+    protected void dumpBer(byte[] inbuf, int offset) {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        Ber.dumpBER(baos, "LDAP request buffer\n", inbuf, 0, offset);
+        try {
+            LOGGER.debug(new String(baos.toByteArray(), "UTF-8"));
+        } catch (UnsupportedEncodingException e) {
+            // should not happen
+            LOGGER.error(e);
+        }
+    }
+
+    protected LdapFilter parseFilter(BerDecoder reqBer) throws IOException {
+        LdapFilter ldapFilter;
+        if (reqBer.peekByte() == LDAP_FILTER_PRESENT) {
+            String attributeName = reqBer.parseStringWithTag(LDAP_FILTER_PRESENT, isLdapV3(), null).toLowerCase();
+            ldapFilter = new SimpleFilter(attributeName);
+        } else {
+            int[] seqSize = new int[1];
+            int ldapFilterType = reqBer.parseSeq(seqSize);
+            int end = reqBer.getParsePosition() + seqSize[0];
+
+            ldapFilter = parseNestedFilter(reqBer, ldapFilterType, end);
+        }
+
+        return ldapFilter;
+    }
+
+    protected LdapFilter parseNestedFilter(BerDecoder reqBer, int ldapFilterType, int end) throws IOException {
+        LdapFilter nestedFilter;
+
+        if ((ldapFilterType == LDAP_FILTER_OR) || (ldapFilterType == LDAP_FILTER_AND)
+                || ldapFilterType == LDAP_FILTER_NOT) {
+            nestedFilter = new CompoundFilter(ldapFilterType);
+
+            while (reqBer.getParsePosition() < end && reqBer.bytesLeft() > 0) {
+                if (reqBer.peekByte() == LDAP_FILTER_PRESENT) {
+                    String attributeName = reqBer.parseStringWithTag(LDAP_FILTER_PRESENT, isLdapV3(), null).toLowerCase();
+                    nestedFilter.add(new SimpleFilter(attributeName));
+                } else {
+                    int[] seqSize = new int[1];
+                    int ldapFilterOperator = reqBer.parseSeq(seqSize);
+                    int subEnd = reqBer.getParsePosition() + seqSize[0];
+                    nestedFilter.add(parseNestedFilter(reqBer, ldapFilterOperator, subEnd));
+                }
+            }
+        } else {
+            // simple filter
+            nestedFilter = parseSimpleFilter(reqBer, ldapFilterType);
+        }
+
+        return nestedFilter;
+    }
+
+    protected LdapFilter parseSimpleFilter(BerDecoder reqBer, int ldapFilterOperator) throws IOException {
+        String attributeName = reqBer.parseString(isLdapV3()).toLowerCase();
+        int ldapFilterMode = 0;
+
+        StringBuilder value = new StringBuilder();
+        if (ldapFilterOperator == LDAP_FILTER_SUBSTRINGS) {
+            // Thunderbird sends values with space as separate strings, rebuild value
+            int[] seqSize = new int[1];
+            /*LBER_SEQUENCE*/
+            reqBer.parseSeq(seqSize);
+            int end = reqBer.getParsePosition() + seqSize[0];
+            while (reqBer.getParsePosition() < end && reqBer.bytesLeft() > 0) {
+                ldapFilterMode = reqBer.peekByte();
+                if (value.length() > 0) {
+                    value.append(' ');
+                }
+                value.append(reqBer.parseStringWithTag(ldapFilterMode, isLdapV3(), null));
+            }
+        } else if (ldapFilterOperator == LDAP_FILTER_EQUALITY) {
+            value.append(reqBer.parseString(isLdapV3()));
+        } else {
+            DavGatewayTray.warn(new BundleMessage("LOG_LDAP_UNSUPPORTED_FILTER_VALUE"));
+        }
+
+        String sValue = value.toString();
+
+        if ("uid".equalsIgnoreCase(attributeName) && sValue.equals(userName)) {
+            // replace with actual alias instead of login name search, only in Dav mode
+            if (sValue.equals(userName) && session instanceof DavExchangeSession) {
+                sValue = session.getAlias();
+                DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REPLACED_UID_FILTER", userName, sValue));
+            }
+        }
+
+        return new SimpleFilter(attributeName, sValue, ldapFilterOperator, ldapFilterMode);
+    }
+
+    protected Set<String> parseReturningAttributes(BerDecoder reqBer) throws IOException {
+        Set<String> returningAttributes = new HashSet<String>();
+        int[] seqSize = new int[1];
+        reqBer.parseSeq(seqSize);
+        int end = reqBer.getParsePosition() + seqSize[0];
+        while (reqBer.getParsePosition() < end && reqBer.bytesLeft() > 0) {
+            returningAttributes.add(reqBer.parseString(isLdapV3()).toLowerCase());
+        }
+        return returningAttributes;
+    }
+
+    /**
+     * Send Root DSE
+     *
+     * @param currentMessageId current message id
+     * @throws IOException on error
+     */
+    protected void sendRootDSE(int currentMessageId) throws IOException {
+        DavGatewayTray.debug(new BundleMessage("LOG_LDAP_SEND_ROOT_DSE"));
+
+        Map<String, Object> attributes = new HashMap<String, Object>();
+        attributes.put("objectClass", "top");
+        attributes.put("namingContexts", NAMING_CONTEXTS);
+        //attributes.put("supportedsaslmechanisms", "PLAIN");
+
+        sendEntry(currentMessageId, "Root DSE", attributes);
+    }
+
+    protected void addIf(Map<String, Object> attributes, Set<String> returningAttributes, String name, Object value) {
+        if ((returningAttributes.isEmpty()) || returningAttributes.contains(name)) {
+            attributes.put(name, value);
+        }
+    }
+
+    protected String currentHostName;
+
+    protected String getCurrentHostName() throws UnknownHostException {
+        if (currentHostName == null) {
+            if (client.getInetAddress().isLoopbackAddress()) {
+                // local address, probably using localhost in iCal URL
+                currentHostName = "localhost";
+            } else {
+                // remote address, send fully qualified domain name
+                currentHostName = InetAddress.getLocalHost().getCanonicalHostName();
+            }
+        }
+        return currentHostName;
+    }
+
+    /**
+     * Cache serviceInfo string value
+     */
+    protected String serviceInfo;
+
+    protected String getServiceInfo() throws UnknownHostException {
+        if (serviceInfo == null) {
+            StringBuilder buffer = new StringBuilder();
+            buffer.append("<?xml version='1.0' encoding='UTF-8'?>" +
+                    "<!DOCTYPE plist PUBLIC '-//Apple//DTD PLIST 1.0//EN' 'http://www.apple.com/DTDs/PropertyList-1.0.dtd'>" +
+                    "<plist version='1.0'>" +
+                    "<dict>" +
+                    "<key>com.apple.macosxserver.host</key>" +
+                    "<array>" +
+                    "<string>localhost</string>" +        // NOTE: Will be replaced by real hostname
+                    "</array>" +
+                    "<key>com.apple.macosxserver.virtualhosts</key>" +
+                    "<dict>" +
+                    "<key>" + VIRTUALHOST_GUID + "</key>" +
+                    "<dict>" +
+                    "<key>hostDetails</key>" +
+                    "<dict>" +
+                    "<key>http</key>" +
+                    "<dict>" +
+                    "<key>enabled</key>" +
+                    "<true/>" +
+                    "<key>port</key>" +
+                    "<integer>");
+            buffer.append(Settings.getProperty("davmail.caldavPort"));
+            buffer.append("</integer>" +
+                    "</dict>" +
+                    "<key>https</key>" +
+                    "<dict>" +
+                    "<key>disabled</key>" +
+                    "<false/>" +
+                    "<key>port</key>" +
+                    "<integer>0</integer>" +
+                    "</dict>" +
+                    "</dict>" +
+                    "<key>hostname</key>" +
+                    "<string>");
+            buffer.append(getCurrentHostName());
+            buffer.append("</string>" +
+                    "<key>serviceInfo</key>" +
+                    "<dict>" +
+                    "<key>calendar</key>" +
+                    "<dict>" +
+                    "<key>enabled</key>" +
+                    "<true/>" +
+                    "<key>templates</key>" +
+                    "<dict>" +
+                    "<key>calendarUserAddresses</key>" +
+                    "<array>" +
+                    "<string>%(principaluri)s</string>" +
+                    "<string>mailto:%(email)s</string>" +
+                    "<string>urn:uuid:%(guid)s</string>" +
+                    "</array>" +
+                    "<key>principalPath</key>" +
+                    "<string>/principals/__uuids__/%(guid)s/</string>" +
+                    "</dict>" +
+                    "</dict>" +
+                    "</dict>" +
+                    "<key>serviceType</key>" +
+                    "<array>" +
+                    "<string>calendar</string>" +
+                    "</array>" +
+                    "</dict>" +
+                    "</dict>" +
+                    "</dict>" +
+                    "</plist>");
+            serviceInfo = buffer.toString();
+        }
+        return serviceInfo;
+    }
+
+    /**
+     * Send ComputerContext
+     *
+     * @param currentMessageId    current message id
+     * @param returningAttributes attributes to return
+     * @throws IOException on error
+     */
+    protected void sendComputerContext(int currentMessageId, Set<String> returningAttributes) throws IOException {
+        List<String> objectClasses = new ArrayList<String>();
+        objectClasses.add("top");
+        objectClasses.add("apple-computer");
+        Map<String, Object> attributes = new HashMap<String, Object>();
+        addIf(attributes, returningAttributes, "objectClass", objectClasses);
+        addIf(attributes, returningAttributes, "apple-generateduid", COMPUTER_GUID);
+        addIf(attributes, returningAttributes, "apple-serviceinfo", getServiceInfo());
+        // TODO: remove ?
+        addIf(attributes, returningAttributes, "apple-xmlplist", getServiceInfo());
+        addIf(attributes, returningAttributes, "apple-serviceslocator", "::anyService");
+        addIf(attributes, returningAttributes, "cn", getCurrentHostName());
+
+        String dn = "cn=" + getCurrentHostName() + ", " + COMPUTER_CONTEXT;
+        DavGatewayTray.debug(new BundleMessage("LOG_LDAP_SEND_COMPUTER_CONTEXT", dn, attributes));
+
+        sendEntry(currentMessageId, dn, attributes);
+    }
+
+    /**
+     * Send Base Context
+     *
+     * @param currentMessageId current message id
+     * @throws IOException on error
+     */
+    protected void sendBaseContext(int currentMessageId) throws IOException {
+        List<String> objectClasses = new ArrayList<String>();
+        objectClasses.add("top");
+        objectClasses.add("organizationalUnit");
+        Map<String, Object> attributes = new HashMap<String, Object>();
+        attributes.put("objectClass", objectClasses);
+        attributes.put("description", "DavMail Gateway LDAP for " + Settings.getProperty("davmail.url"));
+        sendEntry(currentMessageId, BASE_CONTEXT, attributes);
+    }
+
+    protected void sendEntry(int currentMessageId, String dn, Map<String, Object> attributes) throws IOException {
+        // synchronize on responseBer
+        synchronized (responseBer) {
+            responseBer.reset();
+            responseBer.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
+            responseBer.encodeInt(currentMessageId);
+            responseBer.beginSeq(LDAP_REP_SEARCH);
+            responseBer.encodeString(dn, isLdapV3());
+            responseBer.beginSeq(LBER_SEQUENCE);
+            for (Map.Entry<String, Object> entry : attributes.entrySet()) {
+                responseBer.beginSeq(LBER_SEQUENCE);
+                responseBer.encodeString(entry.getKey(), isLdapV3());
+                responseBer.beginSeq(LBER_SET);
+                Object values = entry.getValue();
+                if (values instanceof String) {
+                    responseBer.encodeString((String) values, isLdapV3());
+                } else if (values instanceof List) {
+                    for (Object value : (List) values) {
+                        responseBer.encodeString((String) value, isLdapV3());
+                    }
+                } else {
+                    throw new DavMailException("EXCEPTION_UNSUPPORTED_VALUE", values);
+                }
+                responseBer.endSeq();
+                responseBer.endSeq();
+            }
+            responseBer.endSeq();
+            responseBer.endSeq();
+            responseBer.endSeq();
+            sendResponse();
+        }
+    }
+
+    protected void sendErr(int currentMessageId, int responseOperation, Exception e) throws IOException {
+        String message = e.getMessage();
+        if (message == null) {
+            message = e.toString();
+        }
+        sendClient(currentMessageId, responseOperation, LDAP_OTHER, message);
+    }
+
+    protected void sendClient(int currentMessageId, int responseOperation, int status, String message) throws IOException {
+        responseBer.reset();
+
+        responseBer.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
+        responseBer.encodeInt(currentMessageId);
+        responseBer.beginSeq(responseOperation);
+        responseBer.encodeInt(status, LBER_ENUMERATED);
+        // dn
+        responseBer.encodeString("", isLdapV3());
+        // error message
+        responseBer.encodeString(message, isLdapV3());
+        responseBer.endSeq();
+        responseBer.endSeq();
+        sendResponse();
+    }
+
+    protected void sendResponse() throws IOException {
+        //Ber.dumpBER(System.out, ">\n", responseBer.getBuf(), 0, responseBer.getDataLen());
+        os.write(responseBer.getBuf(), 0, responseBer.getDataLen());
+        os.flush();
+    }
+
+    static interface LdapFilter {
+        ExchangeSession.Condition getContactSearchFilter();
+
+        Map<String, ExchangeSession.Contact> findInGAL(ExchangeSession session, Set<String> returningAttributes, int sizeLimit) throws IOException;
+
+        void add(LdapFilter filter);
+
+        boolean isFullSearch();
+
+        boolean isMatch(Map<String, String> person);
+    }
+
+    class CompoundFilter implements LdapFilter {
+        final Set<LdapFilter> criteria = new HashSet<LdapFilter>();
+        final int type;
+
+        CompoundFilter(int filterType) {
+            type = filterType;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder buffer = new StringBuilder();
+
+            if (type == LDAP_FILTER_OR) {
+                buffer.append("(|");
+            } else if (type == LDAP_FILTER_AND) {
+                buffer.append("(&");
+            } else {
+                buffer.append("(!");
+            }
+
+            for (LdapFilter child : criteria) {
+                buffer.append(child.toString());
+            }
+
+            buffer.append(')');
+
+            return buffer.toString();
+        }
+
+        /**
+         * Add child filter
+         *
+         * @param filter inner filter
+         */
+        public void add(LdapFilter filter) {
+            criteria.add(filter);
+        }
+
+        /**
+         * This is only a full search if every child
+         * is also a full search
+         *
+         * @return true if full search filter
+         */
+        public boolean isFullSearch() {
+            for (LdapFilter child : criteria) {
+                if (!child.isFullSearch()) {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        /**
+         * Build search filter for Contacts folder search.
+         * Use Exchange SEARCH syntax
+         *
+         * @return contact search filter
+         */
+        public ExchangeSession.Condition getContactSearchFilter() {
+            ExchangeSession.MultiCondition condition;
+
+            if (type == LDAP_FILTER_OR) {
+                condition = session.or();
+            } else {
+                condition = session.and();
+            }
+
+            for (LdapFilter child : criteria) {
+                condition.add(child.getContactSearchFilter());
+            }
+
+            return condition;
+        }
+
+        /**
+         * Test if person matches the current filter.
+         *
+         * @param person person attributes map
+         * @return true if filter match
+         */
+        public boolean isMatch(Map<String, String> person) {
+            if (type == LDAP_FILTER_OR) {
+                for (LdapFilter child : criteria) {
+                    if (!child.isFullSearch()) {
+                        if (child.isMatch(person)) {
+                            // We've found a match
+                            return true;
+                        }
+                    }
+                }
+
+                // No subconditions are met
+                return false;
+            } else if (type == LDAP_FILTER_AND) {
+                for (LdapFilter child : criteria) {
+                    if (!child.isFullSearch()) {
+                        if (!child.isMatch(person)) {
+                            // We've found a miss
+                            return false;
+                        }
+                    }
+                }
+
+                // All subconditions are met
+                return true;
+            }
+
+            return false;
+        }
+
+        /**
+         * Find persons in Exchange GAL matching filter.
+         * Iterate over child filters to build results.
+         *
+         * @param session Exchange session
+         * @return persons map
+         * @throws IOException on error
+         */
+        public Map<String, ExchangeSession.Contact> findInGAL(ExchangeSession session, Set<String> returningAttributes, int sizeLimit) throws IOException {
+            Map<String, ExchangeSession.Contact> persons = null;
+
+            for (LdapFilter child : criteria) {
+                int currentSizeLimit = sizeLimit;
+                if (persons != null) {
+                    currentSizeLimit -= persons.size();
+                }
+                Map<String, ExchangeSession.Contact> childFind = child.findInGAL(session, returningAttributes, currentSizeLimit);
+
+                if (childFind != null) {
+                    if (persons == null) {
+                        persons = childFind;
+                    } else if (type == LDAP_FILTER_OR) {
+                        // Create the union of the existing results and the child found results
+                        persons.putAll(childFind);
+                    } else if (type == LDAP_FILTER_AND) {
+                        // Append current child filter results that match all child filters to persons.
+                        // The hard part is that, due to the 100-item-returned galFind limit
+                        // we may catch new items that match all child filters in each child search.
+                        // Thus, instead of building the intersection, we check each result against
+                        // all filters.
+
+                        for (ExchangeSession.Contact result : childFind.values()) {
+                            if (isMatch(result)) {
+                                // This item from the child result set matches all sub-criteria, add it
+                                persons.put(result.get("uid"), result);
+                            }
+                        }
+                    }
+                }
+            }
+
+            if ((persons == null) && !isFullSearch()) {
+                // return an empty map (indicating no results were found)
+                return new HashMap<String, ExchangeSession.Contact>();
+            }
+
+            return persons;
+        }
+    }
+
+    class SimpleFilter implements LdapFilter {
+        static final String STAR = "*";
+        final String attributeName;
+        final String value;
+        final int mode;
+        final int operator;
+        final boolean canIgnore;
+
+        SimpleFilter(String attributeName) {
+            this.attributeName = attributeName;
+            this.value = SimpleFilter.STAR;
+            this.operator = LDAP_FILTER_SUBSTRINGS;
+            this.mode = 0;
+            this.canIgnore = checkIgnore();
+        }
+
+        SimpleFilter(String attributeName, String value, int ldapFilterOperator, int ldapFilterMode) {
+            this.attributeName = attributeName;
+            this.value = value;
+            this.operator = ldapFilterOperator;
+            this.mode = ldapFilterMode;
+            this.canIgnore = checkIgnore();
+        }
+
+        private boolean checkIgnore() {
+            if ("objectclass".equals(attributeName) && STAR.equals(value)) {
+                // ignore cases where any object class can match
+                return true;
+            } else if (IGNORE_MAP.contains(attributeName)) {
+                // Ignore this specific attribute
+                return true;
+            } else if (CRITERIA_MAP.get(attributeName) == null && getContactAttributeName(attributeName) == null) {
+                DavGatewayTray.debug(new BundleMessage("LOG_LDAP_UNSUPPORTED_FILTER_ATTRIBUTE",
+                        attributeName, value));
+
+                return true;
+            }
+
+            return false;
+        }
+
+        public boolean isFullSearch() {
+            // only (objectclass=*) is a full search
+            return "objectclass".equals(attributeName) && STAR.equals(value);
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder buffer = new StringBuilder();
+            buffer.append('(');
+            buffer.append(attributeName);
+            buffer.append('=');
+            if (SimpleFilter.STAR.equals(value)) {
+                buffer.append(SimpleFilter.STAR);
+            } else if (operator == LDAP_FILTER_SUBSTRINGS) {
+                if (mode == LDAP_SUBSTRING_FINAL || mode == LDAP_SUBSTRING_ANY) {
+                    buffer.append(SimpleFilter.STAR);
+                }
+                buffer.append(value);
+                if (mode == LDAP_SUBSTRING_INITIAL || mode == LDAP_SUBSTRING_ANY) {
+                    buffer.append(SimpleFilter.STAR);
+                }
+            } else {
+                buffer.append(value);
+            }
+
+            buffer.append(')');
+            return buffer.toString();
+        }
+
+        public ExchangeSession.Condition getContactSearchFilter() {
+            String contactAttributeName = getContactAttributeName(attributeName);
+
+            if (canIgnore || (contactAttributeName == null)) {
+                return null;
+            }
+
+            ExchangeSession.Condition condition = null;
+
+            if (operator == LDAP_FILTER_EQUALITY) {
+                condition = session.isEqualTo(contactAttributeName, value);
+            } else if ("*".equals(value)) {
+                condition = session.not(session.isNull(contactAttributeName));
+                // do not allow substring search on integer field imapUid
+            } else if (!"imapUid".equals(contactAttributeName)) {
+                // endsWith not supported by exchange, convert to contains
+                if (mode == LDAP_SUBSTRING_FINAL || mode == LDAP_SUBSTRING_ANY) {
+                    condition = session.contains(contactAttributeName, value);
+                } else {
+                    condition = session.startsWith(contactAttributeName, value);
+                }
+            }
+            return condition;
+        }
+
+        public boolean isMatch(Map<String, String> person) {
+            if (canIgnore) {
+                // Ignore this filter
+                return true;
+            }
+
+            String personAttributeValue = person.get(attributeName);
+
+            if (personAttributeValue == null) {
+                // No value to allow for filter match
+                return false;
+            } else if (value == null) {
+                // This is a presence filter: found
+                return true;
+            } else if ((operator == LDAP_FILTER_EQUALITY) && personAttributeValue.equalsIgnoreCase(value)) {
+                // Found an exact match
+                return true;
+            } else if ((operator == LDAP_FILTER_SUBSTRINGS) && (personAttributeValue.toLowerCase().indexOf(value.toLowerCase()) >= 0)) {
+                // Found a substring match
+                return true;
+            }
+
+            return false;
+        }
+
+        public Map<String, ExchangeSession.Contact> findInGAL(ExchangeSession session, Set<String> returningAttributes, int sizeLimit) throws IOException {
+            if (canIgnore) {
+                return null;
+            }
+
+            String contactAttributeName = getContactAttributeName(attributeName);
+
+            if (contactAttributeName != null) {
+                // quick fix for cn=* filter
+                Map<String, ExchangeSession.Contact> galPersons = session.galFind(session.startsWith(contactAttributeName, "*".equals(value) ? "A" : value),
+                        convertLdapToContactReturningAttributes(returningAttributes), sizeLimit);
+
+                if (operator == LDAP_FILTER_EQUALITY) {
+                    // Make sure only exact matches are returned
+
+                    Map<String, ExchangeSession.Contact> results = new HashMap<String, ExchangeSession.Contact>();
+
+                    for (ExchangeSession.Contact person : galPersons.values()) {
+                        if (isMatch(person)) {
+                            // Found an exact match
+                            results.put(person.get("uid"), person);
+                        }
+                    }
+
+                    return results;
+                } else {
+                    return galPersons;
+                }
+            }
+
+            return null;
+        }
+
+        public void add(LdapFilter filter) {
+            // Should never be called
+            DavGatewayTray.error(new BundleMessage("LOG_LDAP_UNSUPPORTED_FILTER", "nested simple filters"));
+        }
+    }
+
+    /**
+     * Convert contact attribute name to LDAP attribute name.
+     *
+     * @param ldapAttributeName ldap attribute name
+     * @return contact attribute name
+     */
+    protected static String getContactAttributeName(String ldapAttributeName) {
+        String contactAttributeName = null;
+        // first look in contact attributes
+        if (ExchangeSession.CONTACT_ATTRIBUTES.contains(ldapAttributeName)) {
+            contactAttributeName = ldapAttributeName;
+        } else if (LDAP_TO_CONTACT_ATTRIBUTE_MAP.containsKey(ldapAttributeName)) {
+            String mappedAttribute = LDAP_TO_CONTACT_ATTRIBUTE_MAP.get(ldapAttributeName);
+            if (mappedAttribute != null) {
+                contactAttributeName = mappedAttribute;
+            }
+        } else {
+            DavGatewayTray.debug(new BundleMessage("UNKNOWN_ATTRIBUTE", ldapAttributeName));
+        }
+        return contactAttributeName;
+    }
+
+    /**
+     * Convert LDAP attribute name to contact attribute name.
+     *
+     * @param contactAttributeName ldap attribute name
+     * @return contact attribute name
+     */
+    protected static String getLdapAttributeName(String contactAttributeName) {
+        String mappedAttributeName = CONTACT_TO_LDAP_ATTRIBUTE_MAP.get(contactAttributeName);
+        if (mappedAttributeName != null) {
+            return mappedAttributeName;
+        } else {
+            return contactAttributeName;
+        }
+    }
+
+    protected Set<String> convertLdapToContactReturningAttributes(Set<String> returningAttributes) {
+        Set<String> contactReturningAttributes;
+        if (returningAttributes != null && !returningAttributes.isEmpty()) {
+            contactReturningAttributes = new HashSet<String>();
+            // always return uid
+            contactReturningAttributes.add("imapUid");
+            for (String attribute : returningAttributes) {
+                String contactAttributeName = getContactAttributeName(attribute);
+                if (contactAttributeName != null) {
+                    contactReturningAttributes.add(contactAttributeName);
+                }
+            }
+        } else {
+            contactReturningAttributes = ExchangeSession.CONTACT_ATTRIBUTES;
+        }
+        return contactReturningAttributes;
+    }
+
+    protected class SearchRunnable implements Runnable {
+        private final int currentMessageId;
+        private final String dn;
+        private final int scope;
+        private final int sizeLimit;
+        private final int timelimit;
+        private final LdapFilter ldapFilter;
+        private final Set<String> returningAttributes;
+        private boolean abandon;
+
+        protected SearchRunnable(int currentMessageId, String dn, int scope, int sizeLimit, int timelimit, LdapFilter ldapFilter, Set<String> returningAttributes) {
+            this.currentMessageId = currentMessageId;
+            this.dn = dn;
+            this.scope = scope;
+            this.sizeLimit = sizeLimit;
+            this.timelimit = timelimit;
+            this.ldapFilter = ldapFilter;
+            this.returningAttributes = returningAttributes;
+        }
+
+        /**
+         * Abandon search.
+         */
+        protected void abandon() {
+            abandon = true;
+        }
+
+        public void run() {
+            try {
+                int size = 0;
+                DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_SEARCH", currentMessageId, dn, scope, sizeLimit, timelimit, ldapFilter.toString(), returningAttributes));
+
+                if (scope == SCOPE_BASE_OBJECT) {
+                    if ("".equals(dn)) {
+                        size = 1;
+                        sendRootDSE(currentMessageId);
+                    } else if (BASE_CONTEXT.equals(dn)) {
+                        size = 1;
+                        // root
+                        sendBaseContext(currentMessageId);
+                    } else if (dn.startsWith("uid=") && dn.indexOf(',') > 0) {
+                        if (session != null) {
+                            // single user request
+                            String uid = dn.substring("uid=".length(), dn.indexOf(','));
+                            Map<String, ExchangeSession.Contact> persons = null;
+
+                            // first search in contact
+                            try {
+                                // check if this is a contact uid
+                                Integer.parseInt(uid);
+                                persons = contactFind(session.isEqualTo("imapUid", uid), returningAttributes, sizeLimit);
+                            } catch (NumberFormatException e) {
+                                // ignore, this is not a contact uid
+                            }
+
+                            // then in GAL
+                            if (persons == null || persons.isEmpty()) {
+                                persons = session.galFind(session.isEqualTo("imapUid", uid),
+                                        convertLdapToContactReturningAttributes(returningAttributes), sizeLimit);
+
+                                ExchangeSession.Contact person = persons.get(uid.toLowerCase());
+                                // filter out non exact results
+                                if (persons.size() > 1 || person == null) {
+                                    persons = new HashMap<String, ExchangeSession.Contact>();
+                                    if (person != null) {
+                                        persons.put(uid.toLowerCase(), person);
+                                    }
+                                }
+                            }
+                            size = persons.size();
+                            sendPersons(currentMessageId, dn.substring(dn.indexOf(',')), persons, returningAttributes);
+                        } else {
+                            DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_SEARCH_ANONYMOUS_ACCESS_FORBIDDEN", currentMessageId, dn));
+                        }
+                    } else {
+                        DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_SEARCH_INVALID_DN", currentMessageId, dn));
+                    }
+                } else if (COMPUTER_CONTEXT.equals(dn) || COMPUTER_CONTEXT_LION.equals(dn)) {
+                    size = 1;
+                    // computer context for iCal
+                    sendComputerContext(currentMessageId, returningAttributes);
+                } else if ((BASE_CONTEXT.equalsIgnoreCase(dn) || OD_USER_CONTEXT.equalsIgnoreCase(dn)) || OD_USER_CONTEXT_LION.equalsIgnoreCase(dn)) {
+                    if (session != null) {
+                        Map<String, ExchangeSession.Contact> persons = new HashMap<String, ExchangeSession.Contact>();
+                        if (ldapFilter.isFullSearch()) {
+                            // append personal contacts first
+                            for (ExchangeSession.Contact person : contactFind(null, returningAttributes, sizeLimit).values()) {
+                                persons.put(person.get("imapUid"), person);
+                                if (persons.size() == sizeLimit) {
+                                    break;
+                                }
+                            }
+                            // full search
+                            for (char c = 'A'; c <= 'Z'; c++) {
+                                if (!abandon && persons.size() < sizeLimit) {
+                                    for (ExchangeSession.Contact person : session.galFind(session.startsWith("cn", String.valueOf(c)),
+                                            convertLdapToContactReturningAttributes(returningAttributes), sizeLimit).values()) {
+                                        persons.put(person.get("uid"), person);
+                                        if (persons.size() == sizeLimit) {
+                                            break;
+                                        }
+                                    }
+                                }
+                                if (persons.size() == sizeLimit) {
+                                    break;
+                                }
+                            }
+                        } else {
+                            // append personal contacts first
+                            ExchangeSession.Condition filter = ldapFilter.getContactSearchFilter();
+
+                            // if ldapfilter is not a full search and filter is null,
+                            // ignored all attribute filters => return empty results
+                            if (ldapFilter.isFullSearch() || filter != null) {
+                                for (ExchangeSession.Contact person : contactFind(filter, returningAttributes, sizeLimit).values()) {
+                                    persons.put(person.get("imapUid"), person);
+
+                                    if (persons.size() == sizeLimit) {
+                                        break;
+                                    }
+                                }
+                                if (!abandon && persons.size() < sizeLimit) {
+                                    for (ExchangeSession.Contact person : ldapFilter.findInGAL(session, returningAttributes, sizeLimit - persons.size()).values()) {
+                                        if (persons.size() == sizeLimit) {
+                                            break;
+                                        }
+
+                                        persons.put(person.get("uid"), person);
+                                    }
+                                }
+                            }
+                        }
+
+                        size = persons.size();
+                        DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_SEARCH_FOUND_RESULTS", currentMessageId, size));
+                        sendPersons(currentMessageId, ", " + dn, persons, returningAttributes);
+                        DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_SEARCH_END", currentMessageId));
+                    } else {
+                        DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_SEARCH_ANONYMOUS_ACCESS_FORBIDDEN", currentMessageId, dn));
+                    }
+                } else if (dn != null && dn.length() > 0 && !OD_CONFIG_CONTEXT.equals(dn) && !OD_GROUP_CONTEXT.equals(dn)) {
+                    DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_SEARCH_INVALID_DN", currentMessageId, dn));
+                }
+
+                // iCal: do not send LDAP_SIZE_LIMIT_EXCEEDED on apple-computer search by cn with sizelimit 1
+                if (size > 1 && size == sizeLimit) {
+                    DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_SEARCH_SIZE_LIMIT_EXCEEDED", currentMessageId));
+                    sendClient(currentMessageId, LDAP_REP_RESULT, LDAP_SIZE_LIMIT_EXCEEDED, "");
+                } else {
+                    DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_SEARCH_SUCCESS", currentMessageId));
+                    sendClient(currentMessageId, LDAP_REP_RESULT, LDAP_SUCCESS, "");
+                }
+            } catch (SocketException e) {
+                // client closed connection
+                LOGGER.warn(BundleMessage.formatLog("LOG_CLIENT_CLOSED_CONNECTION"));
+            } catch (IOException e) {
+                DavGatewayTray.log(e);
+                try {
+                    sendErr(currentMessageId, LDAP_REP_RESULT, e);
+                } catch (IOException e2) {
+                    DavGatewayTray.debug(new BundleMessage("LOG_EXCEPTION_SENDING_ERROR_TO_CLIENT"), e2);
+                }
+            } finally {
+                synchronized (searchThreadMap) {
+                    searchThreadMap.remove(currentMessageId);
+                }
+            }
+
+        }
+
+        /**
+         * Search users in contacts folder
+         *
+         * @param condition           search filter
+         * @param returningAttributes requested attributes
+         * @param maxCount            maximum item count
+         * @return List of users
+         * @throws IOException on error
+         */
+        public Map<String, ExchangeSession.Contact> contactFind(ExchangeSession.Condition condition, Set<String> returningAttributes, int maxCount) throws IOException {
+            Map<String, ExchangeSession.Contact> results = new HashMap<String, ExchangeSession.Contact>();
+
+            Set<String> contactReturningAttributes = convertLdapToContactReturningAttributes(returningAttributes);
+            contactReturningAttributes.remove("apple-serviceslocator");
+            List<ExchangeSession.Contact> contacts = session.searchContacts(ExchangeSession.CONTACTS, contactReturningAttributes, condition, maxCount);
+
+            for (ExchangeSession.Contact contact : contacts) {
+                // use imapUid as uid
+                String imapUid = contact.get("imapUid");
+                if (imapUid != null) {
+                    results.put(imapUid, contact);
+                }
+            }
+
+            return results;
+        }
+
+
+        /**
+         * Convert to LDAP attributes and send entry
+         *
+         * @param currentMessageId    current Message Id
+         * @param baseContext         request base context (BASE_CONTEXT or OD_BASE_CONTEXT)
+         * @param persons             persons Map
+         * @param returningAttributes returning attributes
+         * @throws IOException on error
+         */
+        protected void sendPersons(int currentMessageId, String baseContext, Map<String, ExchangeSession.Contact> persons, Set<String> returningAttributes) throws IOException {
+            boolean needObjectClasses = returningAttributes.contains("objectclass") || returningAttributes.isEmpty();
+            boolean returnAllAttributes = returningAttributes.isEmpty();
+
+            for (ExchangeSession.Contact person : persons.values()) {
+                if (abandon) {
+                    break;
+                }
+
+                Map<String, Object> ldapPerson = new HashMap<String, Object>();
+
+                // convert Contact entries
+                if (returnAllAttributes) {
+                    // just convert contact attributes to default ldap names
+                    for (Map.Entry<String, String> entry : person.entrySet()) {
+                        String ldapAttribute = getLdapAttributeName(entry.getKey());
+                        String value = entry.getValue();
+                        if (value != null) {
+                            ldapPerson.put(ldapAttribute, value);
+                        }
+                    }
+                } else {
+                    // always map uid
+                    ldapPerson.put("uid", person.get("imapUid"));
+                    // iterate over requested attributes
+                    for (String ldapAttribute : returningAttributes) {
+                        String contactAttribute = getContactAttributeName(ldapAttribute);
+                        String value = person.get(contactAttribute);
+                        if (value != null) {
+                            if (ldapAttribute.startsWith("birth")) {
+                                SimpleDateFormat parser = ExchangeSession.getZuluDateFormat();
+                                Calendar calendar = Calendar.getInstance();
+                                try {
+                                    calendar.setTime(parser.parse(value));
+                                } catch (ParseException e) {
+                                    throw new IOException(e + " " + e.getMessage());
+                                }
+                                if ("birthday".equals(ldapAttribute)) {
+                                    value = String.valueOf(calendar.get(Calendar.DAY_OF_MONTH));
+                                } else if ("birthmonth".equals(ldapAttribute)) {
+                                    value = String.valueOf(calendar.get(Calendar.MONTH) + 1);
+                                } else if ("birthyear".equals(ldapAttribute)) {
+                                    value = String.valueOf(calendar.get(Calendar.YEAR));
+                                }
+                            }
+                            ldapPerson.put(ldapAttribute, value);
+                        }
+                    }
+                }
+
+                // Process all attributes which have static mappings
+                for (Map.Entry<String, String> entry : STATIC_ATTRIBUTE_MAP.entrySet()) {
+                    String ldapAttribute = entry.getKey();
+                    String value = entry.getValue();
+
+                    if (value != null
+                            && (returnAllAttributes || returningAttributes.contains(ldapAttribute))) {
+                        ldapPerson.put(ldapAttribute, value);
+                    }
+                }
+
+                if (needObjectClasses) {
+                    ldapPerson.put("objectClass", PERSON_OBJECT_CLASSES);
+                }
+
+                // iCal: copy email to apple-generateduid, encode @
+                if (returnAllAttributes || returningAttributes.contains("apple-generateduid")) {
+                    String mail = (String) ldapPerson.get("mail");
+                    if (mail != null) {
+                        ldapPerson.put("apple-generateduid", mail.replaceAll("@", "__AT__"));
+                    } else {
+                        // failover, should not happen
+                        ldapPerson.put("apple-generateduid", ldapPerson.get("uid"));
+                    }
+                }
+
+                // iCal: replace current user alias with login name
+                if (session.getAlias().equals(ldapPerson.get("uid"))) {
+                    if (returningAttributes.contains("uidnumber")) {
+                        ldapPerson.put("uidnumber", userName);
+                    }
+                }
+                DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_SEARCH_SEND_PERSON", currentMessageId, ldapPerson.get("uid"), baseContext, ldapPerson));
+                sendEntry(currentMessageId, "uid=" + ldapPerson.get("uid") + baseContext, ldapPerson);
+            }
+
+        }
+
+    }
+}
diff --git a/src/java/davmail/ldap/LdapServer.java b/src/java/davmail/ldap/LdapServer.java
new file mode 100644
index 0000000..d562712
--- /dev/null
+++ b/src/java/davmail/ldap/LdapServer.java
@@ -0,0 +1,56 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.ldap;
+
+import davmail.AbstractConnection;
+import davmail.AbstractServer;
+import davmail.Settings;
+
+import java.net.Socket;
+
+/**
+ * LDAP server, handle LDAP directory requests.
+ */
+public class LdapServer extends AbstractServer {
+    /**
+     * Default LDAP port
+     */
+    public static final int DEFAULT_PORT = 389;
+
+    /**
+     * Create a ServerSocket to listen for connections.
+     * Start the thread.
+     *
+     * @param port pop listen port, 389 if not defined (0)
+     */
+    public LdapServer(int port) {
+        super(LdapServer.class.getName(), port, LdapServer.DEFAULT_PORT);
+        nosslFlag = Settings.getBooleanProperty("davmail.ssl.nosecureldap");
+    }
+
+    @Override
+    public String getProtocolName() {
+        return "LDAP";
+    }
+
+    @Override
+    public AbstractConnection createConnectionHandler(Socket clientSocket) {
+        return new LdapConnection(clientSocket);
+    }
+}
\ No newline at end of file
diff --git a/src/java/davmail/pop/PopConnection.java b/src/java/davmail/pop/PopConnection.java
new file mode 100644
index 0000000..2792694
--- /dev/null
+++ b/src/java/davmail/pop/PopConnection.java
@@ -0,0 +1,354 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.pop;
+
+import davmail.AbstractConnection;
+import davmail.BundleMessage;
+import davmail.DavGateway;
+import davmail.Settings;
+import davmail.exchange.DoubleDotOutputStream;
+import davmail.exchange.ExchangeSession;
+import davmail.exchange.ExchangeSessionFactory;
+import davmail.ui.tray.DavGatewayTray;
+import davmail.util.IOUtil;
+import org.apache.log4j.Logger;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.net.SocketException;
+import java.util.Date;
+import java.util.List;
+import java.util.StringTokenizer;
+
+/**
+ * Dav Gateway pop connection implementation
+ */
+public class PopConnection extends AbstractConnection {
+    private static final Logger LOGGER = Logger.getLogger(PopConnection.class);
+
+    private List<ExchangeSession.Message> messages;
+
+    /**
+     * Initialize the streams and start the thread.
+     *
+     * @param clientSocket POP client socket
+     */
+    public PopConnection(Socket clientSocket) {
+        super(PopConnection.class.getSimpleName(), clientSocket, null);
+    }
+
+    protected long getTotalMessagesLength() {
+        int result = 0;
+        for (ExchangeSession.Message message : messages) {
+            result += message.size;
+        }
+        return result;
+    }
+
+    protected void printCapabilities() throws IOException {
+        sendClient("TOP");
+        sendClient("USER");
+        sendClient("UIDL");
+        sendClient(".");
+    }
+
+    protected void printList() throws IOException {
+        int i = 1;
+        for (ExchangeSession.Message message : messages) {
+            sendClient(i++ + " " + message.size);
+        }
+        sendClient(".");
+    }
+
+    protected void printUidList() throws IOException {
+        int i = 1;
+        for (ExchangeSession.Message message : messages) {
+            sendClient(i++ + " " + message.getUid());
+        }
+        sendClient(".");
+    }
+
+
+    @Override
+    public void run() {
+        String line;
+        StringTokenizer tokens;
+
+        try {
+            ExchangeSessionFactory.checkConfig();
+            sendOK("DavMail " + DavGateway.getCurrentVersion() + " POP ready at " + new Date());
+
+            for (; ;) {
+                line = readClient();
+                // unable to read line, connection closed ?
+                if (line == null) {
+                    break;
+                }
+
+                tokens = new StringTokenizer(line);
+                if (tokens.hasMoreTokens()) {
+                    String command = tokens.nextToken();
+
+                    if ("QUIT".equalsIgnoreCase(command)) {
+                        // delete messages before quit
+                        if (session != null) {
+                            session.purgeOldestTrashAndSentMessages();
+                        }
+                        sendOK("Bye");
+                        break;
+                    } else if ("USER".equalsIgnoreCase(command)) {
+                        userName = null;
+                        password = null;
+                        session = null;
+                        if (tokens.hasMoreTokens()) {
+                            userName = line.substring("USER ".length());
+                            sendOK("USER : " + userName);
+                            state = State.USER;
+                        } else {
+                            sendERR("invalid syntax");
+                            state = State.INITIAL;
+                        }
+                    } else if ("PASS".equalsIgnoreCase(command)) {
+                        if (state != State.USER) {
+                            sendERR("invalid state");
+                            state = State.INITIAL;
+                        } else if (!tokens.hasMoreTokens()) {
+                            sendERR("invalid syntax");
+                        } else {
+                            // bug 2194492 : allow space in password
+                            password = line.substring("PASS".length() + 1);
+                            try {
+                                session = ExchangeSessionFactory.getInstance(userName, password);
+                                sendOK("PASS");
+                                state = State.AUTHENTICATED;
+                            } catch (SocketException e) {
+                                // can not send error to client after a socket exception
+                                LOGGER.warn(BundleMessage.formatLog("LOG_CLIENT_CLOSED_CONNECTION"));
+                            } catch (Exception e) {
+                                DavGatewayTray.error(e);
+                                sendERR(e);
+                            }
+                        }
+                    } else if ("CAPA".equalsIgnoreCase(command)) {
+                        sendOK("Capability list follows");
+                        printCapabilities();
+                    } else if (state != State.AUTHENTICATED) {
+                        sendERR("Invalid state not authenticated");
+                    } else {
+                        // load messages (once)
+                        if (messages == null) {
+                            messages = session.getAllMessageUidAndSize("INBOX");
+                        }
+                        if ("STAT".equalsIgnoreCase(command)) {
+                            sendOK(messages.size() + " " +
+                                    getTotalMessagesLength());
+                        } else if ("NOOP".equalsIgnoreCase(command)) {
+                            sendOK("");
+                        } else if ("LIST".equalsIgnoreCase(command)) {
+                            if (tokens.hasMoreTokens()) {
+                                String token = tokens.nextToken();
+                                try {
+                                    int messageNumber = Integer.valueOf(token);
+                                    ExchangeSession.Message message = messages.get(messageNumber - 1);
+                                    sendOK("" + messageNumber + ' ' + message.size);
+                                } catch (NumberFormatException e) {
+                                    sendERR("Invalid message index: " + token);
+                                } catch (IndexOutOfBoundsException e) {
+                                    sendERR("Invalid message index: " + token);
+                                }
+                            } else {
+                                sendOK(messages.size() +
+                                        " messages (" + getTotalMessagesLength() +
+                                        " octets)");
+                                printList();
+                            }
+                        } else if ("UIDL".equalsIgnoreCase(command)) {
+                            if (tokens.hasMoreTokens()) {
+                                String token = tokens.nextToken();
+                                try {
+                                    int messageNumber = Integer.valueOf(token);
+                                    sendOK(messageNumber + " " + messages.get(messageNumber - 1).getUid());
+                                } catch (NumberFormatException e) {
+                                    sendERR("Invalid message index: " + token);
+                                } catch (IndexOutOfBoundsException e) {
+                                    sendERR("Invalid message index: " + token);
+                                }
+                            } else {
+                                sendOK(messages.size() +
+                                        " messages (" + getTotalMessagesLength() +
+                                        " octets)");
+                                printUidList();
+                            }
+                        } else if ("RETR".equalsIgnoreCase(command)) {
+                            if (tokens.hasMoreTokens()) {
+                                try {
+                                    int messageNumber = Integer.valueOf(tokens.nextToken()) - 1;
+                                    sendOK("");
+                                    DoubleDotOutputStream doubleDotOutputStream = new DoubleDotOutputStream(os);
+                                    ExchangeSession.Message message = messages.get(messageNumber);
+                                    IOUtil.write(message.getRawInputStream(), doubleDotOutputStream);
+                                    doubleDotOutputStream.close();
+                                    if (Settings.getBooleanProperty("davmail.popMarkReadOnRetr")) {
+                                        message.markRead();
+                                    }
+                                } catch (SocketException e) {
+                                    // can not send error to client after a socket exception
+                                    LOGGER.warn(BundleMessage.formatLog("LOG_CLIENT_CLOSED_CONNECTION"));
+                                } catch (Exception e) {
+                                    DavGatewayTray.error(new BundleMessage("LOG_ERROR_RETRIEVING_MESSAGE"), e);
+                                    sendERR("error retrieving message " + e + ' ' + e.getMessage());
+                                }
+                            } else {
+                                sendERR("invalid message index");
+                            }
+                        } else if ("DELE".equalsIgnoreCase(command)) {
+                            if (tokens.hasMoreTokens()) {
+                                ExchangeSession.Message message;
+                                try {
+                                    int messageNumber = Integer.valueOf(tokens.
+                                            nextToken()) - 1;
+                                    message = messages.get(messageNumber);
+                                    message.moveToTrash();
+                                    sendOK("DELETE");
+                                } catch (NumberFormatException e) {
+                                    sendERR("invalid message index");
+                                } catch (IndexOutOfBoundsException e) {
+                                    sendERR("invalid message index");
+                                }
+                            } else {
+                                sendERR("invalid message index");
+                            }
+                        } else if ("TOP".equalsIgnoreCase(command)) {
+                            int message = 0;
+                            try {
+                                message = Integer.valueOf(tokens.nextToken());
+                                int lines = Integer.valueOf(tokens.nextToken());
+                                ExchangeSession.Message m = messages.get(message - 1);
+                                sendOK("");
+                                DoubleDotOutputStream doubleDotOutputStream = new DoubleDotOutputStream(os);
+                                IOUtil.write(m.getRawInputStream(), new TopOutputStream(doubleDotOutputStream, lines));
+                                doubleDotOutputStream.close();
+                            } catch (NumberFormatException e) {
+                                sendERR("invalid command");
+                            } catch (IndexOutOfBoundsException e) {
+                                sendERR("invalid message index: " + message);
+                            } catch (Exception e) {
+                                sendERR("error retreiving top of messages");
+                                DavGatewayTray.error(e);
+                            }
+                        } else if ("RSET".equalsIgnoreCase(command)) {
+                            sendOK("RSET");
+                        } else {
+                            sendERR("unknown command");
+                        }
+                    }
+
+                } else {
+                    sendERR("unknown command");
+                }
+
+                os.flush();
+            }
+        } catch (SocketException e) {
+            DavGatewayTray.debug(new BundleMessage("LOG_CONNECTION_CLOSED"));
+        } catch (Exception e) {
+            DavGatewayTray.log(e);
+            try {
+                sendERR(e.getMessage());
+            } catch (IOException e2) {
+                DavGatewayTray.debug(new BundleMessage("LOG_EXCEPTION_SENDING_ERROR_TO_CLIENT"), e2);
+            }
+        } finally {
+            close();
+        }
+        DavGatewayTray.resetIcon();
+    }
+
+    protected void sendOK(String message) throws IOException {
+        sendClient("+OK ", message);
+    }
+
+    protected void sendERR(Exception e) throws IOException {
+        String message = e.getMessage();
+        if (message == null) {
+            message = e.toString();
+        }
+        sendERR(message);
+    }
+
+    protected void sendERR(String message) throws IOException {
+        sendClient("-ERR ", message.replaceAll("\\n", " "));
+    }
+
+    /**
+     * Filter to limit output lines to max body lines after header
+     */
+    private static final class TopOutputStream extends FilterOutputStream {
+        private static final int START = 0;
+        private static final int CR = 1;
+        private static final int CRLF = 2;
+        private static final int CRLFCR = 3;
+        private static final int BODY = 4;
+
+        private int maxLines;
+        private int state = START;
+
+        private TopOutputStream(OutputStream os, int maxLines) {
+            super(os);
+            this.maxLines = maxLines;
+        }
+
+        @Override
+        public void write(int b) throws IOException {
+            if (state != BODY || maxLines > 0) {
+                super.write(b);
+            }
+            if (state == BODY) {
+                if (b == '\n') {
+                    maxLines--;
+                }
+            } else if (state == START) {
+                if (b == '\r') {
+                    state = CR;
+                }
+            } else if (state == CR) {
+                if (b == '\n') {
+                    state = CRLF;
+                } else {
+                    state = START;
+                }
+            } else if (state == CRLF) {
+                if (b == '\r') {
+                    state = CRLFCR;
+                } else {
+                    state = START;
+                }
+            } else if (state == CRLFCR) {
+                if (b == '\n') {
+                    state = BODY;
+                } else {
+                    state = START;
+                }
+            }
+        }
+    }
+
+}
diff --git a/src/java/davmail/pop/PopServer.java b/src/java/davmail/pop/PopServer.java
new file mode 100644
index 0000000..7fa9cdd
--- /dev/null
+++ b/src/java/davmail/pop/PopServer.java
@@ -0,0 +1,58 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.pop;
+
+
+import davmail.AbstractConnection;
+import davmail.AbstractServer;
+import davmail.Settings;
+
+import java.net.Socket;
+
+/**
+ * Pop3 server
+ */
+public class PopServer extends AbstractServer {
+    /**
+     * Default POP port
+     */
+    public static final int DEFAULT_PORT = 110;
+
+    /**
+     * Create a ServerSocket to listen for connections.
+     * Start the thread.
+     *
+     * @param port pop listen port, 110 if not defined (0)
+     */
+    public PopServer(int port) {
+        super(PopServer.class.getName(), port, PopServer.DEFAULT_PORT);
+        nosslFlag = Settings.getBooleanProperty("davmail.ssl.nosecurepop");
+    }
+
+    @Override
+    public String getProtocolName() {
+        return "POP";
+    }
+
+    @Override
+    public AbstractConnection createConnectionHandler(Socket clientSocket) {
+        return new PopConnection(clientSocket);
+    }
+
+}
diff --git a/src/java/davmail/service/DavService.java b/src/java/davmail/service/DavService.java
new file mode 100644
index 0000000..6cd64e9
--- /dev/null
+++ b/src/java/davmail/service/DavService.java
@@ -0,0 +1,71 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.service;
+
+import davmail.BundleMessage;
+import davmail.DavGateway;
+import davmail.Settings;
+import davmail.ui.tray.DavGatewayTray;
+import org.boris.winrun4j.AbstractService;
+import org.boris.winrun4j.ServiceException;
+
+/**
+ * WinRun4J DavMail service.
+ */
+public class DavService extends AbstractService {
+
+    /**
+     * Perform a service request.
+     *
+     * @param control service control.
+     * @return return code.
+     * @throws ServiceException on error.
+     */
+    @Override
+    public int serviceRequest(int control) throws ServiceException {
+        switch (control) {
+            case SERVICE_CONTROL_STOP:
+            case SERVICE_CONTROL_SHUTDOWN:
+                DavGatewayTray.debug(new BundleMessage("LOG_STOPPING_DAVMAIL"));
+                DavGateway.stop();
+        }
+        return 0;
+    }
+
+    /**
+     * Run the service.
+     *
+     * @param args command line arguments
+     * @return return code
+     * @throws ServiceException on error
+     */
+    public int serviceMain(String[] args) throws ServiceException {
+        if (args.length >= 1) {
+            Settings.setConfigFilePath(args[0]);
+        }
+
+        Settings.load();
+        DavGatewayTray.init();
+
+        DavGateway.start();
+        DavGatewayTray.debug(new BundleMessage("LOG_DAVMAIL_STARTED"));
+
+        return 0;
+    }
+}
diff --git a/src/java/davmail/smtp/SmtpConnection.java b/src/java/davmail/smtp/SmtpConnection.java
new file mode 100644
index 0000000..3d3fc4d
--- /dev/null
+++ b/src/java/davmail/smtp/SmtpConnection.java
@@ -0,0 +1,262 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.smtp;
+
+import davmail.AbstractConnection;
+import davmail.BundleMessage;
+import davmail.DavGateway;
+import davmail.exception.DavMailException;
+import davmail.exchange.DoubleDotInputStream;
+import davmail.exchange.ExchangeSessionFactory;
+import davmail.ui.tray.DavGatewayTray;
+
+import javax.mail.internet.AddressException;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+import javax.mail.util.SharedByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.Socket;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.StringTokenizer;
+
+/**
+ * Dav Gateway smtp connection implementation
+ */
+public class SmtpConnection extends AbstractConnection {
+
+    /**
+     * Initialize the streams and start the thread.
+     *
+     * @param clientSocket SMTP client socket
+     */
+    public SmtpConnection(Socket clientSocket) {
+        super(SmtpConnection.class.getSimpleName(), clientSocket, null);
+    }
+
+
+    @Override
+    public void run() {
+        String line;
+        StringTokenizer tokens;
+        List<String> recipients = new ArrayList<String>();
+
+        try {
+            ExchangeSessionFactory.checkConfig();
+            sendClient("220 DavMail " + DavGateway.getCurrentVersion() + " SMTP ready at " + new Date());
+            for (; ;) {
+                line = readClient();
+                // unable to read line, connection closed ?
+                if (line == null) {
+                    break;
+                }
+
+                tokens = new StringTokenizer(line);
+                if (tokens.hasMoreTokens()) {
+                    String command = tokens.nextToken();
+
+                    if (state == State.LOGIN) {
+                        // AUTH LOGIN, read userName
+                        userName = base64Decode(line);
+                        sendClient("334 " + base64Encode("Password:"));
+                        state = State.PASSWORD;
+                    } else if (state == State.PASSWORD) {
+                        // AUTH LOGIN, read password
+                        password = base64Decode(line);
+                        authenticate();
+                    } else if ("QUIT".equalsIgnoreCase(command)) {
+                        sendClient("221 Closing connection");
+                        break;
+                    } else if ("NOOP".equalsIgnoreCase(command)) {
+                        sendClient("250 OK");
+                    } else if ("EHLO".equalsIgnoreCase(command)) {
+                        sendClient("250-" + tokens.nextToken());
+                        // inform server that AUTH is supported
+                        // actually it is mandatory (only way to get credentials)
+                        sendClient("250-AUTH LOGIN PLAIN");
+                        sendClient("250-8BITMIME");
+                        sendClient("250 Hello");
+                    } else if ("HELO".equalsIgnoreCase(command)) {
+                        sendClient("250 Hello");
+                    } else if ("AUTH".equalsIgnoreCase(command)) {
+                        if (tokens.hasMoreElements()) {
+                            String authType = tokens.nextToken();
+                            if ("PLAIN".equalsIgnoreCase(authType) && tokens.hasMoreElements()) {
+                                decodeCredentials(tokens.nextToken());
+                                authenticate();
+                            } else if ("LOGIN".equalsIgnoreCase(authType)) {
+                                if (tokens.hasMoreTokens()) {
+                                    // user name sent on auth line
+                                    userName = base64Decode(tokens.nextToken());
+                                    sendClient("334 " + base64Encode("Password:"));
+                                    state = State.PASSWORD;
+                                } else {
+                                    sendClient("334 " + base64Encode("Username:"));
+                                    state = State.LOGIN;
+                                }
+                            } else {
+                                sendClient("451 Error : unknown authentication type");
+                            }
+                        } else {
+                            sendClient("451 Error : authentication type not specified");
+                        }
+                    } else if ("MAIL".equalsIgnoreCase(command)) {
+                        if (state == State.AUTHENTICATED) {
+                            state = State.STARTMAIL;
+                            recipients.clear();
+                            sendClient("250 Sender OK");
+                        } else if (state == State.INITIAL) {
+                            sendClient("503 Authentication required");
+                        } else {
+                            state = State.INITIAL;
+                            sendClient("503 Bad sequence of commands");
+                        }
+                    } else if ("RCPT".equalsIgnoreCase(command)) {
+                        if (state == State.STARTMAIL || state == State.RECIPIENT) {
+                            if (line.toUpperCase().startsWith("RCPT TO:")) {
+                                state = State.RECIPIENT;
+                                try {
+                                    InternetAddress internetAddress = new InternetAddress(line.substring("RCPT TO:".length()));
+                                    recipients.add(internetAddress.getAddress());
+                                } catch (AddressException e) {
+                                    throw new DavMailException("EXCEPTION_INVALID_RECIPIENT", line);
+                                }
+                                sendClient("250 Recipient OK");
+                            } else {
+                                sendClient("500 Unrecognized command");
+                            }
+
+                        } else {
+                            state = State.AUTHENTICATED;
+                            sendClient("503 Bad sequence of commands");
+                        }
+                    } else if ("DATA".equalsIgnoreCase(command)) {
+                        if (state == State.RECIPIENT) {
+                            state = State.MAILDATA;
+                            sendClient("354 Start mail input; end with <CRLF>.<CRLF>");
+
+                            try {
+                                // read message in buffer
+                                ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                                DoubleDotInputStream doubleDotInputStream = new DoubleDotInputStream(in);
+                                int b;
+                                while ((b = doubleDotInputStream.read()) >= 0) {
+                                    baos.write(b);
+                                }
+                                MimeMessage mimeMessage = new MimeMessage(null, new SharedByteArrayInputStream(baos.toByteArray()));
+                                session.sendMessage(recipients, mimeMessage);
+                                state = State.AUTHENTICATED;
+                                sendClient("250 Queued mail for delivery");
+                            } catch (Exception e) {
+                                DavGatewayTray.error(e);
+                                state = State.AUTHENTICATED;
+                                sendClient("451 Error : " + e + ' ' + e.getMessage());
+                            }
+
+                        } else {
+                            state = State.AUTHENTICATED;
+                            sendClient("503 Bad sequence of commands");
+                        }
+                    } else if ("RSET".equalsIgnoreCase(command)) {
+                        recipients.clear();
+
+                        if (state == State.STARTMAIL ||
+                                state == State.RECIPIENT ||
+                                state == State.MAILDATA ||
+                                state == State.AUTHENTICATED) {
+                            state = State.AUTHENTICATED;
+                        } else {
+                            state = State.INITIAL;
+                        }
+                        sendClient("250 OK Reset");
+                    } else {
+                        sendClient("500 Unrecognized command");
+                    }
+                } else {
+                    sendClient("500 Unrecognized command");
+                }
+
+                os.flush();
+            }
+
+        } catch (SocketException e) {
+            DavGatewayTray.debug(new BundleMessage("LOG_CONNECTION_CLOSED"));
+        } catch (Exception e) {
+            DavGatewayTray.log(e);
+            try {
+                sendClient("500 " + ((e.getMessage() == null) ? e : e.getMessage()));
+            } catch (IOException e2) {
+                DavGatewayTray.debug(new BundleMessage("LOG_EXCEPTION_SENDING_ERROR_TO_CLIENT"), e2);
+            }
+        } finally {
+            close();
+        }
+        DavGatewayTray.resetIcon();
+    }
+
+    /**
+     * Create authenticated session with Exchange server
+     *
+     * @throws IOException on error
+     */
+    protected void authenticate() throws IOException {
+        try {
+            session = ExchangeSessionFactory.getInstance(userName, password);
+            sendClient("235 OK Authenticated");
+            state = State.AUTHENTICATED;
+        } catch (Exception e) {
+            DavGatewayTray.error(e);
+            String message = e.getMessage();
+            if (message == null) {
+                message = e.toString();
+            }
+            message = message.replaceAll("\\n", " ");
+            sendClient("554 Authenticated failed " + message);
+            state = State.INITIAL;
+        }
+
+    }
+
+    /**
+     * Decode SMTP credentials
+     *
+     * @param encodedCredentials smtp encoded credentials
+     * @throws IOException if invalid credentials
+     */
+    protected void decodeCredentials(String encodedCredentials) throws IOException {
+        String decodedCredentials = base64Decode(encodedCredentials);
+        int startIndex = decodedCredentials.indexOf((char) 0);
+        if (startIndex >=0) {
+            int endIndex = decodedCredentials.indexOf((char) 0, startIndex+1);
+            if (endIndex >=0) {
+                userName = decodedCredentials.substring(startIndex+1, endIndex);
+                password = decodedCredentials.substring(endIndex + 1);
+            } else {
+                throw new DavMailException("EXCEPTION_INVALID_CREDENTIALS");
+            }
+        } else {
+            throw new DavMailException("EXCEPTION_INVALID_CREDENTIALS");
+        }
+    }
+
+}
+
diff --git a/src/java/davmail/smtp/SmtpServer.java b/src/java/davmail/smtp/SmtpServer.java
new file mode 100644
index 0000000..1629d80
--- /dev/null
+++ b/src/java/davmail/smtp/SmtpServer.java
@@ -0,0 +1,57 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.smtp;
+
+import davmail.AbstractConnection;
+import davmail.AbstractServer;
+import davmail.Settings;
+
+import java.net.Socket;
+
+/**
+ * SMTP server, handle message send requests.
+ */
+public class SmtpServer extends AbstractServer {
+    /**
+     * Default SMTP Caldav port
+     */
+    public static final int DEFAULT_PORT = 25;
+
+    /**
+     * Create a ServerSocket to listen for connections.
+     * Start the thread.
+     *
+     * @param port smtp port
+     */
+    public SmtpServer(int port) {
+        super(SmtpServer.class.getName(), port, SmtpServer.DEFAULT_PORT);
+        nosslFlag = Settings.getBooleanProperty("davmail.ssl.nosecuresmtp");
+    }
+
+    @Override
+    public String getProtocolName() {
+        return "SMTP";
+    }
+
+    @Override
+    public AbstractConnection createConnectionHandler(Socket clientSocket) {
+        return new SmtpConnection(clientSocket);
+    }
+
+}
diff --git a/src/java/davmail/ui/AboutFrame.java b/src/java/davmail/ui/AboutFrame.java
new file mode 100644
index 0000000..ce01d4f
--- /dev/null
+++ b/src/java/davmail/ui/AboutFrame.java
@@ -0,0 +1,144 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.ui;
+
+import davmail.BundleMessage;
+import davmail.DavGateway;
+import davmail.ui.browser.DesktopBrowser;
+import davmail.ui.tray.DavGatewayTray;
+
+import javax.imageio.ImageIO;
+import javax.swing.*;
+import javax.swing.event.HyperlinkEvent;
+import javax.swing.event.HyperlinkListener;
+import javax.swing.text.html.HTMLEditorKit;
+import javax.swing.text.html.StyleSheet;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+/**
+ * About frame
+ */
+public class AboutFrame extends JFrame {
+    private final JEditorPane jEditorPane;
+
+    /**
+     * About frame.
+     */
+    public AboutFrame() {
+        setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
+        setTitle(BundleMessage.format("UI_ABOUT_DAVMAIL"));
+        try {
+            setIconImage(DavGatewayTray.getFrameIcon());
+        } catch (NoSuchMethodError error) {
+            DavGatewayTray.debug(new BundleMessage("LOG_UNABLE_TO_SET_ICON_IMAGE"));
+        }
+        try {
+            JLabel imageLabel = new JLabel();
+            ClassLoader classloader = this.getClass().getClassLoader();
+            URL imageUrl = classloader.getResource("tray32.png");
+            Image iconImage = ImageIO.read(imageUrl);
+            ImageIcon icon = new ImageIcon(iconImage);
+            imageLabel.setIcon(icon);
+            JPanel imagePanel = new JPanel();
+            imagePanel.add(imageLabel);
+            add(BorderLayout.WEST, imagePanel);
+        } catch (IOException e) {
+            DavGatewayTray.error(new BundleMessage("LOG_UNABLE_TO_CREATE_ICON"), e);
+        }
+
+        jEditorPane = new JEditorPane();
+        HTMLEditorKit htmlEditorKit = new HTMLEditorKit();
+        StyleSheet stylesheet = htmlEditorKit.getStyleSheet();
+        stylesheet.addRule("body { font-size:small;font-family: " + jEditorPane.getFont().getFamily() + '}');
+        jEditorPane.setEditorKit(htmlEditorKit);
+        jEditorPane.setContentType("text/html");
+        jEditorPane.setText(getContent(null));
+
+        jEditorPane.setEditable(false);
+        jEditorPane.setOpaque(false);
+        jEditorPane.addHyperlinkListener(new HyperlinkListener() {
+            public void hyperlinkUpdate(HyperlinkEvent hle) {
+                if (HyperlinkEvent.EventType.ACTIVATED.equals(hle.getEventType())) {
+                    try {
+                        DesktopBrowser.browse(hle.getURL().toURI());
+                    } catch (URISyntaxException e) {
+                        DavGatewayTray.error(new BundleMessage("LOG_UNABLE_TO_OPEN_LINK"), e);
+                    }
+                    setVisible(false);
+                }
+            }
+        });
+
+
+        JPanel mainPanel = new JPanel();
+        mainPanel.add(jEditorPane);
+        add(BorderLayout.CENTER, mainPanel);
+
+        JPanel buttonPanel = new JPanel();
+        JButton ok = new JButton(BundleMessage.format("UI_BUTTON_OK"));
+        ActionListener close = new ActionListener() {
+            public void actionPerformed(ActionEvent evt) {
+                setVisible(false);
+            }
+        };
+        ok.addActionListener(close);
+
+        buttonPanel.add(ok);
+
+        add(BorderLayout.SOUTH, buttonPanel);
+
+        pack();
+        setResizable(false);
+        // center frame
+        setLocation(getToolkit().getScreenSize().width / 2 -
+                getSize().width / 2,
+                getToolkit().getScreenSize().height / 2 -
+                        getSize().height / 2);
+    }
+
+    String getContent(String releasedVersion) {
+        Package davmailPackage = DavGateway.class.getPackage();
+        StringBuilder buffer = new StringBuilder();
+        buffer.append(BundleMessage.format("UI_ABOUT_DAVMAIL_AUTHOR"));
+        String currentVersion = davmailPackage.getImplementationVersion();
+        if (currentVersion != null) {
+            buffer.append(BundleMessage.format("UI_CURRENT_VERSION", currentVersion));
+        }
+        if (currentVersion != null && releasedVersion != null && currentVersion.compareTo(releasedVersion) < 0) {
+            buffer.append(BundleMessage.format("UI_LATEST_VERSION", releasedVersion));
+        }
+        buffer.append(BundleMessage.format("UI_HELP_INSTRUCTIONS"));
+        return buffer.toString();
+    }
+
+
+    /**
+     * Update about frame content with current released version.
+     */
+    public void update() {
+        jEditorPane.setText(getContent(DavGateway.getReleasedVersion()));
+        pack();
+    }
+
+}
diff --git a/src/java/davmail/ui/AcceptCertificateDialog.java b/src/java/davmail/ui/AcceptCertificateDialog.java
new file mode 100644
index 0000000..c33484c
--- /dev/null
+++ b/src/java/davmail/ui/AcceptCertificateDialog.java
@@ -0,0 +1,169 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.ui;
+
+import davmail.BundleMessage;
+import davmail.http.DavGatewayX509TrustManager;
+import davmail.ui.tray.DavGatewayTray;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.lang.reflect.InvocationTargetException;
+import java.security.cert.X509Certificate;
+import java.text.DateFormat;
+import java.util.Date;
+
+/**
+ * Accept certificate dialog
+ */
+public class AcceptCertificateDialog extends JDialog {
+    protected boolean accepted;
+
+    /**
+     * Accept status.
+     *
+     * @return true if user accepted certificate
+     */
+    public boolean isAccepted() {
+        return accepted;
+    }
+
+    /**
+     * Add a new JLabel to panel with <b>label</b>: value text.
+     *
+     * @param panel certificate details panel
+     * @param label certificate attribute label
+     * @param value certificate attribute value
+     */
+    protected void addFieldValue(JPanel panel, String label, String value) {
+        JPanel fieldPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+        StringBuilder buffer = new StringBuilder();
+        buffer.append("<html><b>");
+        buffer.append(label);
+        buffer.append(":</b></html>");
+        fieldPanel.add(new JLabel(buffer.toString()));
+        fieldPanel.add(new JLabel(value));
+        panel.add(fieldPanel);
+    }
+
+    /**
+     * Accept certificate dialog.
+     *
+     * @param certificate certificate sent by server
+     */
+    public AcceptCertificateDialog(X509Certificate certificate) {
+        setAlwaysOnTop(true);
+        String sha1Hash = DavGatewayX509TrustManager.getFormattedHash(certificate);
+        DateFormat formatter = DateFormat.getDateInstance(DateFormat.MEDIUM);
+
+        setTitle(BundleMessage.format("UI_ACCEPT_CERTIFICATE"));
+        try {
+            setIconImage(DavGatewayTray.getFrameIcon());
+        } catch (NoSuchMethodError error) {
+            DavGatewayTray.debug(new BundleMessage("LOG_UNABLE_TO_SET_ICON_IMAGE"));
+        }
+
+        JPanel subjectPanel = new JPanel();
+        subjectPanel.setLayout(new BoxLayout(subjectPanel, BoxLayout.Y_AXIS));
+        subjectPanel.setBorder(BorderFactory.createTitledBorder(BundleMessage.format("UI_SERVER_CERTIFICATE")));
+        addFieldValue(subjectPanel, BundleMessage.format("UI_ISSUED_TO"), DavGatewayX509TrustManager.getRDN(certificate.getSubjectDN()));
+        addFieldValue(subjectPanel, BundleMessage.format("UI_ISSUED_BY"), DavGatewayX509TrustManager.getRDN(certificate.getIssuerDN()));
+        Date now = new Date();
+        String notBefore = formatter.format(certificate.getNotBefore());
+        if (now.before(certificate.getNotBefore())) {
+            notBefore = "<html><font color=\"#FF0000\">" + notBefore + "</font></html>";
+        }
+        addFieldValue(subjectPanel, BundleMessage.format("UI_VALID_FROM"), notBefore);
+        String notAfter = formatter.format(certificate.getNotAfter());
+        if (now.after(certificate.getNotAfter())) {
+            notAfter = "<html><font color=\"#FF0000\">" + notAfter + "</font></html>";
+        }
+        addFieldValue(subjectPanel, BundleMessage.format("UI_VALID_UNTIL"), notAfter);
+        addFieldValue(subjectPanel, BundleMessage.format("UI_SERIAL"), DavGatewayX509TrustManager.getFormattedSerial(certificate));
+        addFieldValue(subjectPanel, BundleMessage.format("UI_FINGERPRINT"), sha1Hash);
+
+        JPanel warningPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+        JLabel imageLabel = new JLabel();
+        imageLabel.setIcon(UIManager.getIcon("OptionPane.warningIcon"));
+        imageLabel.setText(BundleMessage.format("UI_UNTRUSTED_CERTIFICATE_HTML"));
+        warningPanel.add(imageLabel);
+        add(warningPanel, BorderLayout.NORTH);
+        add(subjectPanel, BorderLayout.CENTER);
+        add(getButtonPanel(), BorderLayout.SOUTH);
+        setModal(true);
+
+        pack();
+        // center frame
+        setLocation(getToolkit().getScreenSize().width / 2 -
+                getSize().width / 2,
+                getToolkit().getScreenSize().height / 2 -
+                        getSize().height / 2);
+	setAlwaysOnTop(true);
+        setVisible(true);
+    }
+
+    protected JPanel getButtonPanel() {
+        JPanel buttonPanel = new JPanel();
+        JButton accept = new JButton(BundleMessage.format("UI_BUTTON_ACCEPT"));
+        JButton deny = new JButton(BundleMessage.format("UI_BUTTON_DENY"));
+        accept.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent evt) {
+                accepted = true;
+                setVisible(false);
+            }
+        });
+        deny.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent evt) {
+                accepted = false;
+                setVisible(false);
+            }
+        });
+
+        buttonPanel.add(accept);
+        buttonPanel.add(deny);
+        return buttonPanel;
+    }
+
+
+    /**
+     * Display certificate accept dialog and get user answer.
+     *
+     * @param certificate certificate sent by server
+     * @return true if user accepted certificate
+     */
+    public static boolean isCertificateTrusted(final X509Certificate certificate) {
+        final boolean[] answer = new boolean[1];
+        try {
+            SwingUtilities.invokeAndWait(new Runnable() {
+                public void run() {
+                    AcceptCertificateDialog certificateDialog = new AcceptCertificateDialog(certificate);
+                    answer[0] = certificateDialog.isAccepted();
+                }
+            });
+        } catch (InterruptedException ie) {
+            DavGatewayTray.error(new BundleMessage("UI_ERROR_WAITING_FOR_CERTIFICATE_CHECK"), ie);
+        } catch (InvocationTargetException ite) {
+            DavGatewayTray.error(new BundleMessage("UI_ERROR_WAITING_FOR_CERTIFICATE_CHECK"), ite);
+        }
+
+        return answer[0];
+    }
+}
diff --git a/src/java/davmail/ui/NotificationDialog.java b/src/java/davmail/ui/NotificationDialog.java
new file mode 100644
index 0000000..fb3d38e
--- /dev/null
+++ b/src/java/davmail/ui/NotificationDialog.java
@@ -0,0 +1,215 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.ui;
+
+import davmail.BundleMessage;
+import davmail.ui.browser.DesktopBrowser;
+import davmail.ui.tray.DavGatewayTray;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+/**
+ * Edit Caldav scheduling notifications.
+ */
+public class NotificationDialog extends JDialog {
+
+    protected boolean sendNotification;
+
+    protected JTextField toField;
+    protected JTextField ccField;
+    protected JTextField subjectField;
+    protected JEditorPane bodyField;
+
+    protected void addRecipientComponent(JPanel panel, String label, JTextField textField, String toolTipText) {
+        JLabel fieldLabel = new JLabel(label);
+        fieldLabel.setHorizontalAlignment(SwingConstants.RIGHT);
+        fieldLabel.setVerticalAlignment(SwingConstants.CENTER);
+        textField.setMaximumSize(textField.getPreferredSize());
+        JPanel innerPanel = new JPanel();
+        innerPanel.setLayout(new BoxLayout(innerPanel, BoxLayout.X_AXIS));
+        innerPanel.setAlignmentX(Component.RIGHT_ALIGNMENT);
+        innerPanel.add(fieldLabel);
+        innerPanel.add(textField);
+        panel.add(innerPanel);
+        if (toolTipText != null) {
+            fieldLabel.setToolTipText(toolTipText);
+            textField.setToolTipText(toolTipText);
+        }
+    }
+
+    /**
+     * Notification dialog to let user edit message body or cancel notification.
+     *
+     * @param to      main recipients
+     * @param cc      copy recipients
+     * @param subject notification subject
+     */
+    public NotificationDialog(String to, String cc, String subject, String description) {
+        setModal(true);
+        setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
+        setTitle(BundleMessage.format("UI_CALDAV_NOTIFICATION"));
+        try {
+            setIconImage(DavGatewayTray.getFrameIcon());
+        } catch (NoSuchMethodError error) {
+            DavGatewayTray.debug(new BundleMessage("LOG_UNABLE_TO_SET_ICON_IMAGE"));
+        }
+
+        JPanel mainPanel = new JPanel();
+        // add help (F1 handler)
+        mainPanel.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke("F1"),
+                "help");
+        mainPanel.getActionMap().put("help", new AbstractAction() {
+            public void actionPerformed(ActionEvent e) {
+                DesktopBrowser.browse("http://davmail.sourceforge.net");
+            }
+        });
+
+        mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
+        mainPanel.add(getRecipientsPanel());
+        mainPanel.add(getBodyPanel(description));
+
+        JPanel recipientsPanel = getRecipientsPanel();
+        if (to != null) {
+            toField.setText(to);
+        }
+        if (cc != null) {
+            ccField.setText(cc);
+        }
+        if (subject != null) {
+            subjectField.setText(subject);
+        }
+        add(BorderLayout.NORTH, recipientsPanel);
+        JPanel bodyPanel = getBodyPanel(description);
+        add(BorderLayout.CENTER, bodyPanel);
+        bodyField.setPreferredSize(recipientsPanel.getPreferredSize());
+
+        JPanel buttonPanel = new JPanel();
+        JButton cancel = new JButton(BundleMessage.format("UI_BUTTON_CANCEL"));
+        JButton send = new JButton(BundleMessage.format("UI_BUTTON_SEND"));
+
+        send.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent evt) {
+                sendNotification = true;
+                setVisible(false);
+            }
+        });
+
+        cancel.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent evt) {
+                // nothing to do, just hide
+                setVisible(false);
+            }
+        });
+
+        buttonPanel.add(send);
+        buttonPanel.add(cancel);
+
+        add(BorderLayout.SOUTH, buttonPanel);
+
+        pack();
+        setResizable(true);
+        // center frame
+        setLocation(getToolkit().getScreenSize().width / 2 -
+                getSize().width / 2,
+                getToolkit().getScreenSize().height / 2 -
+                        getSize().height / 2);
+	setAlwaysOnTop(true);
+        setVisible(true);
+    }
+
+    protected JPanel getRecipientsPanel() {
+        JPanel recipientsPanel = new JPanel();
+        recipientsPanel.setLayout(new BoxLayout(recipientsPanel, BoxLayout.Y_AXIS));
+        recipientsPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+
+        toField = new JTextField("", 40);
+        ccField = new JTextField("", 40);
+        subjectField = new JTextField("", 40);
+
+        addRecipientComponent(recipientsPanel, BundleMessage.format("UI_TO"), toField,
+                BundleMessage.format("UI_TO_HELP"));
+        addRecipientComponent(recipientsPanel, BundleMessage.format("UI_CC"), ccField,
+                BundleMessage.format("UI_CC"));
+        addRecipientComponent(recipientsPanel, BundleMessage.format("UI_SUBJECT"), subjectField,
+                BundleMessage.format("UI_SUBJECT_HELP"));
+        return recipientsPanel;
+    }
+
+    protected JPanel getBodyPanel(String description) {
+        JPanel bodyPanel = new JPanel();
+        bodyPanel.setBorder(BorderFactory.createTitledBorder(BundleMessage.format("UI_NOTIFICATION_BODY")));
+
+        bodyField = new JEditorPane();
+        bodyField.setText(description);
+        //HTMLEditorKit htmlEditorKit = new HTMLEditorKit();
+        //bodyField.setEditorKit(htmlEditorKit);
+        //bodyField.setContentType("text/html");
+
+        bodyPanel.add(new JScrollPane(bodyField));
+        return bodyPanel;
+    }
+
+    /**
+     * Cancel notification flag.
+     *
+     * @return false if user chose to cancel notification
+     */
+    public boolean getSendNotification() {
+        return sendNotification;
+    }
+
+    /**
+     * Get edited recipients.
+     *
+     * @return recipients string
+     */
+    public String getTo() {
+        return toField.getText();
+    }
+
+    /**
+     * Get edited copy recipients.
+     *
+     * @return copy recipients string
+     */
+    public String getCc() {
+        return ccField.getText();
+    }
+
+    /**
+     * Get edited subject.
+     *
+     * @return subject
+     */
+    public String getSubject() {
+        return subjectField.getText();
+    }
+
+    /**
+     * Get edited body.
+     *
+     * @return edited notification body
+     */
+    public String getBody() {
+        return bodyField.getText();
+    }
+}
diff --git a/src/java/davmail/ui/OSXAdapter.java b/src/java/davmail/ui/OSXAdapter.java
new file mode 100644
index 0000000..3b458b7
--- /dev/null
+++ b/src/java/davmail/ui/OSXAdapter.java
@@ -0,0 +1,231 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.ui;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+/**
+ * Reflection based MacOS handler
+ */
+public class OSXAdapter implements InvocationHandler {
+
+    protected final Object targetObject;
+    protected final Method targetMethod;
+    protected final String proxySignature;
+
+    static Object macOSXApplication;
+
+    /**
+     * Pass this method an Object and Method equipped to perform application shutdown logic.
+     * The method passed should return a boolean stating whether or not the quit should occur
+     *
+     * @param target      target object
+     * @param quitHandler quit method
+     * @throws InvocationTargetException on error
+     * @throws ClassNotFoundException    on error
+     * @throws NoSuchMethodException     on error
+     * @throws InstantiationException    on error
+     * @throws IllegalAccessException    on error
+     */
+    public static void setQuitHandler(Object target, Method quitHandler) throws InvocationTargetException, ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException {
+        setHandler(new OSXAdapter("handleQuit", target, quitHandler));
+    }
+
+    /**
+     * Pass this method an Object and Method equipped to display application info
+     * They will be called when the About menu item is selected from the application menu
+     *
+     * @param target       target object
+     * @param aboutHandler about method
+     * @throws InvocationTargetException on error
+     * @throws ClassNotFoundException    on error
+     * @throws NoSuchMethodException     on error
+     * @throws InstantiationException    on error
+     * @throws IllegalAccessException    on error
+     */
+    public static void setAboutHandler(Object target, Method aboutHandler) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException, InstantiationException {
+        boolean enableAboutMenu = (target != null && aboutHandler != null);
+        if (enableAboutMenu) {
+            setHandler(new OSXAdapter("handleAbout", target, aboutHandler));
+        }
+        Method enableAboutMethod = macOSXApplication.getClass().getDeclaredMethod("setEnabledAboutMenu", new Class[]{boolean.class});
+        enableAboutMethod.invoke(macOSXApplication, enableAboutMenu);
+    }
+
+    /**
+     * Pass this method an Object and a Method equipped to display application options.
+     * They will be called when the Preferences menu item is selected from the application menu
+     *
+     * @param target       target object
+     * @param prefsHandler preferences method
+     * @throws InvocationTargetException on error
+     * @throws ClassNotFoundException    on error
+     * @throws NoSuchMethodException     on error
+     * @throws InstantiationException    on error
+     * @throws IllegalAccessException    on error
+     */
+    public static void setPreferencesHandler(Object target, Method prefsHandler) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException, InstantiationException {
+        boolean enablePrefsMenu = (target != null && prefsHandler != null);
+        if (enablePrefsMenu) {
+            setHandler(new OSXAdapter("handlePreferences", target, prefsHandler));
+        }
+        Method enablePrefsMethod = macOSXApplication.getClass().getDeclaredMethod("setEnabledPreferencesMenu", new Class[]{boolean.class});
+        enablePrefsMethod.invoke(macOSXApplication, enablePrefsMenu);
+    }
+
+    /**
+     * Pass this method an Object and a Method equipped to handle document events from the Finder.
+     * Documents are registered with the Finder via the CFBundleDocumentTypes dictionary in the
+     * application bundle's Info.plist
+     *
+     * @param target      target object
+     * @param fileHandler file method
+     * @throws InvocationTargetException on error
+     * @throws ClassNotFoundException    on error
+     * @throws NoSuchMethodException     on error
+     * @throws InstantiationException    on error
+     * @throws IllegalAccessException    on error
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    public static void setFileHandler(Object target, Method fileHandler) throws InvocationTargetException, ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException {
+        setHandler(new OSXAdapter("handleOpenFile", target, fileHandler) {
+            // Override OSXAdapter.callTarget to send information on the
+            // file to be opened
+            @Override
+            public boolean callTarget(Object appleEvent) {
+                if (appleEvent != null) {
+                    try {
+                        Method getFilenameMethod = appleEvent.getClass().getDeclaredMethod("getFilename", (Class[]) null);
+                        String filename = (String) getFilenameMethod.invoke(appleEvent, (Object[]) null);
+                        this.targetMethod.invoke(this.targetObject, filename);
+                    } catch (Exception ex) {
+                        throw new RuntimeException(ex);
+                    }
+                }
+                return true;
+            }
+        });
+    }
+
+    /**
+     * setHandler creates a Proxy object from the passed OSXAdapter and adds it as an ApplicationListener.
+     *
+     * @param adapter OSX adapter
+     * @throws InvocationTargetException on error
+     * @throws ClassNotFoundException    on error
+     * @throws NoSuchMethodException     on error
+     * @throws InstantiationException    on error
+     * @throws IllegalAccessException    on error
+     */
+    public static void setHandler(OSXAdapter adapter) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
+        Class<?> applicationClass = Class.forName("com.apple.eawt.Application");
+        if (macOSXApplication == null) {
+            macOSXApplication = applicationClass.getConstructor((Class[]) null).newInstance((Object[]) null);
+        }
+        Class applicationListenerClass = Class.forName("com.apple.eawt.ApplicationListener");
+        Method addListenerMethod = applicationClass.getDeclaredMethod("addApplicationListener", new Class[]{applicationListenerClass});
+        // Create a proxy object around this handler that can be reflectively added as an Apple ApplicationListener
+        Object osxAdapterProxy = Proxy.newProxyInstance(OSXAdapter.class.getClassLoader(), new Class[]{applicationListenerClass}, adapter);
+        addListenerMethod.invoke(macOSXApplication, osxAdapterProxy);
+    }
+
+    /**
+     * Each OSXAdapter has the name of the EAWT method it intends to listen for (handleAbout, for example),
+     * the Object that will ultimately perform the task, and the Method to be called on that Object
+     *
+     * @param proxySignature proxy signature
+     * @param target         target object
+     * @param handler        handler method
+     */
+    protected OSXAdapter(String proxySignature, Object target, Method handler) {
+        this.proxySignature = proxySignature;
+        this.targetObject = target;
+        this.targetMethod = handler;
+    }
+
+    /**
+     * Override this method to perform any operations on the event
+     * that comes with the various callbacks.
+     * See setFileHandler above for an example
+     *
+     * @param appleEvent apple event object
+     * @return true on success
+     * @throws InvocationTargetException on error
+     * @throws IllegalAccessException    on error
+     */
+    public boolean callTarget(Object appleEvent) throws InvocationTargetException, IllegalAccessException {
+        Object result = targetMethod.invoke(targetObject, (Object[]) null);
+        return result == null || Boolean.valueOf(result.toString());
+    }
+
+    /**
+     * InvocationHandler implementation.
+     * This is the entry point for our proxy object; it is called every time an ApplicationListener method is invoked
+     *
+     * @param proxy  proxy object
+     * @param method handler method
+     * @param args   method arguments
+     * @return null
+     * @throws Throwable on error
+     */
+    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+        if (isCorrectMethod(method, args)) {
+            boolean handled = callTarget(args[0]);
+            setApplicationEventHandled(args[0], handled);
+        }
+        // All of the ApplicationListener methods are void; return null regardless of what happens
+        return null;
+    }
+
+    //
+    //
+
+    /**
+     * Compare the method that was called to the intended method when the OSXAdapter instance was created
+     * (e.g. handleAbout, handleQuit, handleOpenFile, etc.).
+     *
+     * @param method handler method
+     * @param args   method arguments
+     * @return true if method is correct
+     */
+    protected boolean isCorrectMethod(Method method, Object[] args) {
+        return (targetMethod != null && proxySignature.equals(method.getName()) && args.length == 1);
+    }
+
+    /**
+     * It is important to mark the ApplicationEvent as handled and cancel the default behavior.
+     * This method checks for a boolean result from the proxy method and sets the event accordingly
+     *
+     * @param event   event object
+     * @param handled true if event handled
+     * @throws NoSuchMethodException     on error
+     * @throws InvocationTargetException on error
+     * @throws IllegalAccessException    on error
+     */
+    protected void setApplicationEventHandled(Object event, boolean handled) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+        if (event != null) {
+            Method setHandledMethod = event.getClass().getDeclaredMethod("setHandled", new Class[]{boolean.class});
+            // If the target method returns a boolean, use that as a hint
+            setHandledMethod.invoke(event, handled);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/java/davmail/ui/OSXInfoPlist.java b/src/java/davmail/ui/OSXInfoPlist.java
new file mode 100644
index 0000000..932e28a
--- /dev/null
+++ b/src/java/davmail/ui/OSXInfoPlist.java
@@ -0,0 +1,85 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2012  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.ui;
+
+import davmail.util.IOUtil;
+import org.apache.log4j.Logger;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Handle OSX Info.plist file access
+ */
+public class OSXInfoPlist {
+    protected static final Logger LOGGER = Logger.getLogger(OSXInfoPlist.class);
+    protected static final String INFO_PLIST_PATH = "Contents/Info.plist";
+
+    protected static boolean isOSX() {
+        return System.getProperty("os.name").toLowerCase().startsWith("mac os x");
+    }
+
+    protected static String getInfoPlistContent() throws IOException {
+        FileInputStream fileInputStream = new FileInputStream(INFO_PLIST_PATH);
+        return new String(IOUtil.readFully(fileInputStream));
+    }
+
+    /**
+     * Test current LSUIElement (hide from dock) value
+     *
+     * @return true if application is hidden from dock
+     */
+    public static boolean isHideFromDock() {
+        boolean result = false;
+        try {
+            result = isOSX() && getInfoPlistContent().indexOf("<key>LSUIElement</key><string>1</string>") >= 0;
+        } catch (IOException e) {
+            LOGGER.warn("Unable to update Info.plist", e);
+        }
+        return result;
+    }
+
+    /**
+     * Update LSUIElement (hide from dock) value
+     *
+     * @param hideFromDock new hide from dock value
+     */
+    public static void setOSXHideFromDock(boolean hideFromDock) {
+        try {
+            if (isOSX()) {
+                boolean currentHideFromDock = isHideFromDock();
+                if (currentHideFromDock != hideFromDock) {
+                    String content = getInfoPlistContent();
+                    FileOutputStream fileOutputStream = new FileOutputStream(INFO_PLIST_PATH);
+                    try {
+                        fileOutputStream.write(content.replaceFirst(
+                                "<key>LSUIElement</key><string>" + (currentHideFromDock ? "1" : "0") + "</string>",
+                                "<key>LSUIElement</key><string>" + (hideFromDock ? "1" : "0") + "</string>").getBytes("UTF-8"));
+                    } finally {
+                        fileOutputStream.close();
+                    }
+                    fileOutputStream.close();
+                }
+            }
+        } catch (IOException e) {
+            LOGGER.warn("Unable to update Info.plist", e);
+        }
+    }
+}
diff --git a/src/java/davmail/ui/PasswordPromptDialog.java b/src/java/davmail/ui/PasswordPromptDialog.java
new file mode 100644
index 0000000..cb7cee3
--- /dev/null
+++ b/src/java/davmail/ui/PasswordPromptDialog.java
@@ -0,0 +1,138 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.ui;
+
+import davmail.BundleMessage;
+import davmail.ui.tray.DavGatewayTray;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+/**
+ * Get smartcard password
+ */
+public class PasswordPromptDialog extends JDialog {
+    final JPasswordField passwordField = new JPasswordField(20);
+    protected char[] password;
+
+    /**
+     * Get user password.
+     *
+     * @return user password as char array
+     */
+    public char[] getPassword() {
+        if (password != null) {
+            return password.clone();
+        } else {
+            return "".toCharArray();
+        }
+    }
+
+    /**
+     * Get smartcard password.
+     *
+     * @param prompt password prompt from PKCS11 module
+     */
+    public PasswordPromptDialog(String prompt) {
+        this(prompt, null);
+    }
+
+    /**
+     * Get smartcard password.
+     *
+     * @param prompt password prompt from PKCS11 module
+     * @param captchaImage ISA filter pinsafe image
+     */
+    public PasswordPromptDialog(String prompt, Image captchaImage) {
+        setAlwaysOnTop(true);
+
+        setTitle(BundleMessage.format("UI_PASSWORD_PROMPT"));
+        try {
+            //noinspection Since15
+            setIconImage(DavGatewayTray.getFrameIcon());
+        } catch (NoSuchMethodError error) {
+            DavGatewayTray.debug(new BundleMessage("LOG_UNABLE_TO_SET_ICON_IMAGE"));
+        }
+
+
+        JPanel questionPanel = new JPanel();
+        questionPanel.setLayout(new BoxLayout(questionPanel, BoxLayout.Y_AXIS));
+        JLabel imageLabel = new JLabel();
+        imageLabel.setIcon(UIManager.getIcon("OptionPane.questionIcon"));
+        imageLabel.setText(prompt);
+        questionPanel.add(imageLabel);
+
+        passwordField.setMaximumSize(passwordField.getPreferredSize());
+        passwordField.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                password = passwordField.getPassword();
+                setVisible(false);
+            }
+        });
+        JPanel passwordPanel = new JPanel();
+        passwordPanel.setLayout(new BoxLayout(passwordPanel, BoxLayout.Y_AXIS));
+        if (captchaImage != null) {
+            JLabel captchaLabel = new JLabel(new ImageIcon(captchaImage));
+            captchaLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
+            captchaLabel.setBorder(new EmptyBorder(10,10,10,10));
+            passwordPanel.add(captchaLabel);
+        }
+        passwordPanel.add(passwordField);
+
+        add(questionPanel, BorderLayout.NORTH);
+        add(passwordPanel, BorderLayout.CENTER);
+        add(getButtonPanel(), BorderLayout.SOUTH);
+        setModal(true);
+
+        pack();
+        // center frame
+        setLocation(getToolkit().getScreenSize().width / 2 -
+                getSize().width / 2,
+                getToolkit().getScreenSize().height / 2 -
+                        getSize().height / 2);
+	setAlwaysOnTop(true);
+        setVisible(true);
+    }
+
+    protected JPanel getButtonPanel() {
+        JPanel buttonPanel = new JPanel();
+        JButton okButton = new JButton(BundleMessage.format("UI_BUTTON_OK"));
+        JButton cancelButton = new JButton(BundleMessage.format("UI_BUTTON_CANCEL"));
+        okButton.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent evt) {
+                password = passwordField.getPassword();
+                setVisible(false);
+            }
+        });
+        cancelButton.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent evt) {
+                password = null;
+                setVisible(false);
+            }
+        });
+
+        buttonPanel.add(okButton);
+        buttonPanel.add(cancelButton);
+        return buttonPanel;
+    }
+
+}
diff --git a/src/java/davmail/ui/SelectCertificateDialog.java b/src/java/davmail/ui/SelectCertificateDialog.java
new file mode 100644
index 0000000..d20bb0b
--- /dev/null
+++ b/src/java/davmail/ui/SelectCertificateDialog.java
@@ -0,0 +1,111 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2011  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.ui;
+
+import davmail.BundleMessage;
+import davmail.ui.tray.DavGatewayTray;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+/**
+ * Let user select a client certificate
+ */
+public class SelectCertificateDialog extends JDialog {
+    protected final JList aliasListBox;
+    protected String selectedAlias;
+
+    /**
+     * Gets user selected alias.
+     *
+     * @return user selected alias
+     */
+    public String getSelectedAlias() {
+        return this.selectedAlias;
+    }
+
+    /**
+     * Select a client certificate
+     *
+     * @param aliases An array of certificate aliases for the user to pick from
+     */
+    public SelectCertificateDialog(String[] aliases) {
+        setAlwaysOnTop(true);
+
+        setTitle(BundleMessage.format("UI_CERTIFICATE_ALIAS_PROMPT"));
+        try {
+            setIconImage(DavGatewayTray.getFrameIcon());
+        } catch (NoSuchMethodError error) {
+            DavGatewayTray.debug(new BundleMessage("LOG_UNABLE_TO_SET_ICON_IMAGE"));
+        }
+
+        JPanel questionPanel = new JPanel();
+        questionPanel.setLayout(new BoxLayout(questionPanel, BoxLayout.Y_AXIS));
+        JLabel imageLabel = new JLabel();
+        imageLabel.setIcon(UIManager.getIcon("OptionPane.questionIcon"));
+        imageLabel.setText(BundleMessage.format("UI_CERTIFICATE_ALIAS_PROMPT"));
+        questionPanel.add(imageLabel);
+
+        aliasListBox = new JList(aliases);
+        aliasListBox.setMaximumSize(aliasListBox.getPreferredSize());
+
+        JPanel aliasPanel = new JPanel();
+        aliasPanel.setLayout(new BoxLayout(aliasPanel, BoxLayout.Y_AXIS));
+        aliasPanel.add(aliasListBox);
+
+        add(questionPanel, BorderLayout.NORTH);
+        add(aliasPanel, BorderLayout.CENTER);
+        add(getButtonPanel(), BorderLayout.SOUTH);
+        setModal(true);
+
+        pack();
+        // center frame
+        setLocation(getToolkit().getScreenSize().width / 2 -
+                getSize().width / 2,
+                getToolkit().getScreenSize().height / 2 -
+                        getSize().height / 2);
+	setAlwaysOnTop(true);
+        setVisible(true);
+    }
+
+    protected JPanel getButtonPanel() {
+        JPanel buttonPanel = new JPanel();
+        JButton okButton = new JButton(BundleMessage.format("UI_BUTTON_OK"));
+        JButton cancelButton = new JButton(BundleMessage.format("UI_BUTTON_CANCEL"));
+        okButton.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent evt) {
+                selectedAlias = aliasListBox.getSelectedValue().toString();
+                setVisible(false);
+            }
+        });
+        cancelButton.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent evt) {
+                selectedAlias = null;
+                setVisible(false);
+            }
+        });
+
+        buttonPanel.add(okButton);
+        buttonPanel.add(cancelButton);
+        return buttonPanel;
+    }
+
+}
diff --git a/src/java/davmail/ui/SettingsFrame.java b/src/java/davmail/ui/SettingsFrame.java
new file mode 100644
index 0000000..3ee4e63
--- /dev/null
+++ b/src/java/davmail/ui/SettingsFrame.java
@@ -0,0 +1,821 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.ui;
+
+import davmail.BundleMessage;
+import davmail.DavGateway;
+import davmail.Settings;
+import davmail.ui.browser.DesktopBrowser;
+import davmail.ui.tray.DavGatewayTray;
+import org.apache.log4j.Level;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+
+/**
+ * DavMail settings frame
+ */
+public class SettingsFrame extends JFrame {
+    static final Level[] LOG_LEVELS = {Level.OFF, Level.FATAL, Level.ERROR, Level.WARN, Level.INFO, Level.DEBUG, Level.ALL};
+
+    protected JTextField urlField;
+    protected JTextField popPortField;
+    protected JCheckBox popPortCheckBox;
+    protected JCheckBox popNoSSLCheckBox;
+    protected JTextField imapPortField;
+    protected JCheckBox imapPortCheckBox;
+    protected JCheckBox imapNoSSLCheckBox;
+    protected JTextField smtpPortField;
+    protected JCheckBox smtpPortCheckBox;
+    protected JCheckBox smtpNoSSLCheckBox;
+    protected JTextField caldavPortField;
+    protected JCheckBox caldavPortCheckBox;
+    protected JCheckBox caldavNoSSLCheckBox;
+    protected JTextField ldapPortField;
+    protected JCheckBox ldapPortCheckBox;
+    protected JCheckBox ldapNoSSLCheckBox;
+    protected JTextField keepDelayField;
+    protected JTextField sentKeepDelayField;
+    protected JTextField caldavPastDelayField;
+    protected JTextField imapIdleDelayField;
+
+    JCheckBox useSystemProxiesField;
+    JCheckBox enableProxyField;
+    JTextField httpProxyField;
+    JTextField httpProxyPortField;
+    JTextField httpProxyUserField;
+    JTextField httpProxyPasswordField;
+
+    JCheckBox allowRemoteField;
+    JTextField bindAddressField;
+    JTextField clientSoTimeoutField;
+    JTextField certHashField;
+    JCheckBox disableUpdateCheck;
+
+    JComboBox keystoreTypeCombo;
+    JTextField keystoreFileField;
+    JPasswordField keystorePassField;
+    JPasswordField keyPassField;
+
+    JComboBox clientKeystoreTypeCombo;
+    JTextField clientKeystoreFileField;
+    JPasswordField clientKeystorePassField;
+    JTextField pkcs11LibraryField;
+    JTextArea pkcs11ConfigField;
+
+    JComboBox rootLoggingLevelField;
+    JComboBox davmailLoggingLevelField;
+    JComboBox httpclientLoggingLevelField;
+    JComboBox wireLoggingLevelField;
+    JTextField logFilePathField;
+    JTextField logFileSizeField;
+
+    JCheckBox caldavEditNotificationsField;
+    JTextField caldavAlarmSoundField;
+    JCheckBox forceActiveSyncUpdateCheckBox;
+    JTextField defaultDomainField;
+    JCheckBox showStartupBannerCheckBox;
+    JCheckBox disableGuiNotificationsCheckBox;
+    JCheckBox imapAutoExpungeCheckBox;
+    JCheckBox popMarkReadOnRetrCheckBox;
+    JComboBox enableEwsComboBox;
+    JCheckBox smtpSaveInSentCheckBox;
+
+    JCheckBox osxHideFromDockCheckBox;
+
+    protected void addSettingComponent(JPanel panel, String label, JComponent component) {
+        addSettingComponent(panel, label, component, null);
+    }
+
+    protected void addSettingComponent(JPanel panel, String label, JComponent component, String toolTipText) {
+        JLabel fieldLabel = new JLabel(label);
+        fieldLabel.setHorizontalAlignment(SwingConstants.RIGHT);
+        fieldLabel.setVerticalAlignment(SwingConstants.CENTER);
+        panel.add(fieldLabel);
+        component.setMaximumSize(component.getPreferredSize());
+        JPanel innerPanel = new JPanel();
+        innerPanel.setLayout(new BoxLayout(innerPanel, BoxLayout.X_AXIS));
+        innerPanel.add(component);
+        panel.add(innerPanel);
+        if (toolTipText != null) {
+            fieldLabel.setToolTipText(toolTipText);
+            component.setToolTipText(toolTipText);
+        }
+    }
+
+    protected void addPortSettingComponent(JPanel panel, String label, JComponent component, JComponent checkboxComponent, JComponent checkboxSSLComponent, String toolTipText) {
+        JLabel fieldLabel = new JLabel(label);
+        fieldLabel.setHorizontalAlignment(SwingConstants.RIGHT);
+        fieldLabel.setVerticalAlignment(SwingConstants.CENTER);
+        panel.add(fieldLabel);
+        component.setMaximumSize(component.getPreferredSize());
+        JPanel innerPanel = new JPanel();
+        innerPanel.setLayout(new BoxLayout(innerPanel, BoxLayout.X_AXIS));
+        innerPanel.add(checkboxComponent);
+        innerPanel.add(component);
+        innerPanel.add(checkboxSSLComponent);
+        panel.add(innerPanel);
+        if (toolTipText != null) {
+            fieldLabel.setToolTipText(toolTipText);
+            component.setToolTipText(toolTipText);
+        }
+    }
+
+    protected JPanel getSettingsPanel() {
+        JPanel settingsPanel = new JPanel(new GridLayout(6, 2));
+        settingsPanel.setBorder(BorderFactory.createTitledBorder(BundleMessage.format("UI_GATEWAY")));
+
+        urlField = new JTextField(Settings.getProperty("davmail.url"), 17);
+        popPortField = new JTextField(Settings.getProperty("davmail.popPort"), 4);
+        popPortCheckBox = new JCheckBox();
+        popNoSSLCheckBox = new JCheckBox(BundleMessage.format("UI_NO_SSL"), Settings.getBooleanProperty("davmail.ssl.nosecurepop"));
+        popPortCheckBox.setSelected(Settings.getProperty("davmail.popPort") != null && Settings.getProperty("davmail.popPort").length() > 0);
+        popPortField.setEnabled(popPortCheckBox.isSelected());
+        popNoSSLCheckBox.setEnabled(popPortCheckBox.isSelected() && isSslEnabled());
+        popPortCheckBox.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent evt) {
+                popPortField.setEnabled(popPortCheckBox.isSelected());
+                popNoSSLCheckBox.setEnabled(popPortCheckBox.isSelected() && isSslEnabled());
+            }
+        });
+
+        imapPortField = new JTextField(Settings.getProperty("davmail.imapPort"), 4);
+        imapPortCheckBox = new JCheckBox();
+        imapNoSSLCheckBox = new JCheckBox(BundleMessage.format("UI_NO_SSL"), Settings.getBooleanProperty("davmail.ssl.nosecureimap"));
+        imapPortCheckBox.setSelected(Settings.getProperty("davmail.imapPort") != null && Settings.getProperty("davmail.imapPort").length() > 0);
+        imapPortField.setEnabled(imapPortCheckBox.isSelected());
+        imapNoSSLCheckBox.setEnabled(imapPortCheckBox.isSelected() && isSslEnabled());
+        imapPortCheckBox.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent evt) {
+                imapPortField.setEnabled(imapPortCheckBox.isSelected());
+                imapNoSSLCheckBox.setEnabled(imapPortCheckBox.isSelected() && isSslEnabled());
+            }
+        });
+
+        smtpPortField = new JTextField(Settings.getProperty("davmail.smtpPort"), 4);
+        smtpPortCheckBox = new JCheckBox();
+        smtpNoSSLCheckBox = new JCheckBox(BundleMessage.format("UI_NO_SSL"), Settings.getBooleanProperty("davmail.ssl.nosecuresmtp"));
+        smtpPortCheckBox.setSelected(Settings.getProperty("davmail.smtpPort") != null && Settings.getProperty("davmail.smtpPort").length() > 0);
+        smtpPortField.setEnabled(smtpPortCheckBox.isSelected());
+        smtpNoSSLCheckBox.setEnabled(smtpPortCheckBox.isSelected() && isSslEnabled());
+        smtpPortCheckBox.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent evt) {
+                smtpPortField.setEnabled(smtpPortCheckBox.isSelected());
+                smtpNoSSLCheckBox.setEnabled(smtpPortCheckBox.isSelected() && isSslEnabled());
+            }
+        });
+
+        caldavPortField = new JTextField(Settings.getProperty("davmail.caldavPort"), 4);
+        caldavPortCheckBox = new JCheckBox();
+        caldavNoSSLCheckBox = new JCheckBox(BundleMessage.format("UI_NO_SSL"), Settings.getBooleanProperty("davmail.ssl.nosecurecaldav"));
+        caldavPortCheckBox.setSelected(Settings.getProperty("davmail.caldavPort") != null && Settings.getProperty("davmail.caldavPort").length() > 0);
+        caldavPortField.setEnabled(caldavPortCheckBox.isSelected());
+        caldavNoSSLCheckBox.setEnabled(caldavPortCheckBox.isSelected() && isSslEnabled());
+        caldavPortCheckBox.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent evt) {
+                caldavPortField.setEnabled(caldavPortCheckBox.isSelected());
+                caldavNoSSLCheckBox.setEnabled(caldavPortCheckBox.isSelected() && isSslEnabled());
+            }
+        });
+
+        ldapPortField = new JTextField(Settings.getProperty("davmail.ldapPort"), 4);
+        ldapPortCheckBox = new JCheckBox();
+        ldapNoSSLCheckBox = new JCheckBox(BundleMessage.format("UI_NO_SSL"), Settings.getBooleanProperty("davmail.ssl.nosecureldap"));
+        ldapPortCheckBox.setSelected(Settings.getProperty("davmail.ldapPort") != null && Settings.getProperty("davmail.ldapPort").length() > 0);
+        ldapPortField.setEnabled(ldapPortCheckBox.isSelected());
+        ldapNoSSLCheckBox.setEnabled(ldapPortCheckBox.isSelected() && isSslEnabled());
+        ldapPortCheckBox.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent evt) {
+                ldapPortField.setEnabled(ldapPortCheckBox.isSelected());
+                ldapNoSSLCheckBox.setEnabled(ldapPortCheckBox.isSelected() && isSslEnabled());
+            }
+        });
+
+        addSettingComponent(settingsPanel, BundleMessage.format("UI_OWA_URL"), urlField, BundleMessage.format("UI_OWA_URL_HELP"));
+        addPortSettingComponent(settingsPanel, BundleMessage.format("UI_POP_PORT"), popPortField, popPortCheckBox,
+                popNoSSLCheckBox, BundleMessage.format("UI_POP_PORT_HELP"));
+        addPortSettingComponent(settingsPanel, BundleMessage.format("UI_IMAP_PORT"), imapPortField, imapPortCheckBox,
+                imapNoSSLCheckBox, BundleMessage.format("UI_IMAP_PORT_HELP"));
+        addPortSettingComponent(settingsPanel, BundleMessage.format("UI_SMTP_PORT"), smtpPortField, smtpPortCheckBox,
+                smtpNoSSLCheckBox, BundleMessage.format("UI_SMTP_PORT_HELP"));
+        addPortSettingComponent(settingsPanel, BundleMessage.format("UI_CALDAV_PORT"), caldavPortField, caldavPortCheckBox,
+                caldavNoSSLCheckBox, BundleMessage.format("UI_CALDAV_PORT_HELP"));
+        addPortSettingComponent(settingsPanel, BundleMessage.format("UI_LDAP_PORT"), ldapPortField, ldapPortCheckBox,
+                ldapNoSSLCheckBox, BundleMessage.format("UI_LDAP_PORT_HELP"));
+        return settingsPanel;
+    }
+
+    protected JPanel getDelaysPanel() {
+        JPanel delaysPanel = new JPanel(new GridLayout(4, 2));
+        delaysPanel.setBorder(BorderFactory.createTitledBorder(BundleMessage.format("UI_DELAYS")));
+
+        keepDelayField = new JTextField(Settings.getProperty("davmail.keepDelay"), 4);
+        sentKeepDelayField = new JTextField(Settings.getProperty("davmail.sentKeepDelay"), 4);
+        caldavPastDelayField = new JTextField(Settings.getProperty("davmail.caldavPastDelay"), 4);
+        imapIdleDelayField = new JTextField(Settings.getProperty("davmail.imapIdleDelay"), 4);
+
+        addSettingComponent(delaysPanel, BundleMessage.format("UI_KEEP_DELAY"), keepDelayField,
+                BundleMessage.format("UI_KEEP_DELAY_HELP"));
+        addSettingComponent(delaysPanel, BundleMessage.format("UI_SENT_KEEP_DELAY"), sentKeepDelayField,
+                BundleMessage.format("UI_SENT_KEEP_DELAY_HELP"));
+        addSettingComponent(delaysPanel, BundleMessage.format("UI_CALENDAR_PAST_EVENTS"), caldavPastDelayField,
+                BundleMessage.format("UI_CALENDAR_PAST_EVENTS_HELP"));
+        addSettingComponent(delaysPanel, BundleMessage.format("UI_IMAP_IDLE_DELAY"), imapIdleDelayField,
+                BundleMessage.format("UI_IMAP_IDLE_DELAY_HELP"));
+        return delaysPanel;
+    }
+
+    protected JPanel getProxyPanel() {
+        JPanel proxyPanel = new JPanel(new GridLayout(6, 2));
+        proxyPanel.setBorder(BorderFactory.createTitledBorder(BundleMessage.format("UI_PROXY")));
+
+        boolean useSystemProxies = Settings.getBooleanProperty("davmail.useSystemProxies", Boolean.FALSE);
+        boolean enableProxy = Settings.getBooleanProperty("davmail.enableProxy");
+        useSystemProxiesField = new JCheckBox();
+        useSystemProxiesField.setSelected(useSystemProxies);
+        enableProxyField = new JCheckBox();
+        enableProxyField.setSelected(enableProxy);
+        httpProxyField = new JTextField(Settings.getProperty("davmail.proxyHost"), 15);
+        httpProxyPortField = new JTextField(Settings.getProperty("davmail.proxyPort"), 4);
+        httpProxyUserField = new JTextField(Settings.getProperty("davmail.proxyUser"), 10);
+        httpProxyPasswordField = new JPasswordField(Settings.getProperty("davmail.proxyPassword"), 10);
+
+        enableProxyField.setEnabled(!useSystemProxies);
+        httpProxyField.setEnabled(enableProxy);
+        httpProxyPortField.setEnabled(enableProxy);
+        httpProxyUserField.setEnabled(enableProxy || useSystemProxies);
+        httpProxyPasswordField.setEnabled(enableProxy || useSystemProxies);
+
+        useSystemProxiesField.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent evt) {
+                boolean newUseSystemProxies = useSystemProxiesField.isSelected();
+                boolean newEnableProxy = enableProxyField.isSelected();
+                enableProxyField.setEnabled(!newUseSystemProxies);
+                httpProxyField.setEnabled(!newUseSystemProxies && newEnableProxy);
+                httpProxyPortField.setEnabled(!newUseSystemProxies && newEnableProxy);
+                httpProxyUserField.setEnabled(newUseSystemProxies || newEnableProxy);
+                httpProxyPasswordField.setEnabled(newUseSystemProxies || newEnableProxy);
+            }
+        });
+        enableProxyField.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent evt) {
+                boolean newEnableProxy = enableProxyField.isSelected();
+                httpProxyField.setEnabled(newEnableProxy);
+                httpProxyPortField.setEnabled(newEnableProxy);
+                httpProxyUserField.setEnabled(newEnableProxy);
+                httpProxyPasswordField.setEnabled(newEnableProxy);
+            }
+        });
+
+        addSettingComponent(proxyPanel, BundleMessage.format("UI_USE_SYSTEM_PROXIES"), useSystemProxiesField);
+        addSettingComponent(proxyPanel, BundleMessage.format("UI_ENABLE_PROXY"), enableProxyField);
+        addSettingComponent(proxyPanel, BundleMessage.format("UI_PROXY_SERVER"), httpProxyField);
+        addSettingComponent(proxyPanel, BundleMessage.format("UI_PROXY_PORT"), httpProxyPortField);
+        addSettingComponent(proxyPanel, BundleMessage.format("UI_PROXY_USER"), httpProxyUserField);
+        addSettingComponent(proxyPanel, BundleMessage.format("UI_PROXY_PASSWORD"), httpProxyPasswordField);
+        updateMaximumSize(proxyPanel);
+        return proxyPanel;
+    }
+
+    protected JPanel getKeystorePanel() {
+        JPanel keyStorePanel = new JPanel(new GridLayout(4, 2));
+        keyStorePanel.setBorder(BorderFactory.createTitledBorder(BundleMessage.format("UI_DAVMAIL_SERVER_CERTIFICATE")));
+
+        keystoreTypeCombo = new JComboBox(new String[]{"JKS", "PKCS12"});
+        keystoreTypeCombo.setSelectedItem(Settings.getProperty("davmail.ssl.keystoreType"));
+        keystoreFileField = new JTextField(Settings.getProperty("davmail.ssl.keystoreFile"), 17);
+        keystorePassField = new JPasswordField(Settings.getProperty("davmail.ssl.keystorePass"), 15);
+        keyPassField = new JPasswordField(Settings.getProperty("davmail.ssl.keyPass"), 15);
+
+        addSettingComponent(keyStorePanel, BundleMessage.format("UI_KEY_STORE_TYPE"), keystoreTypeCombo,
+                BundleMessage.format("UI_KEY_STORE_TYPE_HELP"));
+        addSettingComponent(keyStorePanel, BundleMessage.format("UI_KEY_STORE"), keystoreFileField,
+                BundleMessage.format("UI_KEY_STORE_HELP"));
+        addSettingComponent(keyStorePanel, BundleMessage.format("UI_KEY_STORE_PASSWORD"), keystorePassField,
+                BundleMessage.format("UI_KEY_STORE_PASSWORD_HELP"));
+        addSettingComponent(keyStorePanel, BundleMessage.format("UI_KEY_PASSWORD"), keyPassField,
+                BundleMessage.format("UI_KEY_PASSWORD_HELP"));
+        updateMaximumSize(keyStorePanel);
+        return keyStorePanel;
+    }
+
+    protected JPanel getSmartCardPanel() {
+        JPanel clientKeystorePanel = new JPanel(new GridLayout(2, 1));
+        clientKeystorePanel.setLayout(new BoxLayout(clientKeystorePanel, BoxLayout.Y_AXIS));
+        clientKeystorePanel.setBorder(BorderFactory.createTitledBorder(BundleMessage.format("UI_CLIENT_CERTIFICATE")));
+
+        clientKeystoreTypeCombo = new JComboBox(new String[]{"PKCS11", "JKS", "PKCS12"});
+        clientKeystoreTypeCombo.setSelectedItem(Settings.getProperty("davmail.ssl.clientKeystoreType"));
+        clientKeystoreFileField = new JTextField(Settings.getProperty("davmail.ssl.clientKeystoreFile"), 17);
+        clientKeystorePassField = new JPasswordField(Settings.getProperty("davmail.ssl.clientKeystorePass"), 15);
+
+        pkcs11LibraryField = new JTextField(Settings.getProperty("davmail.ssl.pkcs11Library"), 17);
+        pkcs11ConfigField = new JTextArea(2, 17);
+        pkcs11ConfigField.setText(Settings.getProperty("davmail.ssl.pkcs11Config"));
+        pkcs11ConfigField.setBorder(pkcs11LibraryField.getBorder());
+        pkcs11ConfigField.setFont(pkcs11LibraryField.getFont());
+
+        JPanel clientKeystoreTypePanel = new JPanel(new GridLayout(1, 2));
+        addSettingComponent(clientKeystoreTypePanel, BundleMessage.format("UI_CLIENT_KEY_STORE_TYPE"), clientKeystoreTypeCombo,
+                BundleMessage.format("UI_CLIENT_KEY_STORE_TYPE_HELP"));
+        clientKeystorePanel.add(clientKeystoreTypePanel);
+
+        final JPanel cardPanel = new JPanel(new CardLayout());
+        clientKeystorePanel.add(cardPanel);
+
+        JPanel clientKeystoreFilePanel = new JPanel(new GridLayout(2, 2));
+        addSettingComponent(clientKeystoreFilePanel, BundleMessage.format("UI_CLIENT_KEY_STORE"), clientKeystoreFileField,
+                BundleMessage.format("UI_CLIENT_KEY_STORE_HELP"));
+        addSettingComponent(clientKeystoreFilePanel, BundleMessage.format("UI_CLIENT_KEY_STORE_PASSWORD"), clientKeystorePassField,
+                BundleMessage.format("UI_CLIENT_KEY_STORE_PASSWORD_HELP"));
+        cardPanel.add(clientKeystoreFilePanel, "FILE");
+
+        JPanel pkcs11Panel = new JPanel(new GridLayout(2, 2));
+        addSettingComponent(pkcs11Panel, BundleMessage.format("UI_PKCS11_LIBRARY"), pkcs11LibraryField,
+                BundleMessage.format("UI_PKCS11_LIBRARY_HELP"));
+        addSettingComponent(pkcs11Panel, BundleMessage.format("UI_PKCS11_CONFIG"), pkcs11ConfigField,
+                BundleMessage.format("UI_PKCS11_CONFIG_HELP"));
+        cardPanel.add(pkcs11Panel, "PKCS11");
+
+        ((CardLayout) cardPanel.getLayout()).show(cardPanel, (String) clientKeystoreTypeCombo.getSelectedItem());
+
+        clientKeystoreTypeCombo.addItemListener(new ItemListener() {
+            public void itemStateChanged(ItemEvent event) {
+                CardLayout cardLayout = (CardLayout) (cardPanel.getLayout());
+                if ("PKCS11".equals(event.getItem())) {
+                    cardLayout.show(cardPanel, "PKCS11");
+                } else {
+                    cardLayout.show(cardPanel, "FILE");
+                }
+            }
+        });
+        updateMaximumSize(clientKeystorePanel);
+        return clientKeystorePanel;
+    }
+
+    protected JPanel getNetworkSettingsPanel() {
+        JPanel networkSettingsPanel = new JPanel(new GridLayout(4, 2));
+        networkSettingsPanel.setBorder(BorderFactory.createTitledBorder(BundleMessage.format("UI_NETWORK")));
+
+        allowRemoteField = new JCheckBox();
+        allowRemoteField.setSelected(Settings.getBooleanProperty("davmail.allowRemote"));
+
+        bindAddressField = new JTextField(Settings.getProperty("davmail.bindAddress"), 15);
+        clientSoTimeoutField = new JTextField(Settings.getProperty("davmail.clientSoTimeout"), 15);
+
+        certHashField = new JTextField(Settings.getProperty("davmail.server.certificate.hash"), 15);
+
+        addSettingComponent(networkSettingsPanel, BundleMessage.format("UI_BIND_ADDRESS"), bindAddressField,
+                BundleMessage.format("UI_BIND_ADDRESS_HELP"));
+        addSettingComponent(networkSettingsPanel, BundleMessage.format("UI_CLIENT_SO_TIMEOUT"), clientSoTimeoutField,
+                BundleMessage.format("UI_CLIENT_SO_TIMEOUT_HELP"));
+        addSettingComponent(networkSettingsPanel, BundleMessage.format("UI_ALLOW_REMOTE_CONNECTION"), allowRemoteField,
+                BundleMessage.format("UI_ALLOW_REMOTE_CONNECTION_HELP"));
+        addSettingComponent(networkSettingsPanel, BundleMessage.format("UI_SERVER_CERTIFICATE_HASH"), certHashField,
+                BundleMessage.format("UI_SERVER_CERTIFICATE_HASH_HELP"));
+        updateMaximumSize(networkSettingsPanel);
+        return networkSettingsPanel;
+    }
+
+    protected static final String WEBDAV = "WebDav";
+    protected static final String EWS = "EWS";
+    protected static final String AUTO = "Auto";
+
+    protected void setEwsModeSelectedItem(String ewsMode) {
+        if ("true".equals(ewsMode)) {
+            enableEwsComboBox.setSelectedItem(EWS);
+        } else if ("false".equals(ewsMode)) {
+            enableEwsComboBox.setSelectedItem(WEBDAV);
+        } else {
+            enableEwsComboBox.setSelectedItem(AUTO);
+        }
+    }
+
+    protected JPanel getOtherSettingsPanel() {
+        JPanel otherSettingsPanel = new JPanel(new GridLayout(11, 2));
+        otherSettingsPanel.setBorder(BorderFactory.createTitledBorder(BundleMessage.format("UI_OTHER")));
+
+        enableEwsComboBox = new JComboBox(new String[]{WEBDAV, EWS, AUTO});
+        setEwsModeSelectedItem(Settings.getProperty("davmail.enableEws", "auto"));
+        caldavEditNotificationsField = new JCheckBox();
+        caldavEditNotificationsField.setSelected(Settings.getBooleanProperty("davmail.caldavEditNotifications"));
+        caldavAlarmSoundField = new JTextField(Settings.getProperty("davmail.caldavAlarmSound"), 15);
+        forceActiveSyncUpdateCheckBox = new JCheckBox();
+        forceActiveSyncUpdateCheckBox.setSelected(Settings.getBooleanProperty("davmail.forceActiveSyncUpdate"));
+        defaultDomainField = new JTextField(Settings.getProperty("davmail.defaultDomain"), 15);
+        showStartupBannerCheckBox = new JCheckBox();
+        showStartupBannerCheckBox.setSelected(Settings.getBooleanProperty("davmail.showStartupBanner", true));
+        disableGuiNotificationsCheckBox = new JCheckBox();
+        disableGuiNotificationsCheckBox.setSelected(Settings.getBooleanProperty("davmail.disableGuiNotifications", false));
+        imapAutoExpungeCheckBox = new JCheckBox();
+        imapAutoExpungeCheckBox.setSelected(Settings.getBooleanProperty("davmail.imapAutoExpunge", true));
+        popMarkReadOnRetrCheckBox = new JCheckBox();
+        popMarkReadOnRetrCheckBox.setSelected(Settings.getBooleanProperty("davmail.popMarkReadOnRetr", false));
+        smtpSaveInSentCheckBox = new JCheckBox();
+        smtpSaveInSentCheckBox.setSelected(Settings.getBooleanProperty("davmail.smtpSaveInSent", true));
+        disableUpdateCheck = new JCheckBox();
+        disableUpdateCheck.setSelected(Settings.getBooleanProperty("davmail.disableUpdateCheck"));
+
+        addSettingComponent(otherSettingsPanel, BundleMessage.format("UI_ENABLE_EWS"), enableEwsComboBox,
+                BundleMessage.format("UI_ENABLE_EWS_HELP"));
+        addSettingComponent(otherSettingsPanel, BundleMessage.format("UI_CALDAV_EDIT_NOTIFICATIONS"), caldavEditNotificationsField,
+                BundleMessage.format("UI_CALDAV_EDIT_NOTIFICATIONS_HELP"));
+        addSettingComponent(otherSettingsPanel, BundleMessage.format("UI_CALDAV_ALARM_SOUND"), caldavAlarmSoundField,
+                BundleMessage.format("UI_CALDAV_ALARM_SOUND_HELP"));
+        addSettingComponent(otherSettingsPanel, BundleMessage.format("UI_FORCE_ACTIVESYNC_UPDATE"), forceActiveSyncUpdateCheckBox,
+                BundleMessage.format("UI_FORCE_ACTIVESYNC_UPDATE_HELP"));
+        addSettingComponent(otherSettingsPanel, BundleMessage.format("UI_DEFAULT_DOMAIN"), defaultDomainField,
+                BundleMessage.format("UI_DEFAULT_DOMAIN_HELP"));
+        addSettingComponent(otherSettingsPanel, BundleMessage.format("UI_SHOW_STARTUP_BANNER"), showStartupBannerCheckBox,
+                BundleMessage.format("UI_SHOW_STARTUP_BANNER_HELP"));
+        addSettingComponent(otherSettingsPanel, BundleMessage.format("UI_DISABLE_GUI_NOTIFICATIONS"), disableGuiNotificationsCheckBox,
+                BundleMessage.format("UI_DISABLE_GUI_NOTIFICATIONS_HELP"));
+        addSettingComponent(otherSettingsPanel, BundleMessage.format("UI_IMAP_AUTO_EXPUNGE"), imapAutoExpungeCheckBox,
+                BundleMessage.format("UI_IMAP_AUTO_EXPUNGE_HELP"));
+        addSettingComponent(otherSettingsPanel, BundleMessage.format("UI_POP_MARK_READ"), popMarkReadOnRetrCheckBox,
+                BundleMessage.format("UI_POP_MARK_READ_HELP"));
+        addSettingComponent(otherSettingsPanel, BundleMessage.format("UI_SAVE_IN_SENT"), smtpSaveInSentCheckBox,
+                BundleMessage.format("UI_SAVE_IN_SENT_HELP"));
+        addSettingComponent(otherSettingsPanel, BundleMessage.format("UI_DISABLE_UPDATE_CHECK"), disableUpdateCheck,
+                BundleMessage.format("UI_DISABLE_UPDATE_CHECK_HELP"));
+
+        updateMaximumSize(otherSettingsPanel);
+        return otherSettingsPanel;
+    }
+
+    protected JPanel getOSXPanel() {
+        JPanel osxSettingsPanel = new JPanel(new GridLayout(1, 2));
+        osxSettingsPanel.setBorder(BorderFactory.createTitledBorder(BundleMessage.format("UI_OSX")));
+
+        osxHideFromDockCheckBox = new JCheckBox();
+        osxHideFromDockCheckBox.setSelected(OSXInfoPlist.isHideFromDock());
+
+        addSettingComponent(osxSettingsPanel, BundleMessage.format("UI_OSX_HIDE_FROM_DOCK"), osxHideFromDockCheckBox,
+                BundleMessage.format("UI_OSX_HIDE_FROM_DOCK_HELP"));
+
+        updateMaximumSize(osxSettingsPanel);
+        return osxSettingsPanel;
+    }
+
+    protected JPanel getLoggingSettingsPanel() {
+        JPanel loggingLevelPanel = new JPanel();
+        JPanel leftLoggingPanel = new JPanel(new GridLayout(2, 2));
+        JPanel rightLoggingPanel = new JPanel(new GridLayout(2, 2));
+        loggingLevelPanel.add(leftLoggingPanel);
+        loggingLevelPanel.add(rightLoggingPanel);
+
+        rootLoggingLevelField = new JComboBox(LOG_LEVELS);
+        davmailLoggingLevelField = new JComboBox(LOG_LEVELS);
+        httpclientLoggingLevelField = new JComboBox(LOG_LEVELS);
+        wireLoggingLevelField = new JComboBox(LOG_LEVELS);
+        logFilePathField = new JTextField(Settings.getProperty("davmail.logFilePath"), 15);
+        logFileSizeField = new JTextField(Settings.getProperty("davmail.logFileSize"), 15);
+
+        rootLoggingLevelField.setSelectedItem(Settings.getLoggingLevel("rootLogger"));
+        davmailLoggingLevelField.setSelectedItem(Settings.getLoggingLevel("davmail"));
+        httpclientLoggingLevelField.setSelectedItem(Settings.getLoggingLevel("org.apache.commons.httpclient"));
+        wireLoggingLevelField.setSelectedItem(Settings.getLoggingLevel("httpclient.wire"));
+
+        addSettingComponent(leftLoggingPanel, BundleMessage.format("UI_LOG_DEFAULT"), rootLoggingLevelField);
+        addSettingComponent(leftLoggingPanel, BundleMessage.format("UI_LOG_DAVMAIL"), davmailLoggingLevelField);
+        addSettingComponent(rightLoggingPanel, BundleMessage.format("UI_LOG_HTTPCLIENT"), httpclientLoggingLevelField);
+        addSettingComponent(rightLoggingPanel, BundleMessage.format("UI_LOG_WIRE"), wireLoggingLevelField);
+
+        JPanel logFilePathPanel = new JPanel(new GridLayout(2, 2));
+        addSettingComponent(logFilePathPanel, BundleMessage.format("UI_LOG_FILE_PATH"), logFilePathField);
+        addSettingComponent(logFilePathPanel, BundleMessage.format("UI_LOG_FILE_SIZE"), logFileSizeField);
+
+        JButton defaultButton = new JButton(BundleMessage.format("UI_BUTTON_DEFAULT"));
+        defaultButton.setToolTipText(BundleMessage.format("UI_BUTTON_DEFAULT_HELP"));
+        defaultButton.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                rootLoggingLevelField.setSelectedItem(Level.WARN);
+                davmailLoggingLevelField.setSelectedItem(Level.DEBUG);
+                httpclientLoggingLevelField.setSelectedItem(Level.WARN);
+                wireLoggingLevelField.setSelectedItem(Level.WARN);
+            }
+        });
+
+        JPanel loggingPanel = new JPanel();
+        loggingPanel.setLayout(new BoxLayout(loggingPanel, BoxLayout.Y_AXIS));
+        loggingPanel.setBorder(BorderFactory.createTitledBorder(BundleMessage.format("UI_LOGGING_LEVELS")));
+        loggingPanel.add(logFilePathPanel);
+        loggingPanel.add(loggingLevelPanel);
+        loggingPanel.add(defaultButton);
+
+        updateMaximumSize(loggingPanel);
+        return loggingPanel;
+    }
+
+    protected void updateMaximumSize(JPanel panel) {
+        Dimension preferredSize = panel.getPreferredSize();
+        preferredSize.width = Integer.MAX_VALUE;
+        panel.setMaximumSize(preferredSize);
+    }
+
+    /**
+     * Reload settings from properties.
+     */
+    public void reload() {
+        // reload settings in form
+        urlField.setText(Settings.getProperty("davmail.url"));
+        popPortField.setText(Settings.getProperty("davmail.popPort"));
+        popPortCheckBox.setSelected(Settings.getProperty("davmail.popPort") != null && Settings.getProperty("davmail.popPort").length() > 0);
+        popNoSSLCheckBox.setSelected(Settings.getBooleanProperty("davmail.ssl.nosecurepop"));
+        imapPortField.setText(Settings.getProperty("davmail.imapPort"));
+        imapPortCheckBox.setSelected(Settings.getProperty("davmail.imapPort") != null && Settings.getProperty("davmail.imapPort").length() > 0);
+        imapNoSSLCheckBox.setSelected(Settings.getBooleanProperty("davmail.ssl.nosecureimap"));
+        smtpPortField.setText(Settings.getProperty("davmail.smtpPort"));
+        smtpPortCheckBox.setSelected(Settings.getProperty("davmail.smtpPort") != null && Settings.getProperty("davmail.smtpPort").length() > 0);
+        smtpNoSSLCheckBox.setSelected(Settings.getBooleanProperty("davmail.ssl.nosecuresmtp"));
+        caldavPortField.setText(Settings.getProperty("davmail.caldavPort"));
+        caldavPortCheckBox.setSelected(Settings.getProperty("davmail.caldavPort") != null && Settings.getProperty("davmail.caldavPort").length() > 0);
+        caldavNoSSLCheckBox.setSelected(Settings.getBooleanProperty("davmail.ssl.nosecurecaldav"));
+        ldapPortField.setText(Settings.getProperty("davmail.ldapPort"));
+        ldapPortCheckBox.setSelected(Settings.getProperty("davmail.ldapPort") != null && Settings.getProperty("davmail.ldapPort").length() > 0);
+        ldapNoSSLCheckBox.setSelected(Settings.getBooleanProperty("davmail.ssl.nosecureldap"));
+        keepDelayField.setText(Settings.getProperty("davmail.keepDelay"));
+        sentKeepDelayField.setText(Settings.getProperty("davmail.sentKeepDelay"));
+        caldavPastDelayField.setText(Settings.getProperty("davmail.caldavPastDelay"));
+        imapIdleDelayField.setText(Settings.getProperty("davmail.imapIdleDelay"));
+        boolean useSystemProxies = Settings.getBooleanProperty("davmail.useSystemProxies", Boolean.FALSE);
+        useSystemProxiesField.setSelected(useSystemProxies);
+        boolean enableProxy = Settings.getBooleanProperty("davmail.enableProxy");
+        enableProxyField.setSelected(enableProxy);
+        enableProxyField.setEnabled(!useSystemProxies);
+        httpProxyField.setEnabled(!useSystemProxies && enableProxy);
+        httpProxyPortField.setEnabled(!useSystemProxies && enableProxy);
+        httpProxyUserField.setEnabled(useSystemProxies || enableProxy);
+        httpProxyPasswordField.setEnabled(useSystemProxies || enableProxy);
+        httpProxyField.setText(Settings.getProperty("davmail.proxyHost"));
+        httpProxyPortField.setText(Settings.getProperty("davmail.proxyPort"));
+        httpProxyUserField.setText(Settings.getProperty("davmail.proxyUser"));
+        httpProxyPasswordField.setText(Settings.getProperty("davmail.proxyPassword"));
+
+        bindAddressField.setText(Settings.getProperty("davmail.bindAddress"));
+        allowRemoteField.setSelected(Settings.getBooleanProperty(("davmail.allowRemote")));
+        certHashField.setText(Settings.getProperty("davmail.server.certificate.hash"));
+        disableUpdateCheck.setSelected(Settings.getBooleanProperty(("davmail.disableUpdateCheck")));
+
+        caldavEditNotificationsField.setSelected(Settings.getBooleanProperty("davmail.caldavEditNotifications"));
+        clientSoTimeoutField.setText(Settings.getProperty("davmail.clientSoTimeout"));
+        caldavAlarmSoundField.setText(Settings.getProperty("davmail.caldavAlarmSound"));
+        forceActiveSyncUpdateCheckBox.setSelected(Settings.getBooleanProperty("davmail.forceActiveSyncUpdate"));
+        defaultDomainField.setText(Settings.getProperty("davmail.defaultDomain"));
+        showStartupBannerCheckBox.setSelected(Settings.getBooleanProperty("davmail.showStartupBanner", true));
+        disableGuiNotificationsCheckBox.setSelected(Settings.getBooleanProperty("davmail.disableGuiNotifications", false));
+        imapAutoExpungeCheckBox.setSelected(Settings.getBooleanProperty("davmail.imapAutoExpunge", true));
+        popMarkReadOnRetrCheckBox.setSelected(Settings.getBooleanProperty("davmail.popMarkReadOnRetrCheckBox", false));
+        setEwsModeSelectedItem(Settings.getProperty("davmail.enableEws", "auto"));
+        smtpSaveInSentCheckBox.setSelected(Settings.getBooleanProperty("davmail.smtpSaveInSent", true));
+
+        keystoreTypeCombo.setSelectedItem(Settings.getProperty("davmail.ssl.keystoreType"));
+        keystoreFileField.setText(Settings.getProperty("davmail.ssl.keystoreFile"));
+        keystorePassField.setText(Settings.getProperty("davmail.ssl.keystorePass"));
+        keyPassField.setText(Settings.getProperty("davmail.ssl.keyPass"));
+
+        clientKeystoreTypeCombo.setSelectedItem(Settings.getProperty("davmail.ssl.clientKeystoreType"));
+        pkcs11LibraryField.setText(Settings.getProperty("davmail.ssl.pkcs11Library"));
+        pkcs11ConfigField.setText(Settings.getProperty("davmail.ssl.pkcs11Config"));
+
+        rootLoggingLevelField.setSelectedItem(Settings.getLoggingLevel("rootLogger"));
+        davmailLoggingLevelField.setSelectedItem(Settings.getLoggingLevel("davmail"));
+        httpclientLoggingLevelField.setSelectedItem(Settings.getLoggingLevel("org.apache.commons.httpclient"));
+        wireLoggingLevelField.setSelectedItem(Settings.getLoggingLevel("httpclient.wire"));
+        logFilePathField.setText(Settings.getProperty("davmail.logFilePath"));
+        logFileSizeField.setText(Settings.getProperty("davmail.logFileSize"));
+
+        if (osxHideFromDockCheckBox != null) {
+            osxHideFromDockCheckBox.setSelected(OSXInfoPlist.isHideFromDock());
+        }
+    }
+
+    protected boolean isSslEnabled() {
+        if (keystoreFileField != null) {
+            return keystoreFileField.getText().length() > 0;
+        } else {
+            return Settings.getProperty("davmail.ssl.keystoreFile") != null &&
+                    (Settings.getProperty("davmail.ssl.keystoreFile").length() > 0);
+        }
+    }
+
+    /**
+     * DavMail settings frame.
+     */
+    public SettingsFrame() {
+        setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
+        setTitle(BundleMessage.format("UI_DAVMAIL_SETTINGS"));
+        try {
+            setIconImage(DavGatewayTray.getFrameIcon());
+        } catch (NoSuchMethodError error) {
+            DavGatewayTray.debug(new BundleMessage("LOG_UNABLE_TO_SET_ICON_IMAGE"));
+        }
+
+        JTabbedPane tabbedPane = new JTabbedPane();
+        // add help (F1 handler)
+        tabbedPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke("F1"),
+                "help");
+        tabbedPane.getActionMap().put("help", new AbstractAction() {
+            public void actionPerformed(ActionEvent e) {
+                DesktopBrowser.browse("http://davmail.sourceforge.net");
+            }
+        });
+        tabbedPane.addChangeListener(new ChangeListener() {
+            public void stateChanged(ChangeEvent e) {
+                boolean isSslEnabled = isSslEnabled();
+                popNoSSLCheckBox.setEnabled(Settings.getProperty("davmail.popPort") != null && isSslEnabled);
+                imapNoSSLCheckBox.setEnabled(imapPortCheckBox.isSelected() && isSslEnabled);
+                smtpNoSSLCheckBox.setEnabled(smtpPortCheckBox.isSelected() && isSslEnabled);
+                caldavNoSSLCheckBox.setEnabled(caldavPortCheckBox.isSelected() && isSslEnabled);
+                ldapNoSSLCheckBox.setEnabled(ldapPortCheckBox.isSelected() && isSslEnabled);
+            }
+        });
+
+        JPanel mainPanel = new JPanel();
+        mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
+        mainPanel.add(getSettingsPanel());
+        mainPanel.add(getDelaysPanel());
+        mainPanel.add(Box.createVerticalGlue());
+
+        tabbedPane.add(BundleMessage.format("UI_TAB_MAIN"), mainPanel);
+
+        JPanel proxyPanel = new JPanel();
+        proxyPanel.setLayout(new BoxLayout(proxyPanel, BoxLayout.Y_AXIS));
+        proxyPanel.add(getProxyPanel());
+        proxyPanel.add(getNetworkSettingsPanel());
+        tabbedPane.add(BundleMessage.format("UI_TAB_NETWORK"), proxyPanel);
+
+        JPanel encryptionPanel = new JPanel();
+        encryptionPanel.setLayout(new BoxLayout(encryptionPanel, BoxLayout.Y_AXIS));
+        encryptionPanel.add(getKeystorePanel());
+        encryptionPanel.add(getSmartCardPanel());
+        // empty panel
+        encryptionPanel.add(new JPanel());
+        tabbedPane.add(BundleMessage.format("UI_TAB_ENCRYPTION"), encryptionPanel);
+
+        JPanel loggingPanel = new JPanel();
+        loggingPanel.setLayout(new BoxLayout(loggingPanel, BoxLayout.Y_AXIS));
+        loggingPanel.add(getLoggingSettingsPanel());
+        // empty panel
+        loggingPanel.add(new JPanel());
+
+        tabbedPane.add(BundleMessage.format("UI_TAB_LOGGING"), loggingPanel);
+
+        JPanel advancedPanel = new JPanel();
+        advancedPanel.setLayout(new BoxLayout(advancedPanel, BoxLayout.Y_AXIS));
+
+        advancedPanel.add(getOtherSettingsPanel());
+        // empty panel
+        advancedPanel.add(new JPanel());
+
+        tabbedPane.add(BundleMessage.format("UI_TAB_ADVANCED"), advancedPanel);
+
+        if (OSXInfoPlist.isOSX()) {
+            JPanel osxPanel = new JPanel();
+            osxPanel.setLayout(new BoxLayout(osxPanel, BoxLayout.Y_AXIS));
+            osxPanel.add(getOSXPanel());
+            // empty panel
+            osxPanel.add(new JPanel());
+
+            tabbedPane.add(BundleMessage.format("UI_TAB_OSX"), osxPanel);
+        }
+
+        add(BorderLayout.CENTER, tabbedPane);
+
+        JPanel buttonPanel = new JPanel();
+        JButton cancel = new JButton(BundleMessage.format("UI_BUTTON_CANCEL"));
+        JButton ok = new JButton(BundleMessage.format("UI_BUTTON_SAVE"));
+        JButton help = new JButton(BundleMessage.format("UI_BUTTON_HELP"));
+        ActionListener save = new ActionListener() {
+            public void actionPerformed(ActionEvent evt) {
+                // save options
+                Settings.setProperty("davmail.url", urlField.getText());
+                Settings.setProperty("davmail.popPort", popPortCheckBox.isSelected() ? popPortField.getText() : "");
+                Settings.setProperty("davmail.ssl.nosecurepop", String.valueOf(popNoSSLCheckBox.isSelected()));
+                Settings.setProperty("davmail.imapPort", imapPortCheckBox.isSelected() ? imapPortField.getText() : "");
+                Settings.setProperty("davmail.ssl.nosecureimap", String.valueOf(imapNoSSLCheckBox.isSelected()));
+                Settings.setProperty("davmail.smtpPort", smtpPortCheckBox.isSelected() ? smtpPortField.getText() : "");
+                Settings.setProperty("davmail.ssl.nosecuresmtp", String.valueOf(smtpNoSSLCheckBox.isSelected()));
+                Settings.setProperty("davmail.caldavPort", caldavPortCheckBox.isSelected() ? caldavPortField.getText() : "");
+                Settings.setProperty("davmail.ssl.nosecurecaldav", String.valueOf(caldavNoSSLCheckBox.isSelected()));
+                Settings.setProperty("davmail.ldapPort", ldapPortCheckBox.isSelected() ? ldapPortField.getText() : "");
+                Settings.setProperty("davmail.ssl.nosecureldap", String.valueOf(ldapNoSSLCheckBox.isSelected()));
+                Settings.setProperty("davmail.keepDelay", keepDelayField.getText());
+                Settings.setProperty("davmail.sentKeepDelay", sentKeepDelayField.getText());
+                Settings.setProperty("davmail.caldavPastDelay", caldavPastDelayField.getText());
+                Settings.setProperty("davmail.imapIdleDelay", imapIdleDelayField.getText());
+                Settings.setProperty("davmail.useSystemProxies", String.valueOf(useSystemProxiesField.isSelected()));
+                Settings.setProperty("davmail.enableProxy", String.valueOf(enableProxyField.isSelected()));
+                Settings.setProperty("davmail.proxyHost", httpProxyField.getText());
+                Settings.setProperty("davmail.proxyPort", httpProxyPortField.getText());
+                Settings.setProperty("davmail.proxyUser", httpProxyUserField.getText());
+                Settings.setProperty("davmail.proxyPassword", httpProxyPasswordField.getText());
+
+                Settings.setProperty("davmail.bindAddress", bindAddressField.getText());
+                Settings.setProperty("davmail.clientSoTimeout", String.valueOf(clientSoTimeoutField.getText()));
+                Settings.setProperty("davmail.allowRemote", String.valueOf(allowRemoteField.isSelected()));
+                Settings.setProperty("davmail.server.certificate.hash", certHashField.getText());
+                Settings.setProperty("davmail.disableUpdateCheck", String.valueOf(disableUpdateCheck.isSelected()));
+
+                Settings.setProperty("davmail.caldavEditNotifications", String.valueOf(caldavEditNotificationsField.isSelected()));
+                Settings.setProperty("davmail.caldavAlarmSound", String.valueOf(caldavAlarmSoundField.getText()));
+                Settings.setProperty("davmail.forceActiveSyncUpdate", String.valueOf(forceActiveSyncUpdateCheckBox.isSelected()));
+                Settings.setProperty("davmail.defaultDomain", String.valueOf(defaultDomainField.getText()));
+                Settings.setProperty("davmail.showStartupBanner", String.valueOf(showStartupBannerCheckBox.isSelected()));
+                Settings.setProperty("davmail.disableGuiNotifications", String.valueOf(disableGuiNotificationsCheckBox.isSelected()));
+                Settings.setProperty("davmail.imapAutoExpunge", String.valueOf(imapAutoExpungeCheckBox.isSelected()));
+                Settings.setProperty("davmail.popMarkReadOnRetr", String.valueOf(popMarkReadOnRetrCheckBox.isSelected()));
+                String selectedEwsMode = (String) enableEwsComboBox.getSelectedItem();
+                String enableEws;
+                if (EWS.equals(selectedEwsMode)) {
+                    enableEws = "true";
+                } else if (WEBDAV.equals(selectedEwsMode)) {
+                    enableEws = "false";
+                } else {
+                    enableEws = "auto";
+                }
+                Settings.setProperty("davmail.enableEws", enableEws);
+                Settings.setProperty("davmail.smtpSaveInSent", String.valueOf(smtpSaveInSentCheckBox.isSelected()));
+
+                Settings.setProperty("davmail.ssl.keystoreType", (String) keystoreTypeCombo.getSelectedItem());
+                Settings.setProperty("davmail.ssl.keystoreFile", keystoreFileField.getText());
+                Settings.setProperty("davmail.ssl.keystorePass", String.valueOf(keystorePassField.getPassword()));
+                Settings.setProperty("davmail.ssl.keyPass", String.valueOf(keyPassField.getPassword()));
+
+                Settings.setProperty("davmail.ssl.clientKeystoreType", (String) clientKeystoreTypeCombo.getSelectedItem());
+                Settings.setProperty("davmail.ssl.clientKeystoreFile", clientKeystoreFileField.getText());
+                Settings.setProperty("davmail.ssl.clientKeystorePass", String.valueOf(clientKeystorePassField.getPassword()));
+                Settings.setProperty("davmail.ssl.pkcs11Library", pkcs11LibraryField.getText());
+                Settings.setProperty("davmail.ssl.pkcs11Config", pkcs11ConfigField.getText());
+
+                Settings.setLoggingLevel("rootLogger", (Level) rootLoggingLevelField.getSelectedItem());
+                Settings.setLoggingLevel("davmail", (Level) davmailLoggingLevelField.getSelectedItem());
+                Settings.setLoggingLevel("org.apache.commons.httpclient", (Level) httpclientLoggingLevelField.getSelectedItem());
+                Settings.setLoggingLevel("httpclient.wire", (Level) wireLoggingLevelField.getSelectedItem());
+                Settings.setProperty("davmail.logFilePath", logFilePathField.getText());
+                Settings.setProperty("davmail.logFileSize", logFileSizeField.getText());
+
+                setVisible(false);
+                Settings.save();
+
+                if (osxHideFromDockCheckBox != null) {
+                    OSXInfoPlist.setOSXHideFromDock(osxHideFromDockCheckBox.isSelected());
+                }
+
+                // restart listeners with new config
+                DavGateway.restart();
+            }
+        };
+        ok.addActionListener(save);
+
+        cancel.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent evt) {
+                reload();
+                setVisible(false);
+            }
+        });
+
+        help.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                DesktopBrowser.browse("http://davmail.sourceforge.net");
+            }
+        });
+
+        buttonPanel.add(ok);
+        buttonPanel.add(cancel);
+        buttonPanel.add(help);
+
+        add(BorderLayout.SOUTH, buttonPanel);
+
+        pack();
+        //setResizable(false);
+        // center frame
+        setLocation(getToolkit().getScreenSize().width / 2 -
+                getSize().width / 2,
+                getToolkit().getScreenSize().height / 2 -
+                        getSize().height / 2);
+        urlField.requestFocus();
+    }
+}
diff --git a/src/java/davmail/ui/browser/AwtDesktopBrowser.java b/src/java/davmail/ui/browser/AwtDesktopBrowser.java
new file mode 100644
index 0000000..250dcbd
--- /dev/null
+++ b/src/java/davmail/ui/browser/AwtDesktopBrowser.java
@@ -0,0 +1,44 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.ui.browser;
+
+import java.awt.*;
+import java.io.IOException;
+import java.net.URI;
+
+/**
+ * Wrapper class to call Java6 Desktop class to launch default browser.
+ */
+public final class AwtDesktopBrowser {
+    private AwtDesktopBrowser() {
+    }
+
+    /**
+     * Open default browser at location URI.
+     * User Java 6 Desktop class
+     *
+     * @param location location URI
+     * @throws IOException on error
+     */
+    public static void browse(URI location) throws IOException {
+        Desktop desktop = Desktop.getDesktop();
+        desktop.browse(location);
+    }
+
+}
diff --git a/src/java/davmail/ui/browser/DesktopBrowser.java b/src/java/davmail/ui/browser/DesktopBrowser.java
new file mode 100644
index 0000000..b0626b0
--- /dev/null
+++ b/src/java/davmail/ui/browser/DesktopBrowser.java
@@ -0,0 +1,90 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.ui.browser;
+
+import davmail.BundleMessage;
+import davmail.ui.AboutFrame;
+import davmail.ui.tray.DavGatewayTray;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+/**
+ * Open default browser.
+ */
+public final class DesktopBrowser {
+    private DesktopBrowser() {
+    }
+
+    /**
+     * Open default browser at location URI.
+     * User Java 6 Desktop class, OSX open command or SWT program launch
+     *
+     * @param location location URI
+     */
+    public static void browse(URI location) {
+        try {
+            // trigger ClassNotFoundException
+            ClassLoader classloader = AboutFrame.class.getClassLoader();
+            classloader.loadClass("java.awt.Desktop");
+
+            // Open link in default browser
+            AwtDesktopBrowser.browse(location);
+        } catch (ClassNotFoundException e) {
+            DavGatewayTray.debug(new BundleMessage("LOG_JAVA6_DESKTOP_UNAVAILABLE"));
+            // failover for MacOSX
+            if (System.getProperty("os.name").toLowerCase().startsWith("mac os x")) {
+                try {
+                    OSXDesktopBrowser.browse(location);
+                } catch (Exception e2) {
+                    DavGatewayTray.error(new BundleMessage("LOG_UNABLE_TO_OPEN_LINK"), e2);
+                }
+            } else {
+                // failover : try SWT
+                try {
+                    // trigger ClassNotFoundException
+                    ClassLoader classloader = AboutFrame.class.getClassLoader();
+                    classloader.loadClass("org.eclipse.swt.program.Program");
+                    SwtDesktopBrowser.browse(location);
+                } catch (ClassNotFoundException e2) {
+                    DavGatewayTray.error(new BundleMessage("LOG_OPEN_LINK_NOT_SUPPORTED"));
+                } catch (Exception e2) {
+                    DavGatewayTray.error(new BundleMessage("LOG_UNABLE_TO_OPEN_LINK"), e2);
+                }
+            }
+        } catch (Exception e) {
+            DavGatewayTray.error(new BundleMessage("LOG_UNABLE_TO_OPEN_LINK"), e);
+        }
+    }
+
+    /**
+     * Open default browser at location.
+     * User Java 6 Desktop class, OSX open command or SWT program launch
+     *
+     * @param location target location
+     */
+    public static void browse(String location) {
+        try {
+            DesktopBrowser.browse(new URI(location));
+        } catch (URISyntaxException e) {
+            DavGatewayTray.error(new BundleMessage("LOG_UNABLE_TO_OPEN_LINK"), e);
+        }
+    }
+
+}
diff --git a/src/java/davmail/ui/browser/OSXDesktopBrowser.java b/src/java/davmail/ui/browser/OSXDesktopBrowser.java
new file mode 100644
index 0000000..250e5cb
--- /dev/null
+++ b/src/java/davmail/ui/browser/OSXDesktopBrowser.java
@@ -0,0 +1,41 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.ui.browser;
+
+import java.io.IOException;
+import java.net.URI;
+
+/**
+ * Failover: Runtime.exec open URL
+ */
+public final class OSXDesktopBrowser {
+    private OSXDesktopBrowser() {
+    }
+
+    /**
+     * Open default browser at location URI.
+     * User OSX open command
+     *
+     * @param location location URI
+     * @throws IOException on error
+     */
+    public static void browse(URI location) throws IOException {
+        Runtime.getRuntime().exec("open " + location.toString());
+    }
+}
diff --git a/src/java/davmail/ui/browser/SwtDesktopBrowser.java b/src/java/davmail/ui/browser/SwtDesktopBrowser.java
new file mode 100644
index 0000000..d488400
--- /dev/null
+++ b/src/java/davmail/ui/browser/SwtDesktopBrowser.java
@@ -0,0 +1,42 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.ui.browser;
+
+import org.eclipse.swt.program.Program;
+
+import java.net.URI;
+
+/**
+ * Wrapper class to call SWT Program class to launch default browser.
+ */
+public final class SwtDesktopBrowser {
+    private SwtDesktopBrowser() {
+    }
+
+    /**
+     * Open default browser at location URI.
+     * User SWT program launch
+     *
+     * @param location location URI
+     */
+    public static void browse(URI location) {
+        Program.launch(location.toString());
+    }
+
+}
diff --git a/src/java/davmail/ui/tray/AwtGatewayTray.java b/src/java/davmail/ui/tray/AwtGatewayTray.java
new file mode 100644
index 0000000..a0303da
--- /dev/null
+++ b/src/java/davmail/ui/tray/AwtGatewayTray.java
@@ -0,0 +1,296 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.ui.tray;
+
+import davmail.BundleMessage;
+import davmail.DavGateway;
+import davmail.Settings;
+import davmail.ui.AboutFrame;
+import davmail.ui.SettingsFrame;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.lf5.LF5Appender;
+import org.apache.log4j.lf5.LogLevel;
+import org.apache.log4j.lf5.viewer.LogBrokerMonitor;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+/**
+ * Tray icon handler based on java 1.6
+ */
+public class AwtGatewayTray implements DavGatewayTrayInterface {
+    protected static final String TRAY_ACTIVE_PNG = "tray2.png";
+    protected static final String TRAY_PNG = "tray.png";
+    protected static final String TRAY_INACTIVE_PNG = "trayinactive.png";
+
+    protected AwtGatewayTray() {
+    }
+
+    static AboutFrame aboutFrame;
+    static SettingsFrame settingsFrame;
+    ActionListener settingsListener;
+
+    static TrayIcon trayIcon;
+    private static Image image;
+    private static Image image2;
+    private static Image inactiveImage;
+    static LogBrokerMonitor logBrokerMonitor;
+    private boolean isActive = true;
+
+    /**
+     * Return AWT Image icon for frame title.
+     *
+     * @return frame icon
+     */
+    public Image getFrameIcon() {
+        return image;
+    }
+
+    /**
+     * Switch tray icon between active and standby icon.
+     */
+    public void switchIcon() {
+        isActive = true;
+        SwingUtilities.invokeLater(new Runnable() {
+            public void run() {
+                if (trayIcon.getImage().equals(image)) {
+                    trayIcon.setImage(image2);
+                } else {
+                    trayIcon.setImage(image);
+                }
+            }
+        });
+    }
+
+    /**
+     * Set tray icon to inactive (network down)
+     */
+    public void resetIcon() {
+        SwingUtilities.invokeLater(new Runnable() {
+            public void run() {
+                trayIcon.setImage(image);
+            }
+        });
+    }
+
+    /**
+     * Set tray icon to inactive (network down)
+     */
+    public void inactiveIcon() {
+        isActive = false;
+        SwingUtilities.invokeLater(new Runnable() {
+            public void run() {
+                trayIcon.setImage(inactiveImage);
+            }
+        });
+    }
+
+    /**
+     * Check if current tray status is inactive (network down).
+     *
+     * @return true if inactive
+     */
+    public boolean isActive() {
+        return isActive;
+    }
+
+    /**
+     * Display balloon message for log level.
+     *
+     * @param message text message
+     * @param level   log level
+     */
+    public void displayMessage(final String message, final Level level) {
+        SwingUtilities.invokeLater(new Runnable() {
+            public void run() {
+                if (trayIcon != null) {
+                    TrayIcon.MessageType messageType = null;
+                    if (level.equals(Level.INFO)) {
+                        messageType = TrayIcon.MessageType.INFO;
+                    } else if (level.equals(Level.WARN)) {
+                        messageType = TrayIcon.MessageType.WARNING;
+                    } else if (level.equals(Level.ERROR)) {
+                        messageType = TrayIcon.MessageType.ERROR;
+                    }
+                    if (messageType != null) {
+                        trayIcon.displayMessage(BundleMessage.format("UI_DAVMAIL_GATEWAY"), message, messageType);
+                    }
+                    trayIcon.setToolTip(BundleMessage.format("UI_DAVMAIL_GATEWAY") + '\n' + message);
+                }
+            }
+        });
+    }
+
+    /**
+     * Open about window
+     */
+    public void about() {
+        SwingUtilities.invokeLater(new Runnable() {
+            public void run() {
+                aboutFrame.update();
+                aboutFrame.setVisible(true);
+                aboutFrame.toFront();
+                aboutFrame.requestFocus();
+            }
+        });
+    }
+
+    /**
+     * Open settings window
+     */
+    public void preferences() {
+        SwingUtilities.invokeLater(new Runnable() {
+            public void run() {
+                settingsFrame.reload();
+                settingsFrame.setVisible(true);
+                settingsFrame.toFront();
+                settingsFrame.requestFocus();
+            }
+        });
+    }
+
+    /**
+     * Create tray icon and register frame listeners.
+     */
+    public void init() {
+        SwingUtilities.invokeLater(new Runnable() {
+            public void run() {
+                createAndShowGUI();
+            }
+        });
+    }
+
+    protected void createAndShowGUI() {
+        System.setProperty("swing.defaultlaf", UIManager.getSystemLookAndFeelClassName());
+
+        // get the SystemTray instance
+        SystemTray tray = SystemTray.getSystemTray();
+        image = DavGatewayTray.loadImage(getTrayIconPath());
+        image2 = DavGatewayTray.loadImage(getTrayIconActivePath());
+        inactiveImage = DavGatewayTray.loadImage(getTrayIconInactivePath());
+
+        // create a popup menu
+        PopupMenu popup = new PopupMenu();
+
+        aboutFrame = new AboutFrame();
+        // create an action settingsListener to listen for settings action executed on the tray icon
+        ActionListener aboutListener = new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                about();
+            }
+        };
+        // create menu item for the default action
+        MenuItem aboutItem = new MenuItem(BundleMessage.format("UI_ABOUT"));
+        aboutItem.addActionListener(aboutListener);
+        popup.add(aboutItem);
+
+        settingsFrame = new SettingsFrame();
+        // create an action settingsListener to listen for settings action executed on the tray icon
+        settingsListener = new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                preferences();
+            }
+        };
+        // create menu item for the default action
+        MenuItem defaultItem = new MenuItem(BundleMessage.format("UI_SETTINGS"));
+        defaultItem.addActionListener(settingsListener);
+        popup.add(defaultItem);
+
+        MenuItem logItem = new MenuItem(BundleMessage.format("UI_SHOW_LOGS"));
+        logItem.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                Logger rootLogger = Logger.getRootLogger();
+                LF5Appender lf5Appender = (LF5Appender) rootLogger.getAppender("LF5Appender");
+                if (lf5Appender == null) {
+                    logBrokerMonitor = new LogBrokerMonitor(LogLevel.getLog4JLevels()) {
+                        @Override
+                        protected void closeAfterConfirm() {
+                            hide();
+                        }
+                    };
+                    lf5Appender = new LF5Appender(logBrokerMonitor);
+                    lf5Appender.setName("LF5Appender");
+                    rootLogger.addAppender(lf5Appender);
+                }
+                lf5Appender.getLogBrokerMonitor().show();
+            }
+        });
+        popup.add(logItem);
+
+        // create an action exitListener to listen for exit action executed on the tray icon
+        ActionListener exitListener = new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                try {
+                    DavGateway.stop();
+                    SystemTray.getSystemTray().remove(trayIcon);
+
+                    // dispose frames
+                    settingsFrame.dispose();
+                    aboutFrame.dispose();
+                    if (logBrokerMonitor != null) {
+                        logBrokerMonitor.dispose();
+                    }
+                } catch (Exception exc) {
+                    DavGatewayTray.error(exc);
+                }
+                // make sure we do exit
+                System.exit(0);
+            }
+        };
+        // create menu item for the exit action
+        MenuItem exitItem = new MenuItem(BundleMessage.format("UI_EXIT"));
+        exitItem.addActionListener(exitListener);
+        popup.add(exitItem);
+
+        /// ... add other items
+        // construct a TrayIcon
+        trayIcon = new TrayIcon(image, BundleMessage.format("UI_DAVMAIL_GATEWAY"), popup);
+        // set the TrayIcon properties
+        trayIcon.addActionListener(settingsListener);
+        // ...
+        // add the tray image
+        try {
+            tray.add(trayIcon);
+        } catch (AWTException e) {
+            DavGatewayTray.warn(new BundleMessage("LOG_UNABLE_TO_CREATE_TRAY"), e);
+        }
+
+        // display settings frame on first start
+        if (Settings.isFirstStart()) {
+            settingsFrame.setVisible(true);
+            settingsFrame.toFront();
+            settingsFrame.requestFocus();
+        }
+    }
+
+    protected String getTrayIconPath() {
+        return AwtGatewayTray.TRAY_PNG;
+    }
+
+    protected String getTrayIconActivePath() {
+        return AwtGatewayTray.TRAY_ACTIVE_PNG;
+    }
+
+    protected String getTrayIconInactivePath() {
+        return AwtGatewayTray.TRAY_INACTIVE_PNG;
+    }
+}
diff --git a/src/java/davmail/ui/tray/DavGatewayTray.java b/src/java/davmail/ui/tray/DavGatewayTray.java
new file mode 100644
index 0000000..1e1dd6a
--- /dev/null
+++ b/src/java/davmail/ui/tray/DavGatewayTray.java
@@ -0,0 +1,293 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.ui.tray;
+
+import davmail.BundleMessage;
+import davmail.Settings;
+import davmail.exchange.NetworkDownException;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.io.IOException;
+import java.net.URL;
+
+
+/**
+ * Tray icon handler
+ */
+public final class DavGatewayTray {
+    private static final Logger LOGGER = Logger.getLogger("davmail");
+    private static final long ICON_SWITCH_MINIMUM_DELAY = 250;
+    private static long lastIconSwitch;
+
+    private DavGatewayTray() {
+    }
+
+    static DavGatewayTrayInterface davGatewayTray;
+
+    /**
+     * Return AWT Image icon for frame title.
+     *
+     * @return frame icon
+     */
+    public static Image getFrameIcon() {
+        Image icon = null;
+        if (davGatewayTray != null) {
+            icon = davGatewayTray.getFrameIcon();
+        }
+        return icon;
+    }
+
+    /**
+     * Switch tray icon between active and standby icon.
+     */
+    public static void switchIcon() {
+        if (davGatewayTray != null) {
+            if (System.currentTimeMillis() - lastIconSwitch > ICON_SWITCH_MINIMUM_DELAY) {
+                davGatewayTray.switchIcon();
+                lastIconSwitch = System.currentTimeMillis();
+            }
+        }
+    }
+
+    /**
+     * Set tray icon to inactive (network down)
+     */
+    public static void resetIcon() {
+        if (davGatewayTray != null && isActive()) {
+            davGatewayTray.resetIcon();
+        }
+    }
+
+    /**
+     * Check if current tray status is inactive (network down).
+     *
+     * @return true if inactive
+     */
+    public static boolean isActive() {
+        return davGatewayTray == null || davGatewayTray.isActive();
+    }
+
+    /**
+     * Log and display balloon message according to log level.
+     *
+     * @param message text message
+     * @param level   log level
+     */
+    private static void displayMessage(BundleMessage message, Level level) {
+        LOGGER.log(level, message.formatLog());
+        if (davGatewayTray != null && !Settings.getBooleanProperty("davmail.disableGuiNotifications")) {
+            davGatewayTray.displayMessage(message.format(), level);
+        }
+    }
+
+    /**
+     * Log and display balloon message and exception according to log level.
+     *
+     * @param message text message
+     * @param e       exception
+     * @param level   log level
+     */
+    private static void displayMessage(BundleMessage message, Exception e, Level level) {
+        if (e instanceof NetworkDownException) {
+            LOGGER.log(level, BundleMessage.getExceptionLogMessage(message, e));
+        } else {
+            LOGGER.log(level, BundleMessage.getExceptionLogMessage(message, e), e);
+        }
+        if (davGatewayTray != null && !Settings.getBooleanProperty("davmail.disableGuiNotifications")
+                && (!(e instanceof NetworkDownException))) {
+            davGatewayTray.displayMessage(BundleMessage.getExceptionMessage(message, e), level);
+        }
+        if (davGatewayTray != null && e instanceof NetworkDownException) {
+            davGatewayTray.inactiveIcon();
+        }
+    }
+
+    /**
+     * Log message at level DEBUG.
+     *
+     * @param message bundle message
+     */
+    public static void debug(BundleMessage message) {
+        displayMessage(message, Level.DEBUG);
+    }
+
+    /**
+     * Log message at level INFO.
+     *
+     * @param message bundle message
+     */
+    public static void info(BundleMessage message) {
+        displayMessage(message, Level.INFO);
+    }
+
+    /**
+     * Log message at level WARN.
+     *
+     * @param message bundle message
+     */
+    public static void warn(BundleMessage message) {
+        displayMessage(message, Level.WARN);
+    }
+
+    /**
+     * Log exception at level WARN.
+     *
+     * @param e exception
+     */
+    public static void warn(Exception e) {
+        displayMessage(null, e, Level.WARN);
+    }
+
+    /**
+     * Log message at level ERROR.
+     *
+     * @param message bundle message
+     */
+    public static void error(BundleMessage message) {
+        displayMessage(message, Level.ERROR);
+    }
+
+    /**
+     * Log exception at level WARN for NetworkDownException,
+     * ERROR for other exceptions.
+     *
+     * @param e exception
+     */
+    public static void log(Exception e) {
+        // only warn on network down
+        if (e instanceof NetworkDownException) {
+            warn(e);
+        } else {
+            error(e);
+        }
+    }
+
+    /**
+     * Log exception at level ERROR.
+     *
+     * @param e exception
+     */
+    public static void error(Exception e) {
+        displayMessage(null, e, Level.ERROR);
+    }
+
+    /**
+     * Log message and exception at level DEBUG.
+     *
+     * @param message bundle message
+     * @param e       exception
+     */
+    public static void debug(BundleMessage message, Exception e) {
+        displayMessage(message, e, Level.DEBUG);
+    }
+
+    /**
+     * Log message and exception at level WARN.
+     *
+     * @param message bundle message
+     * @param e       exception
+     */
+    public static void warn(BundleMessage message, Exception e) {
+        displayMessage(message, e, Level.WARN);
+    }
+
+    /**
+     * Log message and exception at level ERROR.
+     *
+     * @param message bundle message
+     * @param e       exception
+     */
+    public static void error(BundleMessage message, Exception e) {
+        displayMessage(message, e, Level.ERROR);
+    }
+
+    /**
+     * Create tray icon and register frame listeners.
+     */
+    public static void init() {
+        if (!Settings.getBooleanProperty("davmail.server")) {
+            // first try to load SWT before with Java
+            ClassLoader classloader = DavGatewayTray.class.getClassLoader();
+            try {
+                // trigger ClassNotFoundException
+                classloader.loadClass("org.eclipse.swt.SWT");
+                // SWT available, create tray
+                davGatewayTray = new SwtGatewayTray();
+                davGatewayTray.init();
+            } catch (ClassNotFoundException e) {
+                DavGatewayTray.info(new BundleMessage("LOG_SWT_NOT_AVAILABLE"));
+            }
+            // try java6 tray support
+            if (davGatewayTray == null) {
+                try {
+                    if (SystemTray.isSupported()) {
+                        if (isOSX()) {
+                            davGatewayTray = new OSXAwtGatewayTray();
+                        } else {
+                            davGatewayTray = new AwtGatewayTray();
+                        }
+                        davGatewayTray.init();
+                    }
+                } catch (NoClassDefFoundError e) {
+                    DavGatewayTray.info(new BundleMessage("LOG_SYSTEM_TRAY_NOT_AVAILABLE"));
+                }
+            }
+            if (davGatewayTray == null) {
+                if (isOSX()) {
+                    // MacOS
+                    davGatewayTray = new OSXFrameGatewayTray();
+                } else {
+                    davGatewayTray = new FrameGatewayTray();
+                }
+                davGatewayTray.init();
+            }
+        }
+    }
+
+    /**
+     * Test if running on OSX
+     *
+     * @return true on Mac OS X
+     */
+    private static boolean isOSX() {
+        return System.getProperty("os.name").toLowerCase().startsWith("mac os x");
+    }
+
+    /**
+     * Load image with current class loader.
+     *
+     * @param fileName image resource file name
+     * @return image
+     */
+    public static Image loadImage(String fileName) {
+        Image result = null;
+        try {
+            ClassLoader classloader = DavGatewayTray.class.getClassLoader();
+            URL imageUrl = classloader.getResource(fileName);
+            result = ImageIO.read(imageUrl);
+        } catch (IOException e) {
+            DavGatewayTray.warn(new BundleMessage("LOG_UNABLE_TO_LOAD_IMAGE"), e);
+        }
+        return result;
+    }
+
+}
diff --git a/src/java/davmail/ui/tray/DavGatewayTrayInterface.java b/src/java/davmail/ui/tray/DavGatewayTrayInterface.java
new file mode 100644
index 0000000..7220cea
--- /dev/null
+++ b/src/java/davmail/ui/tray/DavGatewayTrayInterface.java
@@ -0,0 +1,71 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.ui.tray;
+
+import org.apache.log4j.Level;
+
+import java.awt.*;
+
+/**
+ * Gateway tray interface common to SWT and pure java implementations
+ */
+public interface DavGatewayTrayInterface {
+    /**
+     * Switch tray icon between active and standby icon.
+     */
+    void switchIcon();
+
+    /**
+     * Reset tray icon to standby
+     */
+    void resetIcon();
+
+    /**
+     * Set tray icon to inactive (network down)
+     */
+    void inactiveIcon();
+
+    /**
+     * Check if current tray status is inactive (network down).
+     *
+     * @return true if inactive
+     */
+    boolean isActive();
+
+    /**
+     * Return AWT Image icon for frame title.
+     *
+     * @return frame icon
+     */
+    Image getFrameIcon();
+
+    /**
+     * Display balloon message for log level.
+     *
+     * @param message text message
+     * @param level   log level
+     */
+    void displayMessage(String message, Level level);
+
+    /**
+     * Create tray icon and register frame listeners.
+     */
+    void init();
+
+}
diff --git a/src/java/davmail/ui/tray/FrameGatewayTray.java b/src/java/davmail/ui/tray/FrameGatewayTray.java
new file mode 100644
index 0000000..622abcb
--- /dev/null
+++ b/src/java/davmail/ui/tray/FrameGatewayTray.java
@@ -0,0 +1,325 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.ui.tray;
+
+import davmail.BundleMessage;
+import davmail.DavGateway;
+import davmail.Settings;
+import davmail.ui.AboutFrame;
+import davmail.ui.SettingsFrame;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.lf5.LF5Appender;
+import org.apache.log4j.lf5.LogLevel;
+import org.apache.log4j.lf5.viewer.LogBrokerMonitor;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+/**
+ * Failover GUI for Java 1.5 without SWT
+ */
+public class FrameGatewayTray implements DavGatewayTrayInterface {
+    protected FrameGatewayTray() {
+    }
+
+    static JFrame mainFrame;
+    static AboutFrame aboutFrame;
+    static SettingsFrame settingsFrame;
+    LogBrokerMonitor logBrokerMonitor;
+    private static JEditorPane errorArea;
+    private static JLabel errorLabel;
+    private static JEditorPane messageArea;
+    private static Image image;
+    private static Image image2;
+    private static Image inactiveImage;
+    private boolean isActive = true;
+
+    /**
+     * Return AWT Image icon for frame title.
+     *
+     * @return frame icon
+     */
+    public Image getFrameIcon() {
+        return image;
+    }
+
+    /**
+     * Switch tray icon between active and standby icon.
+     */
+    public void switchIcon() {
+        isActive = true;
+        SwingUtilities.invokeLater(new Runnable() {
+            public void run() {
+                if (mainFrame.getIconImage().equals(image)) {
+                    mainFrame.setIconImage(image2);
+                } else {
+                    mainFrame.setIconImage(image);
+                }
+            }
+        });
+    }
+
+    /**
+     * Set tray icon to inactive (network down)
+     */
+    public void resetIcon() {
+        SwingUtilities.invokeLater(new Runnable() {
+            public void run() {
+                mainFrame.setIconImage(image);
+            }
+        });
+    }
+
+    /**
+     * Set tray icon to inactive (network down)
+     */
+    public void inactiveIcon() {
+        isActive = false;
+        SwingUtilities.invokeLater(new Runnable() {
+            public void run() {
+                mainFrame.setIconImage(inactiveImage);
+            }
+        });
+    }
+
+    /**
+     * Check if current tray status is inactive (network down).
+     *
+     * @return true if inactive
+     */
+    public boolean isActive() {
+        return isActive;
+    }
+
+    /**
+     * Log and display balloon message according to log level.
+     *
+     * @param message text message
+     * @param level   log level
+     */
+    public void displayMessage(final String message, final Level level) {
+        SwingUtilities.invokeLater(new Runnable() {
+            public void run() {
+                if (errorArea != null && messageArea != null) {
+                    if (level.equals(Level.INFO)) {
+                        errorLabel.setIcon(UIManager.getIcon("OptionPane.informationIcon"));
+                        errorArea.setText(message);
+                    } else if (level.equals(Level.WARN)) {
+                        errorLabel.setIcon(UIManager.getIcon("OptionPane.warningIcon"));
+                        errorArea.setText(message);
+                    } else if (level.equals(Level.ERROR)) {
+                        errorLabel.setIcon(UIManager.getIcon("OptionPane.errorIcon"));
+                        errorArea.setText(message);
+                    }
+                    messageArea.setText(message);
+                }
+            }
+        });
+    }
+
+    /**
+     * Open about window
+     */
+    public void about() {
+        SwingUtilities.invokeLater(new Runnable() {
+            public void run() {
+                aboutFrame.update();
+                aboutFrame.setVisible(true);
+                aboutFrame.toFront();
+                aboutFrame.requestFocus();                
+            }
+        });
+    }
+
+    /**
+     * Open settings window
+     */
+    public void preferences() {
+        SwingUtilities.invokeLater(new Runnable() {
+            public void run() {
+                settingsFrame.reload();
+                settingsFrame.setVisible(true);
+                settingsFrame.toFront();
+                settingsFrame.repaint();
+                settingsFrame.requestFocus();
+            }
+        });
+    }
+
+    /**
+     * Open logging window.
+     */
+    public void showLogs() {
+        Logger rootLogger = Logger.getRootLogger();
+        LF5Appender lf5Appender = (LF5Appender) rootLogger.getAppender("LF5Appender");
+        if (lf5Appender == null) {
+            logBrokerMonitor = new LogBrokerMonitor(LogLevel.getLog4JLevels()) {
+                @Override
+                protected void closeAfterConfirm() {
+                    hide();
+                }
+            };
+            lf5Appender = new LF5Appender(logBrokerMonitor);
+            lf5Appender.setName("LF5Appender");
+            rootLogger.addAppender(lf5Appender);
+        }
+        lf5Appender.getLogBrokerMonitor().show();
+    }
+
+    /**
+     * Create tray icon and register frame listeners.
+     */
+    public void init() {
+        SwingUtilities.invokeLater(new Runnable() {
+            public void run() {
+                createAndShowGUI();
+            }
+        });
+    }
+
+    protected void buildMenu() {
+        // create a popup menu
+        JMenu menu = new JMenu(BundleMessage.format("UI_DAVMAIL_GATEWAY"));
+        JMenuBar menuBar = new JMenuBar();
+        menuBar.add(menu);
+        mainFrame.setJMenuBar(menuBar);
+
+        // create an action settingsListener to listen for settings action executed on the tray icon
+        ActionListener aboutListener = new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                about();
+            }
+        };
+        // create menu item for the default action
+        JMenuItem aboutItem = new JMenuItem(BundleMessage.format("UI_ABOUT"));
+        aboutItem.addActionListener(aboutListener);
+        menu.add(aboutItem);
+
+
+        // create an action settingsListener to listen for settings action executed on the tray icon
+        ActionListener settingsListener = new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                preferences();
+            }
+        };
+        // create menu item for the default action
+        JMenuItem defaultItem = new JMenuItem(BundleMessage.format("UI_SETTINGS"));
+        defaultItem.addActionListener(settingsListener);
+        menu.add(defaultItem);
+
+        JMenuItem logItem = new JMenuItem(BundleMessage.format("UI_SHOW_LOGS"));
+        logItem.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                showLogs();
+            }
+        });
+        menu.add(logItem);
+
+        // create an action exitListener to listen for exit action executed on the tray icon
+        ActionListener exitListener = new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                try {
+                    DavGateway.stop();
+                    // dispose frames
+                    settingsFrame.dispose();
+                    aboutFrame.dispose();
+                    if (logBrokerMonitor != null) {
+                        logBrokerMonitor.dispose();
+                    }
+                } catch (Exception exc) {
+                    DavGatewayTray.error(exc);
+                }
+                // make sure we do exit
+                System.exit(0);
+            }
+        };
+        // create menu item for the exit action
+        JMenuItem exitItem = new JMenuItem(BundleMessage.format("UI_EXIT"));
+        exitItem.addActionListener(exitListener);
+        menu.add(exitItem);
+    }
+
+    protected void createAndShowGUI() {
+        System.setProperty("swing.defaultlaf", UIManager.getSystemLookAndFeelClassName());
+
+        image = DavGatewayTray.loadImage("tray.png");
+        image2 = DavGatewayTray.loadImage(AwtGatewayTray.TRAY_ACTIVE_PNG);
+        inactiveImage = DavGatewayTray.loadImage(AwtGatewayTray.TRAY_INACTIVE_PNG);
+
+        mainFrame = new JFrame();
+        mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
+        mainFrame.setTitle(BundleMessage.format("UI_DAVMAIL_GATEWAY"));
+        mainFrame.setIconImage(image);
+
+        JPanel errorPanel = new JPanel();
+        errorPanel.setBorder(BorderFactory.createTitledBorder(BundleMessage.format("UI_LAST_MESSAGE")));
+        errorPanel.setLayout(new BoxLayout(errorPanel, BoxLayout.X_AXIS));
+        errorArea = new JTextPane();
+        errorArea.setEditable(false);
+        errorArea.setBackground(mainFrame.getBackground());
+        errorLabel = new JLabel();
+        errorPanel.add(errorLabel);
+        errorPanel.add(errorArea);
+
+        JPanel messagePanel = new JPanel();
+        messagePanel.setBorder(BorderFactory.createTitledBorder(BundleMessage.format("UI_LAST_LOG")));
+        messagePanel.setLayout(new BoxLayout(messagePanel, BoxLayout.X_AXIS));
+
+        messageArea = new JTextPane();
+        messageArea.setText(BundleMessage.format("LOG_STARTING_DAVMAIL"));
+        messageArea.setEditable(false);
+        messageArea.setBackground(mainFrame.getBackground());
+        messagePanel.add(messageArea);
+
+        JPanel mainPanel = new JPanel();
+        mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
+        mainPanel.add(errorPanel);
+        mainPanel.add(messagePanel);
+        mainFrame.add(mainPanel);
+
+        aboutFrame = new AboutFrame();
+        settingsFrame = new SettingsFrame();
+        buildMenu();
+
+        mainFrame.setMinimumSize(new Dimension(400, 180));
+        mainFrame.pack();
+        // workaround MacOSX
+        if (mainFrame.getSize().width < 400 || mainFrame.getSize().height < 180) {
+            mainFrame.setSize(Math.max(mainFrame.getSize().width, 400),
+                    Math.max(mainFrame.getSize().height, 180));
+        }
+        // center frame
+        mainFrame.setLocation(mainFrame.getToolkit().getScreenSize().width / 2 -
+                mainFrame.getSize().width / 2,
+                mainFrame.getToolkit().getScreenSize().height / 2 -
+                        mainFrame.getSize().height / 2);
+        mainFrame.setVisible(true);
+
+        // display settings frame on first start
+        if (Settings.isFirstStart()) {
+            settingsFrame.setVisible(true);
+            settingsFrame.toFront();
+            settingsFrame.repaint();
+            settingsFrame.requestFocus();
+        }
+    }
+}
diff --git a/src/java/davmail/ui/tray/OSXAwtGatewayTray.java b/src/java/davmail/ui/tray/OSXAwtGatewayTray.java
new file mode 100644
index 0000000..9eff9c0
--- /dev/null
+++ b/src/java/davmail/ui/tray/OSXAwtGatewayTray.java
@@ -0,0 +1,133 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.ui.tray;
+
+import davmail.BundleMessage;
+import davmail.DavGateway;
+import davmail.ui.OSXAdapter;
+import info.growl.Growl;
+import info.growl.GrowlException;
+import info.growl.GrowlUtils;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
+
+/**
+ * Extended Awt tray with OSX extensions.
+ */
+public class OSXAwtGatewayTray extends AwtGatewayTray {
+    protected static final String OSX_TRAY_ACTIVE_PNG = "osxtray2.png";
+    protected static final String OSX_TRAY_PNG = "osxtray.png";
+    protected static final String OSX_TRAY_INACTIVE_PNG = "osxtrayinactive.png";
+
+    private static final Logger LOGGER = Logger.getLogger(OSXAwtGatewayTray.class);
+
+    /**
+     * Exit DavMail Gateway.
+     *
+     * @return true
+     */
+    @SuppressWarnings({"SameReturnValue", "UnusedDeclaration"})
+    public boolean quit() {
+        DavGateway.stop();
+        // dispose frames
+        settingsFrame.dispose();
+        aboutFrame.dispose();
+        if (logBrokerMonitor != null) {
+            logBrokerMonitor.dispose();
+        }
+        return true;
+    }
+
+    @Override
+    protected void createAndShowGUI() {
+        System.setProperty("apple.laf.useScreenMenuBar", "true");
+        super.createAndShowGUI();
+        trayIcon.removeActionListener(settingsListener);
+        try {
+            OSXAdapter.setAboutHandler(this, AwtGatewayTray.class.getDeclaredMethod("about", (Class[]) null));
+            OSXAdapter.setPreferencesHandler(this, AwtGatewayTray.class.getDeclaredMethod("preferences", (Class[]) null));
+            OSXAdapter.setQuitHandler(this, OSXAwtGatewayTray.class.getDeclaredMethod("quit", (Class[]) null));
+        } catch (Exception e) {
+            DavGatewayTray.error(new BundleMessage("LOG_ERROR_LOADING_OSXADAPTER"), e);
+        }
+    }
+
+    @Override
+    protected String getTrayIconPath() {
+        return OSXAwtGatewayTray.OSX_TRAY_PNG;
+    }
+
+    @Override
+    protected String getTrayIconActivePath() {
+        return OSXAwtGatewayTray.OSX_TRAY_ACTIVE_PNG;
+    }
+
+    @Override
+    protected String getTrayIconInactivePath() {
+        return OSXAwtGatewayTray.OSX_TRAY_INACTIVE_PNG;
+    }
+
+    @Override
+    public void displayMessage(final String message, final Level level) {
+        if (!GrowlUtils.isGrowlLoaded()) {
+            super.displayMessage(message, level);
+        } else {
+            SwingUtilities.invokeLater(new Runnable() {
+                public void run() {
+                    if (trayIcon != null) {
+                        Icon icon = null;
+                        if (level.equals(Level.INFO)) {
+                            icon = UIManager.getIcon("OptionPane.informationIcon");
+                        } else if (level.equals(Level.WARN)) {
+                            icon = UIManager.getIcon("OptionPane.warningIcon");
+                        } else if (level.equals(Level.ERROR)) {
+                            icon = UIManager.getIcon("OptionPane.errorIcon");
+                        }
+
+                        if (icon != null && message != null && message.length() > 0) {
+                            try {
+                                String title = BundleMessage.format("UI_DAVMAIL_GATEWAY");
+                                Growl growl = GrowlUtils.getGrowlInstance("DavMail");
+                                growl.addNotification(title, true);
+                                growl.register();
+                                growl.sendNotification(title, title, message, (RenderedImage) getImageForIcon(icon));
+                            } catch (GrowlException growlException) {
+                                LOGGER.error(growlException);
+                            }
+                        }
+                        trayIcon.setToolTip(BundleMessage.format("UI_DAVMAIL_GATEWAY") + '\n' + message);
+                    }
+                }
+            });
+        }
+    }
+
+    protected Image getImageForIcon(Icon icon) {
+        BufferedImage bufferedimage = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);
+        Graphics g = bufferedimage.getGraphics();
+        icon.paintIcon(null, g, 0, 0);
+        g.dispose();
+        return bufferedimage;
+    }
+}
diff --git a/src/java/davmail/ui/tray/OSXFrameGatewayTray.java b/src/java/davmail/ui/tray/OSXFrameGatewayTray.java
new file mode 100644
index 0000000..c84058e
--- /dev/null
+++ b/src/java/davmail/ui/tray/OSXFrameGatewayTray.java
@@ -0,0 +1,81 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.ui.tray;
+
+import davmail.BundleMessage;
+import davmail.DavGateway;
+import davmail.ui.OSXAdapter;
+
+import javax.swing.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+/**
+ * MacOSX specific frame to handle menu
+ */
+public class OSXFrameGatewayTray extends FrameGatewayTray {
+
+    /**
+     * Exit DavMail Gateway.
+     *
+     * @return true
+     */
+    @SuppressWarnings({"SameReturnValue", "UnusedDeclaration"})
+    public boolean quit() {
+        DavGateway.stop();
+        // dispose frames
+        settingsFrame.dispose();
+        aboutFrame.dispose();
+        if (logBrokerMonitor != null) {
+            logBrokerMonitor.dispose();
+        }
+        return true;
+    }
+
+    @Override
+    protected void buildMenu() {
+        // create a popup menu
+        JMenu menu = new JMenu(BundleMessage.format("UI_LOGS"));
+        JMenuBar menuBar = new JMenuBar();
+        menuBar.add(menu);
+        mainFrame.setJMenuBar(menuBar);
+
+        JMenuItem logItem = new JMenuItem(BundleMessage.format("UI_SHOW_LOGS"));
+        logItem.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                showLogs();
+            }
+        });
+        menu.add(logItem);
+    }
+
+
+    @Override
+    protected void createAndShowGUI() {
+        System.setProperty("apple.laf.useScreenMenuBar", "true");
+        super.createAndShowGUI();
+        try {
+            OSXAdapter.setAboutHandler(this, FrameGatewayTray.class.getDeclaredMethod("about", (Class[]) null));
+            OSXAdapter.setPreferencesHandler(this, FrameGatewayTray.class.getDeclaredMethod("preferences", (Class[]) null));
+            OSXAdapter.setQuitHandler(this, OSXFrameGatewayTray.class.getDeclaredMethod("quit", (Class[]) null));
+        } catch (Exception e) {
+            DavGatewayTray.error(new BundleMessage("LOG_ERROR_LOADING_OSXADAPTER"), e);
+        }
+    }
+}
diff --git a/src/java/davmail/ui/tray/SwtGatewayTray.java b/src/java/davmail/ui/tray/SwtGatewayTray.java
new file mode 100644
index 0000000..09b9d9e
--- /dev/null
+++ b/src/java/davmail/ui/tray/SwtGatewayTray.java
@@ -0,0 +1,393 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.ui.tray;
+
+import davmail.BundleMessage;
+import davmail.DavGateway;
+import davmail.Settings;
+import davmail.ui.AboutFrame;
+import davmail.ui.SettingsFrame;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.lf5.LF5Appender;
+import org.apache.log4j.lf5.LogLevel;
+import org.apache.log4j.lf5.viewer.LogBrokerMonitor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.internal.gtk.OS;
+import org.eclipse.swt.widgets.*;
+
+import javax.swing.*;
+import java.io.IOException;
+import java.net.URL;
+
+/**
+ * Tray icon handler based on SWT
+ */
+public class SwtGatewayTray implements DavGatewayTrayInterface {
+    protected SwtGatewayTray() {
+    }
+
+    SettingsFrame settingsFrame;
+    AboutFrame aboutFrame;
+
+    private static TrayItem trayItem;
+    private static java.awt.Image awtImage;
+    private static Image image;
+    private static Image image2;
+    private static Image inactiveImage;
+    private static Display display;
+    private static Shell shell;
+    private LogBrokerMonitor logBrokerMonitor;
+    private boolean isActive = true;
+    private boolean isReady;
+
+    private final Thread mainThread = Thread.currentThread();
+
+    /**
+     * Return AWT Image icon for frame title.
+     *
+     * @return frame icon
+     */
+    public java.awt.Image getFrameIcon() {
+        return awtImage;
+    }
+
+    /**
+     * Switch tray icon between active and standby icon.
+     */
+    public void switchIcon() {
+        isActive = true;
+        display.syncExec(new Runnable() {
+            public void run() {
+                if (trayItem.getImage().equals(image)) {
+                    trayItem.setImage(image2);
+                } else {
+                    trayItem.setImage(image);
+                }
+            }
+        });
+
+    }
+
+    /**
+     * Set tray icon to inactive (network down)
+     */
+    public void resetIcon() {
+        display.syncExec(new Runnable() {
+            public void run() {
+                trayItem.setImage(image);
+            }
+        });
+    }
+
+    /**
+     * Set tray icon to inactive (network down)
+     */
+    public void inactiveIcon() {
+        isActive = false;
+        display.syncExec(new Runnable() {
+            public void run() {
+                trayItem.setImage(inactiveImage);
+            }
+        });
+    }
+
+    /**
+     * Check if current tray status is inactive (network down).
+     *
+     * @return true if inactive
+     */
+    public boolean isActive() {
+        return isActive;
+    }
+
+    /**
+     * Log and display balloon message according to log level.
+     *
+     * @param message text message
+     * @param level   log level
+     */
+    public void displayMessage(final String message, final Level level) {
+        if (trayItem != null) {
+            display.asyncExec(new Runnable() {
+                public void run() {
+                    int messageType = 0;
+                    if (level.equals(Level.INFO)) {
+                        messageType = SWT.ICON_INFORMATION;
+                    } else if (level.equals(Level.WARN)) {
+                        messageType = SWT.ICON_WARNING;
+                    } else if (level.equals(Level.ERROR)) {
+                        messageType = SWT.ICON_ERROR;
+                    }
+                    if (messageType != 0) {
+                        final ToolTip toolTip = new ToolTip(shell, SWT.BALLOON | messageType);
+                        toolTip.setText(BundleMessage.format("UI_DAVMAIL_GATEWAY"));
+                        toolTip.setMessage(message);
+                        trayItem.setToolTip(toolTip);
+                        toolTip.setVisible(true);
+                    }
+                    trayItem.setToolTipText(BundleMessage.format("UI_DAVMAIL_GATEWAY") + '\n' + message);
+                }
+            });
+        }
+    }
+
+    /**
+     * Load image with current class loader.
+     *
+     * @param fileName image resource file name
+     * @return image
+     */
+    public static Image loadSwtImage(String fileName) {
+        Image result = null;
+        try {
+            ClassLoader classloader = DavGatewayTray.class.getClassLoader();
+            URL imageUrl = classloader.getResource(fileName);
+            result = new Image(display, imageUrl.openStream());
+        } catch (IOException e) {
+            DavGatewayTray.warn(new BundleMessage("LOG_UNABLE_TO_LOAD_IMAGE"), e);
+        }
+        return result;
+    }
+
+    /**
+     * Create tray icon and register frame listeners.
+     */
+    public void init() {
+        // register error handler to avoid application crash on concurrent X access from SWT and AWT
+        try {
+            OS.gdk_error_trap_push();
+        } catch (NoClassDefFoundError e) {
+            // ignore
+        }
+        final String systemLookAndFeelClassName = UIManager.getSystemLookAndFeelClassName();
+        try {
+            // workaround for bug when SWT and AWT both try to access Gtk
+            if (systemLookAndFeelClassName.indexOf("gtk") >= 0) {
+                System.setProperty("swing.defaultlaf", UIManager.getCrossPlatformLookAndFeelClassName());
+            } else {
+                System.setProperty("swing.defaultlaf", systemLookAndFeelClassName);
+            }
+        } catch (Exception e) {
+            DavGatewayTray.warn(new BundleMessage("LOG_UNABLE_TO_SET_LOOK_AND_FEEL"));
+        }
+
+        new Thread("SWT") {
+            @Override
+            public void run() {
+                try {
+                    display = new Display();
+                    shell = new Shell(display);
+
+                    final Tray tray = display.getSystemTray();
+                    if (tray != null) {
+
+                        trayItem = new TrayItem(tray, SWT.NONE);
+                        trayItem.setToolTipText(BundleMessage.format("UI_DAVMAIL_GATEWAY"));
+
+                        awtImage = DavGatewayTray.loadImage(AwtGatewayTray.TRAY_PNG);
+                        image = loadSwtImage(AwtGatewayTray.TRAY_PNG);
+                        image2 = loadSwtImage(AwtGatewayTray.TRAY_ACTIVE_PNG);
+                        inactiveImage = loadSwtImage(AwtGatewayTray.TRAY_INACTIVE_PNG);
+
+                        trayItem.setImage(image);
+
+                        // create a popup menu
+                        final Menu popup = new Menu(shell, SWT.POP_UP);
+                        trayItem.addListener(SWT.MenuDetect, new Listener() {
+                            public void handleEvent(Event event) {
+                                display.asyncExec(
+                                        new Runnable() {
+                                            public void run() {
+                                                popup.setVisible(true);
+                                            }
+                                        });
+                            }
+                        });
+
+                        MenuItem aboutItem = new MenuItem(popup, SWT.PUSH);
+                        aboutItem.setText(BundleMessage.format("UI_ABOUT"));
+                        aboutItem.addListener(SWT.Selection, new Listener() {
+                            public void handleEvent(Event event) {
+                                display.asyncExec(
+                                        new Runnable() {
+                                            public void run() {
+                                                if (aboutFrame == null) {
+                                                    aboutFrame = new AboutFrame();
+                                                }
+                                                aboutFrame.update();
+                                                aboutFrame.setVisible(true);
+                                                aboutFrame.toFront();
+                                                aboutFrame.requestFocus();
+                                            }
+                                        });
+                            }
+                        });
+
+                        trayItem.addListener(SWT.DefaultSelection, new Listener() {
+                            public void handleEvent(Event event) {
+                                display.asyncExec(
+                                        new Runnable() {
+                                            public void run() {
+                                                // create frame on first call
+                                                if (settingsFrame == null) {
+                                                    settingsFrame = new SettingsFrame();
+                                                }
+                                                settingsFrame.reload();
+                                                settingsFrame.setVisible(true);
+                                                settingsFrame.toFront();
+                                                settingsFrame.requestFocus();
+                                            }
+                                        });
+                            }
+                        });
+
+                        // create menu item for the default action
+                        MenuItem defaultItem = new MenuItem(popup, SWT.PUSH);
+                        defaultItem.setText(BundleMessage.format("UI_SETTINGS"));
+                        defaultItem.addListener(SWT.Selection, new Listener() {
+                            public void handleEvent(Event event) {
+                                display.asyncExec(
+                                        new Runnable() {
+                                            public void run() {
+                                                // create frame on first call
+                                                if (settingsFrame == null) {
+                                                    settingsFrame = new SettingsFrame();
+                                                }
+                                                settingsFrame.reload();
+                                                settingsFrame.setVisible(true);
+                                                settingsFrame.toFront();
+                                                settingsFrame.requestFocus();
+                                            }
+                                        });
+                            }
+                        });
+
+                        MenuItem logItem = new MenuItem(popup, SWT.PUSH);
+                        logItem.setText(BundleMessage.format("UI_SHOW_LOGS"));
+                        logItem.addListener(SWT.Selection, new Listener() {
+                            public void handleEvent(Event event) {
+                                display.asyncExec(
+                                        new Runnable() {
+                                            public void run() {
+
+                                                Logger rootLogger = Logger.getRootLogger();
+                                                LF5Appender lf5Appender = (LF5Appender) rootLogger.getAppender("LF5Appender");
+                                                if (lf5Appender == null) {
+                                                    logBrokerMonitor = new LogBrokerMonitor(LogLevel.getLog4JLevels()) {
+                                                        @Override
+                                                        protected void closeAfterConfirm() {
+                                                            hide();
+                                                        }
+                                                    };
+                                                    lf5Appender = new LF5Appender(logBrokerMonitor);
+                                                    lf5Appender.setName("LF5Appender");
+                                                    rootLogger.addAppender(lf5Appender);
+                                                }
+                                                lf5Appender.getLogBrokerMonitor().show();
+                                            }
+                                        });
+                            }
+                        });
+
+                        MenuItem exitItem = new MenuItem(popup, SWT.PUSH);
+                        exitItem.setText(BundleMessage.format("UI_EXIT"));
+                        exitItem.addListener(SWT.Selection, new Listener() {
+                            public void handleEvent(Event event) {
+                                DavGateway.stop();
+                                shell.dispose();
+                            }
+                        });
+
+                        // display settings frame on first start
+                        if (Settings.isFirstStart()) {
+                            // create frame on first call
+                            if (settingsFrame == null) {
+                                settingsFrame = new SettingsFrame();
+                            }
+                            settingsFrame.setVisible(true);
+                            settingsFrame.toFront();
+                            settingsFrame.requestFocus();
+                        }
+
+                        synchronized (mainThread) {
+                            // ready
+                            isReady = true;
+                            mainThread.notifyAll();
+                        }
+
+                        while (!shell.isDisposed()) {
+                            if (!display.readAndDispatch()) {
+                                display.sleep();
+                            }
+                        }
+
+                        if (trayItem != null) {
+                            trayItem.dispose();
+                            trayItem = null;
+                        }
+
+                        if (image != null) {
+                            image.dispose();
+                        }
+                        if (image2 != null) {
+                            image2.dispose();
+                        }
+                        try {
+                            if (!display.isDisposed()) {
+                                display.dispose();
+                            }
+                        } catch (Exception e) {
+                            // already disposed
+                        }
+                        // dispose AWT frames
+                        if (settingsFrame != null) {
+                            settingsFrame.dispose();
+                        }
+                        if (aboutFrame != null) {
+                            aboutFrame.dispose();
+                        }
+                        if (logBrokerMonitor != null) {
+                            logBrokerMonitor.dispose();
+                        }
+                    }
+                } catch (Exception exc) {
+                    DavGatewayTray.error(exc);
+                }
+                // make sure we do exit
+                System.exit(0);
+            }
+        }.start();
+        while (true) {
+            // wait for SWT init
+            try {
+                synchronized (mainThread) {
+                    if (isReady) {
+                        break;
+                    }
+                    mainThread.wait(1000);
+                }
+            } catch (InterruptedException e) {
+                DavGatewayTray.error(new BundleMessage("LOG_ERROR_WAITING_FOR_SWT_INIT"), e);
+            }
+        }
+    }
+
+}
diff --git a/src/java/davmail/util/IOUtil.java b/src/java/davmail/util/IOUtil.java
new file mode 100644
index 0000000..5a9f060
--- /dev/null
+++ b/src/java/davmail/util/IOUtil.java
@@ -0,0 +1,101 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.util;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.*;
+
+/**
+ * Input output functions.
+ */
+public final class IOUtil {
+    private IOUtil() {
+    }
+
+    /**
+     * Write all inputstream content to outputstream.
+     *
+     * @param inputStream  input stream
+     * @param outputStream output stream
+     * @throws IOException on error
+     */
+    public static void write(InputStream inputStream, OutputStream outputStream) throws IOException {
+        byte[] bytes = new byte[8192];
+        int length;
+        while ((length = inputStream.read(bytes)) > 0) {
+            outputStream.write(bytes, 0, length);
+        }
+    }
+
+
+    /**
+     * Resize image bytes to a max width or height image size.
+     *
+     * @param inputBytes input image bytes
+     * @param max        max size
+     * @return scaled image bytes
+     * @throws IOException on error
+     */
+    public static byte[] resizeImage(byte[] inputBytes, int max) throws IOException {
+        BufferedImage inputImage = ImageIO.read(new ByteArrayInputStream(inputBytes));
+        BufferedImage outputImage = resizeImage(inputImage, max);
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        ImageIO.write(outputImage, "jpg", baos);
+        return baos.toByteArray();
+    }
+
+    /**
+     * Resize image to a max width or height image size.
+     *
+     * @param inputImage input image
+     * @param max        max size
+     * @return scaled image
+     */
+    public static BufferedImage resizeImage(BufferedImage inputImage, int max) {
+        int width = inputImage.getWidth();
+        int height = inputImage.getHeight();
+        int targetWidth;
+        int targetHeight;
+        if (width <= max && height <= max) {
+            return inputImage;
+        } else if (width > height) {
+            targetWidth = max;
+            targetHeight = targetWidth * height / width;
+        } else {
+            targetHeight = max;
+            targetWidth = targetHeight * width / height;
+        }
+        Image scaledImage = inputImage.getScaledInstance(targetWidth, targetHeight, Image.SCALE_SMOOTH);
+        BufferedImage targetImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB);
+        targetImage.getGraphics().drawImage(scaledImage, 0, 0, null);
+        return targetImage;
+    }
+
+    public static byte[] readFully(InputStream inputStream) throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        byte[] bytes = new byte[8192];
+        int length;
+        while ((length = inputStream.read(bytes)) > 0) {
+            baos.write(bytes, 0, length);
+        }
+        return baos.toByteArray();
+    }
+}
diff --git a/src/java/davmail/util/StringUtil.java b/src/java/davmail/util/StringUtil.java
new file mode 100644
index 0000000..9e6a4e4
--- /dev/null
+++ b/src/java/davmail/util/StringUtil.java
@@ -0,0 +1,507 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.util;
+
+import org.apache.commons.codec.DecoderException;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.binary.Hex;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+/**
+ * Various string handling methods
+ */
+public final class StringUtil {
+    private StringUtil() {
+    }
+
+    /**
+     * Return the sub string between startDelimiter and endDelimiter or null.
+     *
+     * @param value          String value
+     * @param startDelimiter start delimiter
+     * @param endDelimiter   end delimiter
+     * @return token value
+     */
+    public static String getToken(String value, String startDelimiter, String endDelimiter) {
+        String token = null;
+        if (value != null) {
+            int startIndex = value.indexOf(startDelimiter);
+            if (startIndex >= 0) {
+                startIndex += startDelimiter.length();
+                int endIndex = value.indexOf(endDelimiter, startIndex);
+                if (endIndex >= 0) {
+                    token = value.substring(startIndex, endIndex);
+                }
+            }
+        }
+        return token;
+    }
+
+    /**
+     * Return the sub string between startDelimiter and endDelimiter or null,
+     * look for last token in string.
+     *
+     * @param value          String value
+     * @param startDelimiter start delimiter
+     * @param endDelimiter   end delimiter
+     * @return token value
+     */
+    public static String getLastToken(String value, String startDelimiter, String endDelimiter) {
+        String token = null;
+        if (value != null) {
+            int startIndex = value.lastIndexOf(startDelimiter);
+            if (startIndex >= 0) {
+                startIndex += startDelimiter.length();
+                int endIndex = value.indexOf(endDelimiter, startIndex);
+                if (endIndex >= 0) {
+                    token = value.substring(startIndex, endIndex);
+                }
+            }
+        }
+        return token;
+    }
+
+    /**
+     * Return the sub string between startDelimiter and endDelimiter with newToken.
+     *
+     * @param value          String value
+     * @param startDelimiter start delimiter
+     * @param endDelimiter   end delimiter
+     * @param newToken       new token value
+     * @return token value
+     */
+    public static String replaceToken(String value, String startDelimiter, String endDelimiter, String newToken) {
+        String result = null;
+        if (value != null) {
+            int startIndex = value.indexOf(startDelimiter);
+            if (startIndex >= 0) {
+                startIndex += startDelimiter.length();
+                int endIndex = value.indexOf(endDelimiter, startIndex);
+                if (endIndex >= 0) {
+                    result = value.substring(0, startIndex) + newToken + value.substring(endIndex);
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Join values with given separator.
+     *
+     * @param values    value set
+     * @param separator separator
+     * @return joined values
+     */
+    public static String join(Set<String> values, String separator) {
+        if (values != null && !values.isEmpty()) {
+            StringBuilder result = new StringBuilder();
+            for (String value : values) {
+                if (result.length() > 0) {
+                    result.append(separator);
+                }
+                result.append(value);
+            }
+            return result.toString();
+        } else {
+            return null;
+        }
+    }
+
+    private static final Pattern AMP_PATTERN = Pattern.compile("&");
+    private static final Pattern LT_PATTERN = Pattern.compile("<");
+    private static final Pattern GT_PATTERN = Pattern.compile(">");
+    private static final Pattern PERCENT_PATTERN = Pattern.compile("%");
+    private static final Pattern HASH_PATTERN = Pattern.compile("#");
+    private static final Pattern STAR_PATTERN = Pattern.compile("\\*");
+
+    private static final Pattern QUOTE_PATTERN = Pattern.compile("\"");
+    private static final Pattern CR_PATTERN = Pattern.compile("\r");
+    private static final Pattern LF_PATTERN = Pattern.compile("\n");
+
+    private static final Pattern URLENCODED_F8FF_PATTERN = Pattern.compile(String.valueOf((char) 0xF8FF));
+    private static final Pattern URLENCODED_AMP_PATTERN = Pattern.compile("%26");
+    private static final Pattern URLENCODED_PLUS_PATTERN = Pattern.compile("%2B");
+    private static final Pattern URLENCODED_COLON_PATTERN = Pattern.compile("%3A");
+    private static final Pattern URLENCODED_LT_PATTERN = Pattern.compile("%3C");
+    private static final Pattern URLENCODED_GT_PATTERN = Pattern.compile("%3E");
+    private static final Pattern URLENCODED_QUOTE_PATTERN = Pattern.compile("%22");
+    private static final Pattern URLENCODED_X0D0A_PATTERN = Pattern.compile("\n");
+    private static final Pattern URLENCODED_PERCENT_PATTERN = Pattern.compile("%25");
+    private static final Pattern URLENCODED_HASH_PATTERN = Pattern.compile("%23");
+    private static final Pattern URLENCODED_STAR_PATTERN = Pattern.compile("%2A");
+    private static final Pattern URLENCODED_PIPE_PATTERN = Pattern.compile("%7C");
+    private static final Pattern URLENCODED_QUESTION_PATTERN = Pattern.compile("%3F");
+
+    private static final Pattern ENCODED_AMP_PATTERN = Pattern.compile("&");
+    private static final Pattern ENCODED_LT_PATTERN = Pattern.compile("<");
+    private static final Pattern ENCODED_GT_PATTERN = Pattern.compile(">");
+
+    private static final Pattern F8FF_PATTERN = Pattern.compile("_xF8FF_");
+    private static final Pattern X0D0A_PATTERN = Pattern.compile("_x000D__x000A_");
+
+    private static final Pattern PLUS_PATTERN = Pattern.compile("\\+");
+    private static final Pattern COLON_PATTERN = Pattern.compile(":");
+    private static final Pattern SLASH_PATTERN = Pattern.compile("/");
+    private static final Pattern UNDERSCORE_PATTERN = Pattern.compile("_");
+    private static final Pattern DASH_PATTERN = Pattern.compile("-");
+    private static final Pattern PIPE_PATTERN = Pattern.compile("\\|");
+    private static final Pattern QUESTION_PATTERN = Pattern.compile("\\?");
+
+    // WebDav search parameter encode
+    private static final Pattern APOS_PATTERN = Pattern.compile("'");
+
+    /**
+     * Encode & to %26 for urlcompname.
+     *
+     * @param name decoded name
+     * @return name encoded name
+     */
+    public static String urlEncodeAmpersand(String name) {
+        String result = name;
+        if (name.indexOf('&') >= 0) {
+            result = AMP_PATTERN.matcher(result).replaceAll("%26");
+        }
+        return result;
+    }
+
+    /**
+     * Decode %26 to & for urlcompname.
+     *
+     * @param name decoded name
+     * @return name encoded name
+     */
+    public static String urlDecodeAmpersand(String name) {
+        String result = name;
+        if (name != null && name.indexOf("%26") >= 0) {
+            result = URLENCODED_AMP_PATTERN.matcher(result).replaceAll("&");
+        }
+        return result;
+    }
+
+    /**
+     * Xml encode content.
+     *
+     * @param name decoded name
+     * @return name encoded name
+     */
+    public static String xmlEncode(String name) {
+        String result = name;
+        if (name != null) {
+            if (name.indexOf('&') >= 0) {
+                result = AMP_PATTERN.matcher(result).replaceAll("&");
+            }
+            if (name.indexOf('<') >= 0) {
+                result = LT_PATTERN.matcher(result).replaceAll("<");
+            }
+            if (name.indexOf('>') >= 0) {
+                result = GT_PATTERN.matcher(result).replaceAll(">");
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Xml encode inside attribute.
+     *
+     * @param name decoded name
+     * @return name encoded name
+     */
+    public static String xmlEncodeAttribute(String name) {
+        String result = xmlEncode(name);
+        if (result != null) {
+            if (result.indexOf('"') >= 0) {
+                result = QUOTE_PATTERN.matcher(result).replaceAll("&#x22;");
+            }
+            if (result.indexOf('\r') >= 0) {
+                result = CR_PATTERN.matcher(result).replaceAll("&#x0D;");
+            }
+            if (result.indexOf('\n') >= 0) {
+                result = LF_PATTERN.matcher(result).replaceAll("&#x0A;");
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Need to decode xml for iCal
+     *
+     * @param name encoded name
+     * @return name decoded name
+     */
+    public static String xmlDecode(String name) {
+        String result = name;
+        if (name.indexOf("&") >= 0) {
+            result = ENCODED_AMP_PATTERN.matcher(result).replaceAll("&");
+        }
+        if (name.indexOf("<") >= 0) {
+            result = ENCODED_LT_PATTERN.matcher(result).replaceAll("<");
+        }
+        if (name.indexOf(">") >= 0) {
+            result = ENCODED_GT_PATTERN.matcher(result).replaceAll(">");
+        }
+        return result;
+    }
+
+    /**
+     * Convert base64 value to hex.
+     *
+     * @param value base64 value
+     * @return hex value
+     */
+    public static String base64ToHex(String value) {
+        String hexValue = null;
+        if (value != null) {
+            hexValue = new String(Hex.encodeHex(Base64.decodeBase64(value.getBytes())));
+        }
+        return hexValue;
+    }
+
+    /**
+     * Convert hex value to base64.
+     *
+     * @param value hex value
+     * @return base64 value
+     * @throws DecoderException on error
+     */
+    public static String hexToBase64(String value) throws DecoderException {
+        String base64Value = null;
+        if (value != null) {
+            base64Value = new String(Base64.encodeBase64(Hex.decodeHex(value.toCharArray())));
+        }
+        return base64Value;
+    }
+
+    /**
+     * Encode item name to get actual value stored in urlcompname MAPI property.
+     *
+     * @param value decoded value
+     * @return urlcompname encoded value
+     */
+    public static String encodeUrlcompname(String value) {
+        String result = value;
+        if (result.indexOf('%') >= 0) {
+            result = PERCENT_PATTERN.matcher(result).replaceAll("%25");
+        }
+        if (result.indexOf("_xF8FF_") >= 0) {
+            result = F8FF_PATTERN.matcher(result).replaceAll(String.valueOf((char) 0xF8FF));
+        }
+        if (result.indexOf('&') >= 0) {
+            result = AMP_PATTERN.matcher(result).replaceAll("%26");
+        }
+        if (result.indexOf('+') >= 0) {
+            result = PLUS_PATTERN.matcher(result).replaceAll("%2B");
+        }
+        if (result.indexOf(':') >= 0) {
+            result = COLON_PATTERN.matcher(result).replaceAll("%3A");
+        }
+        if (result.indexOf('<') >= 0) {
+            result = LT_PATTERN.matcher(result).replaceAll("%3C");
+        }
+        if (result.indexOf('>') >= 0) {
+            result = GT_PATTERN.matcher(result).replaceAll("%3E");
+        }
+        if (result.indexOf('"') >= 0) {
+            result = QUOTE_PATTERN.matcher(result).replaceAll("%22");
+        }
+        if (result.indexOf('#') >= 0) {
+            result = HASH_PATTERN.matcher(result).replaceAll("%23");
+        }
+        if (result.indexOf('*') >= 0) {
+            result = STAR_PATTERN.matcher(result).replaceAll("%2A");
+        }
+        if (result.indexOf("_x000D__x000A_") >= 0) {
+            result = X0D0A_PATTERN.matcher(result).replaceAll("\r\n");
+        }
+        if (result.indexOf('|') >= 0) {
+            result = PIPE_PATTERN.matcher(result).replaceAll("%7C");
+        }
+        if (result.indexOf('?') >= 0) {
+            result = QUESTION_PATTERN.matcher(result).replaceAll("%3F");
+        }
+        return result;
+    }
+
+    /**
+     * Decode urlcompname to get item name.
+     *
+     * @param urlcompname encoded value
+     * @return decoded value
+     */
+    public static String decodeUrlcompname(String urlcompname) {
+        String result = urlcompname;
+        if (result != null) {
+            if (result.indexOf((char) 0xF8FF) >= 0) {
+                result = URLENCODED_F8FF_PATTERN.matcher(result).replaceAll("_xF8FF_");
+            }
+            if (result.indexOf("%26") >= 0) {
+                result = URLENCODED_AMP_PATTERN.matcher(result).replaceAll("&");
+            }
+            if (result.indexOf("%2B") >= 0) {
+                result = URLENCODED_PLUS_PATTERN.matcher(result).replaceAll("+");
+            }
+            if (result.indexOf("%3A") >= 0) {
+                result = URLENCODED_COLON_PATTERN.matcher(result).replaceAll(":");
+            }
+            if (result.indexOf("%3C") >= 0) {
+                result = URLENCODED_LT_PATTERN.matcher(result).replaceAll("<");
+            }
+            if (result.indexOf("%3E") >= 0) {
+                result = URLENCODED_GT_PATTERN.matcher(result).replaceAll(">");
+            }
+            if (result.indexOf("%22") >= 0) {
+                result = URLENCODED_QUOTE_PATTERN.matcher(result).replaceAll("\"");
+            }
+            // CRLF is replaced with LF in response
+            if (result.indexOf('\n') >= 0) {
+                result = URLENCODED_X0D0A_PATTERN.matcher(result).replaceAll("_x000D__x000A_");
+            }
+            if (result.indexOf("%23") >= 0) {
+                result = URLENCODED_HASH_PATTERN.matcher(result).replaceAll("#");
+            }
+            if (result.indexOf("%2A") >= 0) {
+                result = URLENCODED_STAR_PATTERN.matcher(result).replaceAll("*");
+            }
+            if (result.indexOf("%7C") >= 0) {
+                result = URLENCODED_PIPE_PATTERN.matcher(result).replaceAll("|");
+            }
+            if (result.indexOf("%3F") >= 0) {
+                result = URLENCODED_QUESTION_PATTERN.matcher(result).replaceAll("?");
+            }
+            // last replace %
+            if (result.indexOf("%25") >= 0) {
+                result = URLENCODED_PERCENT_PATTERN.matcher(result).replaceAll("%");
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Urlencode plus sign in encoded href.
+     * '+' is decoded as ' ' by URIUtil.decode, the workaround is to force urlencoding to '%2B' first
+     *
+     * @param value encoded href
+     * @return encoded href
+     */
+    public static String encodePlusSign(String value) {
+        String result = value;
+        if (result.indexOf('+') >= 0) {
+            result = PLUS_PATTERN.matcher(result).replaceAll("%2B");
+        }
+        return result;
+    }
+
+    /**
+     * Encode EWS base64 itemId to url compatible value.
+     *
+     * @param value base64 value
+     * @return url compatible value
+     */
+    public static String base64ToUrl(String value) {
+        String result = value;
+        if (result != null) {
+            if (result.indexOf('+') >= 0) {
+                result = PLUS_PATTERN.matcher(result).replaceAll("-");
+            }
+            if (result.indexOf('/') >= 0) {
+                result = SLASH_PATTERN.matcher(result).replaceAll("_");
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Encode EWS url compatible itemId back to base64 value.
+     *
+     * @param value url compatible value
+     * @return base64 value
+     */
+    public static String urlToBase64(String value) {
+        String result = value;
+        if (result.indexOf('-') >= 0) {
+            result = DASH_PATTERN.matcher(result).replaceAll("+");
+        }
+        if (result.indexOf('_') >= 0) {
+            result = UNDERSCORE_PATTERN.matcher(result).replaceAll("/");
+        }
+        return result;
+    }
+
+    /**
+     * Encode quotes in Dav search parameter.
+     *
+     * @param value search parameter
+     * @return escaped value
+     */
+    public static String davSearchEncode(String value) {
+        String result = value;
+        if (result.indexOf('\'') >= 0) {
+            result = APOS_PATTERN.matcher(result).replaceAll("''");
+        }
+        return result;
+    }
+
+    /**
+     * Get allday date value from zulu timestamp.
+     *
+     * @param value zulu datetime
+     * @return yyyyMMdd allday date value
+     */
+    public static String convertZuluDateTimeToAllDay(String value) {
+        String result = value;
+        if (value != null && value.length() != 8) {
+            // try to convert datetime value to date value
+            try {
+                Calendar calendar = Calendar.getInstance();
+                SimpleDateFormat dateParser = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
+                calendar.setTime(dateParser.parse(value));
+                calendar.add(Calendar.HOUR_OF_DAY, 12);
+                SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyyMMdd");
+                result = dateFormatter.format(calendar.getTime());
+            } catch (ParseException e) {
+                // ignore
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Remove quotes if present on value.
+     *
+     * @param value input value
+     * @return unquoted string
+     */
+    public static String removeQuotes(String value) {
+        String result = value;
+        if (result != null) {
+            if (result.startsWith("\"") || result.startsWith("{") || result.startsWith("(")) {
+                result = result.substring(1);
+            }
+            if (result.endsWith("\"") || result.endsWith("}") || result.endsWith(")")) {
+                result = result.substring(0, result.length() - 1);
+            }
+        }
+        return result;
+    }
+
+}
diff --git a/src/java/davmail/web/DavGatewayServletContextListener.java b/src/java/davmail/web/DavGatewayServletContextListener.java
new file mode 100644
index 0000000..e626b11
--- /dev/null
+++ b/src/java/davmail/web/DavGatewayServletContextListener.java
@@ -0,0 +1,59 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.web;
+
+import davmail.BundleMessage;
+import davmail.DavGateway;
+import davmail.Settings;
+import davmail.ui.tray.DavGatewayTray;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Context Listener to start/stop DavMail
+ */
+public class DavGatewayServletContextListener implements ServletContextListener {
+    public void contextInitialized(ServletContextEvent event) {
+        InputStream settingInputStream = null;
+        try {
+            settingInputStream = DavGatewayServletContextListener.class.getClassLoader().getResourceAsStream("davmail.properties");
+            Settings.load(settingInputStream);
+            DavGateway.start();
+        } catch (IOException e) {
+            DavGatewayTray.error(new BundleMessage("LOG_ERROR_LOADING_SETTINGS"), e);
+        } finally {
+            if (settingInputStream != null) {
+                try {
+                    settingInputStream.close();
+                } catch (IOException e) {
+                    DavGatewayTray.debug(new BundleMessage("LOG_ERROR_CLOSING_CONFIG_FILE"), e);
+                }
+            }
+        }
+        DavGatewayTray.debug(new BundleMessage("LOG_DAVMAIL_STARTED"));
+    }
+
+    public void contextDestroyed(ServletContextEvent event) {
+        DavGatewayTray.debug(new BundleMessage("LOG_STOPPING_DAVMAIL"));
+        DavGateway.stop();
+    }
+}
diff --git a/src/java/davmailmessages.properties b/src/java/davmailmessages.properties
new file mode 100644
index 0000000..9dacdfe
--- /dev/null
+++ b/src/java/davmailmessages.properties
@@ -0,0 +1,296 @@
+EXCEPTION_AUTHENTICATION_FAILED=Authentication failed: invalid user or password
+EXCEPTION_AUTHENTICATION_FAILED_PASSWORD_EXPIRED=Authentication failed: password expired ?
+EXCEPTION_AUTHENTICATION_FAILED_RETRY=Authentication failed: invalid user or password, retry with domain\\user or use default domain setting
+EXCEPTION_CONNECTION_FAILED=Unable to connect to OWA at {0}, status code {1}, check configuration
+EXCEPTION_DAVMAIL_CONFIGURATION=DavMail configuration exception:\n{0}
+EXCEPTION_END_OF_STREAM=End of stream reached reading content
+EXCEPTION_ITEM_NOT_FOUND=Item not found
+EXCEPTION_EXCHANGE_LOGIN_FAILED=Exchange login exception: {0}
+EXCEPTION_SESSION_EXPIRED=Exchange session expired
+EXCEPTION_INVALID_CALDAV_REQUEST=Invalid Caldav request: {0}
+EXCEPTION_INVALID_CONTENT_LENGTH=Invalid content length: {0}
+EXCEPTION_INVALID_CONTENT_TYPE=Invalid content type: {0}
+EXCEPTION_INVALID_CREDENTIALS=Invalid credentials
+EXCEPTION_INVALID_DATE=Invalid date: {0}
+EXCEPTION_INVALID_DATES=Invalid dates: {0}
+EXCEPTION_INVALID_FOLDER_URL=Invalid folder URL: {0}
+EXCEPTION_INVALID_HEADER=Invalid header, HTTPS connection to an HTTP listener ?
+EXCEPTION_INVALID_KEEPALIVE=Invalid Keep-Alive: {0}
+EXCEPTION_INVALID_MAIL_PATH=Invalid mail path: {0}
+EXCEPTION_INVALID_MESSAGE_CONTENT=Invalid message content: {0}
+EXCEPTION_INVALID_MESSAGE_URL=Invalid message URL: {0}
+EXCEPTION_INVALID_RECIPIENT=Invalid recipient: {0}
+EXCEPTION_INVALID_REQUEST=Invalid request: {0}
+EXCEPTION_INVALID_SEARCH_PARAMETERS=Invalid search parameters: {0}
+EXCEPTION_UNSUPPORTED_PARAMETER=Unsupported parameter: {0}
+EXCEPTION_INVALID_PARAMETER=Invalid parameter: {0}
+EXCEPTION_NETWORK_DOWN=All network interfaces down or host unreachable !
+EXCEPTION_UNABLE_TO_CREATE_MESSAGE=Unable to create message {0}: {1}{2}{3}
+EXCEPTION_UNABLE_TO_GET_FOLDER=Unable to get folder at {0}
+EXCEPTION_UNABLE_TO_GET_MAIL_FOLDER=Unable to get mail folder at {0}, Webdav not available on Exchange server
+EXCEPTION_UNABLE_TO_MOVE_FOLDER=Unable to move folder, target already exists
+EXCEPTION_UNABLE_TO_MOVE_MESSAGE=Unable to move message, target already exists
+EXCEPTION_UNABLE_TO_COPY_MESSAGE=Unable to copy message, target already exists
+EXCEPTION_UNABLE_TO_PATCH_MESSAGE=Unable to patch message {0}: {1}{2}{3}
+EXCEPTION_UNABLE_TO_UPDATE_MESSAGE=Unable to update message properties
+EXCEPTION_CONNECT=Connect exception: {0} {1}
+EXCEPTION_UNSUPPORTED_AUTHORIZATION_MODE=Unsupported authorization mode: {0}
+EXCEPTION_UNSUPPORTED_VALUE=Unsupported value: {0}
+LOG_CLIENT_CLOSED_CONNECTION=Client closed connection
+LOG_CLOSE_CONNECTION_ON_TIMEOUT=Closing connection on timeout
+LOG_CONNECTION_CLOSED=Connection closed
+LOG_CONNECTION_FROM=Connection from {0} on port {1,number,#}
+LOG_DAVMAIL_GATEWAY_LISTENING=DavMail Gateway {0} listening on {1}
+LOG_DAVMAIL_STARTED=DavMail Gateway started
+LOG_ERROR_CLOSING_CONFIG_FILE=Error closing configuration file
+LOG_ERROR_LOADING_OSXADAPTER=Error while loading the OSXAdapter
+LOG_ERROR_LOADING_SETTINGS=Error loading settings
+LOG_ERROR_RETRIEVING_MESSAGE=Error retreiving message
+LOG_ERROR_WAITING_FOR_SWT_INIT=Error waiting for SWT init
+LOG_ITEM_NOT_AVAILABLE=Item {0} not available: {1}
+LOG_EXCEPTION_CLOSING_CLIENT_INPUT_STREAM=Exception closing client input stream
+LOG_EXCEPTION_CLOSING_CLIENT_OUTPUT_STREAM=Exception closing client output stream
+LOG_EXCEPTION_CLOSING_CLIENT_SOCKET=Exception closing client socket
+LOG_EXCEPTION_CLOSING_CONNECTION_ON_TIMEOUT=Exception closing connection on timeout
+LOG_EXCEPTION_CLOSING_SERVER_SOCKET=Exception closing server socket
+LOG_EXCEPTION_CREATING_SERVER_SOCKET=Exception creating server socket
+LOG_EXCEPTION_CREATING_SSL_SERVER_SOCKET=Unable to bind server socket for {0} on port {1,number,#}: Exception creating secured server socket : {2}
+LOG_EXCEPTION_GETTING_SOCKET_STREAMS=Exception while getting socket streams
+LOG_EXCEPTION_LISTENING_FOR_CONNECTIONS=Exception while listening for connections
+LOG_EXCEPTION_SENDING_ERROR_TO_CLIENT=Exception sending error to client
+LOG_EXCEPTION_WAITING_SERVER_THREAD_DIE=Exception waiting for server thread to die
+LOG_EXECUTE_FOLLOW_REDIRECTS=executeFollowRedirects({0})
+LOG_EXECUTE_FOLLOW_REDIRECTS_COUNT=executeFollowRedirects: {0} redirectCount:{1}
+LOG_EXTERNAL_CONNECTION_REFUSED=Connection from external client refused
+LOG_FOUND_ACCEPTED_CERTIFICATE=Found permanently accepted certificate, hash {0}
+LOG_FOUND_CALENDAR_MESSAGES=Found {0} calendar messages
+LOG_IMAP_COMMAND={0} on {1}
+LOG_INVALID_DEPTH=Invalid depth value: {0}
+LOG_INVALID_SETTING_VALUE=Invalid setting value in {0}
+LOG_INVALID_URL=Invalid URL: {0}
+LOG_JAVA6_DESKTOP_UNAVAILABLE=Java 6 Desktop class not available
+LOG_LDAP_IGNORE_FILTER_ATTRIBUTE=Ignoring filter attribute: {0}= {1}
+LOG_LDAP_REPLACED_UID_FILTER=Replaced {0} with {1} in uid filter
+LOG_LDAP_REQ_ABANDON_SEARCH=LDAP_REQ_ABANDON {0} for search {1}
+LOG_LDAP_REQ_UNBIND=LDAP_REQ_UNBIND {0}
+LOG_LDAP_REQ_BIND_ANONYMOUS=LDAP_REQ_BIND {0} anonymous
+LOG_LDAP_REQ_BIND_USER=LDAP_REQ_BIND {0} {1}
+LOG_LDAP_REQ_BIND_SUCCESS=LOG_LDAP_REQ_BIND Success
+LOG_LDAP_REQ_BIND_INVALID_CREDENTIALS=LDAP_REQ_BIND Invalid credentials
+LOG_LDAP_REQ_SEARCH=LDAP_REQ_SEARCH {0} base={1} scope: {2} sizelimit: {3} timelimit: {4} filter: {5} returning attributes: {6}
+LOG_LDAP_REQ_SEARCH_ANONYMOUS_ACCESS_FORBIDDEN=LDAP_REQ_SEARCH {0} Anonymous access to {1} forbidden
+LOG_LDAP_REQ_SEARCH_END=LDAP_REQ_SEARCH {0} end
+LOG_LDAP_REQ_SEARCH_FOUND_RESULTS=LDAP_REQ_SEARCH {0} found {1} results
+LOG_LDAP_REQ_SEARCH_INVALID_DN=LDAP_REQ_SEARCH {0} unrecognized dn {1}, use base context ou=people or o=od
+LOG_LDAP_REQ_SEARCH_SEND_PERSON=LDAP_REQ_SEARCH {0} send uid={1}{2} {3}
+LOG_LDAP_REQ_SEARCH_SIZE_LIMIT_EXCEEDED=LDAP_REQ_SEARCH {0} size limit exceeded
+LOG_LDAP_REQ_SEARCH_SUCCESS=LDAP_REQ_SEARCH {0} success
+LOG_LDAP_SEND_COMPUTER_CONTEXT=Sending computer context {0} {1}
+LOG_LDAP_SEND_ROOT_DSE=Sending root DSE
+LOG_LDAP_UNSUPPORTED_FILTER=Unsupported filter: {0}
+LOG_LDAP_UNSUPPORTED_FILTER_ATTRIBUTE=Unsupported filter attribute: {0}= {1}
+LOG_LDAP_UNSUPPORTED_FILTER_VALUE=Unsupported filter value
+LOG_LDAP_UNSUPPORTED_OPERATION=Unsupported operation: {0}
+LOG_LISTING_ITEM=Listing item {0}/{1}
+LOG_MESSAGE={0}
+LOG_NEW_VERSION_AVAILABLE=A new version ({0}) of DavMail Gateway is available !
+LOG_OPEN_LINK_NOT_SUPPORTED=Open link not supported (tried AWT Desktop and SWT Program)
+LOG_PROTOCOL_PORT={0} port {1,number,# }
+LOG_READ_CLIENT_AUTHORIZATION=< Authorization: ********
+LOG_READ_CLIENT_AUTH_PLAIN=< AUTH PLAIN ********
+LOG_READ_CLIENT_AUTH_LOGIN=< AUTH LOGIN ********
+LOG_READ_CLIENT_LINE=< {0}
+LOG_READ_CLIENT_LOGIN=< LOGIN ********
+LOG_READ_CLIENT_PASS=< PASS ********
+LOG_READ_CLIENT_PASSWORD=< ********
+LOG_REPORT_ITEM=Report item {0}/{1}
+LOG_GATEWAY_INTERRUPTED=Stopping DavMail gateway
+LOG_GATEWAY_STOP=DavMail gateway stopped
+LOG_SEARCHING_CALENDAR_MESSAGES=Searching calendar messages...
+LOG_SEARCH_QUERY=Search: {0}
+LOG_SEND_CLIENT_MESSAGE=> {0}
+LOG_SEND_CLIENT_PREFIX_MESSAGE=> {0}{1}
+LOG_SET_SOCKET_TIMEOUT=Set socket timeout to {0} seconds
+LOG_SOCKET_BIND_FAILED=Unable to bind server socket for {0} on port {1,number,#}: port not allowed or in use by another process\n
+LOG_STARTING_DAVMAIL=Starting DavMail Gateway...
+LOG_STOPPING_DAVMAIL=Stopping DavMail Gateway...
+LOG_SWT_NOT_AVAILABLE=SWT not available, fallback to JDK 1.6 system tray support
+LOG_SYSTEM_TRAY_NOT_AVAILABLE=JDK 1.6 needed for system tray support
+LOG_UNABLE_TO_CREATE_ICON=Unable to create icon
+LOG_UNABLE_TO_CREATE_LOG_FILE_DIR=Unable to create log file directory
+LOG_UNABLE_TO_CREATE_TRAY=Unable to create tray
+LOG_UNABLE_TO_GET_PARSEINTWITHTAG=Unable to get BerDecoder.parseIntWithTag method
+LOG_UNABLE_TO_GET_RELEASED_VERSION=Unable to get released version
+LOG_UNABLE_TO_LOAD_IMAGE=Unable to load image
+LOG_UNABLE_TO_LOAD_SETTINGS=Unable to load settings:
+LOG_UNABLE_TO_OPEN_LINK=Unable to open link
+LOG_UNABLE_TO_SET_ICON_IMAGE=Unable to set JDialog icon image (not available under Java 1.5)
+LOG_UNABLE_TO_SET_LOG_FILE_PATH=Unable to set log file path
+LOG_UNABLE_TO_SET_LOOK_AND_FEEL=Unable to set look and feel
+LOG_UNABLE_TO_SET_SYSTEM_LOOK_AND_FEEL=Unable to set system look and feel
+LOG_UNABLE_TO_STORE_SETTINGS=Unable to store settings:
+LOG_UNSUPPORTED_REQUEST=Unsupported request: {0}
+LOG_INVALID_TIMEZONE=Invalid timezone: {0}
+LOG_ACCESS_FORBIDDEN=Access to {0} forbidden: {1}
+LOG_DOWNLOAD_PROGRESS=Downloaded {0} KBytes from {1}
+LOG_WEBDAV_NOT_AVAILABLE=WebDav not available, retry with EWS mode
+UI_ABOUT=About...
+UI_ABOUT_DAVMAIL=About DavMail Gateway
+UI_ABOUT_DAVMAIL_AUTHOR=<html><b>DavMail Gateway</b><br>By Mickaël Guessant<br><br>
+UI_ACCEPT_CERTIFICATE=DavMail: Accept certificate ?
+UI_ALLOW_REMOTE_CONNECTION=Allow Remote Connections:
+UI_ALLOW_REMOTE_CONNECTION_HELP=Allow remote connections to the gateway (server mode)
+UI_PASSWORD_PROMPT=DavMail: Enter password
+UI_ANSWER_NO=n
+UI_ANSWER_YES=y
+UI_BIND_ADDRESS=Bind address:
+UI_BIND_ADDRESS_HELP=Bind only to the specified network address
+UI_BUTTON_ACCEPT=Accept
+UI_BUTTON_CANCEL=Cancel
+UI_BUTTON_DENY=Deny
+UI_BUTTON_HELP=Help
+UI_BUTTON_OK=OK
+UI_BUTTON_SAVE=Save
+UI_BUTTON_DEFAULT=Default
+UI_BUTTON_DEFAULT_HELP=Reset default logging levels
+UI_CALDAV_PORT=Caldav HTTP port:
+UI_CALDAV_PORT_HELP=Local Caldav server port to configure in Caldav (calendar) client
+UI_CALENDAR_PAST_EVENTS=Calendar past events (Caldav):
+UI_CALENDAR_PAST_EVENTS_HELP=Get events in the past not older than specified days count, leave empty for no limits
+UI_CURRENT_VERSION=Current version: {0}<br>
+UI_DAVMAIL_GATEWAY=DavMail Gateway
+UI_DAVMAIL_SETTINGS=DavMail Gateway Settings
+UI_DELAYS=Delays
+UI_DISABLE_UPDATE_CHECK=Disable update check:
+UI_DISABLE_UPDATE_CHECK_HELP=Disable DavMail check for new version
+UI_ENABLE_PROXY=Enable proxy:
+UI_ERROR_WAITING_FOR_CERTIFICATE_CHECK=Error waiting for certificate check
+UI_EXIT=Exit
+UI_FINGERPRINT=FingerPrint
+UI_GATEWAY=Gateway
+UI_HELP_INSTRUCTIONS=<br>Help and setup instructions available at:<br><a href=\"http://davmail.sourceforge.net\">http://davmail.sourceforge.net</a><br><br>To send comments or report bugs, <br>use <a href=\"http://sourceforge.net/tracker/?group_id=184600\">DavMail Sourceforge trackers</a><br>or contact me at <a href=\"mailto:mguessan at free.fr\">mguessan at free.fr</a></html>
+UI_IMAP_PORT=Local IMAP port:
+UI_IMAP_PORT_HELP=Local IMAP server port to configure in mail client
+UI_ISSUED_BY=Issued by
+UI_ISSUED_TO=Issued to
+UI_KEEP_DELAY=Trash keep delay (POP):
+UI_KEEP_DELAY_HELP=Number of days to keep messages in trash
+UI_KEY_PASSWORD=Key password:
+UI_KEY_PASSWORD_HELP=SSL key password inside key store
+UI_KEY_STORE=Key store:
+UI_KEY_STORE_HELP=SSL certificate key store file path
+UI_KEY_STORE_PASSWORD=Key store password:
+UI_KEY_STORE_PASSWORD_HELP=Key store password
+UI_KEY_STORE_TYPE=Key store type:
+UI_KEY_STORE_TYPE_HELP=Choose key store type
+UI_CLIENT_SO_TIMEOUT=Client connection timeout:
+UI_CLIENT_SO_TIMEOUT_HELP=Client connection timeout in seconds, 0 to disable timeout, empty for 5 minutes
+UI_CLIENT_KEY_STORE=Client key store:
+UI_CLIENT_KEY_STORE_HELP=SSL client certificate key store file path
+UI_CLIENT_KEY_STORE_PASSWORD=Client key store password:
+UI_CLIENT_KEY_STORE_PASSWORD_HELP=Client key store password, leave empty for runtime prompt
+UI_CLIENT_KEY_STORE_TYPE=Client key store type:
+UI_CLIENT_KEY_STORE_TYPE_HELP=Choose client certificate key store type, choose PKCS11 for smartcard
+UI_CLIENT_CERTIFICATE=Client Certificate (DavMail to Exchange)
+UI_PKCS11_LIBRARY=PKCS11 library:
+UI_PKCS11_LIBRARY_HELP=PKCS11 (smartcard) library path (.so or .dll)
+UI_PKCS11_CONFIG=PKCS11 config:
+UI_PKCS11_CONFIG_HELP=Optional additional PKCS11 settings (slot, nssArgs, ...)
+UI_LAST_LOG=Last log
+UI_LAST_MESSAGE=Last message
+UI_LATEST_VERSION=Latest version available: {0} <br>A new version of DavMail Gateway is available.<br><a href=\"http://sourceforge.net/project/platformdownload.php?group_id=184600\">Download latest version</a><br>
+UI_LDAP_PORT=Local LDAP port:
+UI_LDAP_PORT_HELP=Local LDAP server port to configure in directory (addresse book) client
+UI_LOGGING_LEVELS=Logging levels
+UI_LOGS=Logs
+UI_LOG_DAVMAIL=DavMail:
+UI_LOG_DEFAULT=Default:
+UI_LOG_HTTPCLIENT=HttpClient:
+UI_LOG_WIRE=Wire:
+UI_LOG_FILE_PATH=Log file path:
+UI_NETWORK=Network
+UI_NO_SSL=No SSL
+UI_OWA_URL=OWA (Exchange) URL:
+UI_OWA_URL_HELP=Base Outlook Web Access URL
+UI_POP_PORT=Local POP port:
+UI_POP_PORT_HELP=Local POP server port to configure in mail client
+UI_PROXY=Proxy
+UI_PROXY_PASSWORD=Proxy password:
+UI_PROXY_PORT=Proxy port:
+UI_PROXY_SERVER=Proxy server:
+UI_PROXY_USER=Proxy user:
+UI_SENT_KEEP_DELAY=Sent keep delay (POP):
+UI_SENT_KEEP_DELAY_HELP=Number of days to keep messages in sent folder
+UI_SERIAL=Serial
+UI_DAVMAIL_SERVER_CERTIFICATE=Server Certificate (Client to DavMail)
+UI_SERVER_CERTIFICATE=Exchange Server Certificate
+UI_SERVER_CERTIFICATE_HASH=Server certificate hash:
+UI_SERVER_CERTIFICATE_HASH_HELP=Manually accepted server certificate hash
+UI_SETTINGS=Settings...
+UI_SHOW_LOGS=Show logs...
+UI_SMTP_PORT=Local SMTP port:
+UI_SMTP_PORT_HELP=Local SMTP server port to configure in mail client
+UI_TAB_ADVANCED=Advanced
+UI_TAB_OSX=OSX
+UI_OSX=OSX
+UI_OSX_HIDE_FROM_DOCK=Hide from Dock
+UI_OSX_HIDE_FROM_DOCK_HELP=Hide application from Dock (restart needed)
+UI_TAB_ENCRYPTION=Encryption
+UI_TAB_MAIN=Main
+UI_TAB_NETWORK=Network
+UI_UNTRUSTED_CERTIFICATE=Server provided an untrusted certificate,\n you can choose to accept or deny access.\n Accept certificate (y/n)?
+UI_UNTRUSTED_CERTIFICATE_HTML=<html><b>Server provided an untrusted certificate,<br> you can choose to accept or deny access</b></html>
+UI_VALID_FROM=Valid from
+UI_VALID_UNTIL=Valid until
+MEETING_REQUEST=Meeting request
+LOG_EXCEPTION_CLOSING_KEYSTORE_INPUT_STREAM=Exception closing keystore input stream
+LOG_SUBFOLDER_ACCESS_FORBIDDEN=Subfolder access forbidden to {0}
+LOG_FOLDER_NOT_FOUND=Folder {0} not found
+LOG_FOLDER_ACCESS_FORBIDDEN=Folder access to {0} forbidden
+LOG_FOLDER_ACCESS_ERROR=Folder access to {0} error: {1}
+UI_OTP_PASSWORD_PROMPT=One Time (token) Password:
+UI_CAPTCHA_PROMPT=Enter pin value:
+UI_TAB_LOGGING=Logging
+UI_OTHER=Other
+UI_CALDAV_ALARM_SOUND=Caldav alarm sound:
+UI_CALDAV_ALARM_SOUND_HELP=Convert Caldav alarm to sound alarm supported by iCal, e.g. Basso
+UI_FORCE_ACTIVESYNC_UPDATE=Force ActiveSync update:
+UI_FORCE_ACTIVESYNC_UPDATE_HELP=Force update of Caldav events for ActiveSync connected devices
+UI_DEFAULT_DOMAIN=Default windows domain:
+UI_DEFAULT_DOMAIN_HELP=Default windows domain name
+UI_USE_SYSTEM_PROXIES=Use system proxy settings:
+UI_SHOW_STARTUP_BANNER=Display startup banner
+UI_SHOW_STARTUP_BANNER_HELP=Whether to show the initial startup notification window or not
+UI_DISABLE_GUI_NOTIFICATIONS=Disable balloon notifications
+UI_DISABLE_GUI_NOTIFICATIONS_HELP=Disable all graphical notifications
+UI_IMAP_AUTO_EXPUNGE=IMAP auto expunge:
+UI_IMAP_AUTO_EXPUNGE_HELP=Delete messages immediately on the server over IMAP
+UI_POP_MARK_READ=POP mark read:
+UI_POP_MARK_READ_HELP=Mark messages read on server immediately after retrieval
+UI_IMAP_IDLE_DELAY=IDLE folder monitor delay (IMAP):
+UI_IMAP_IDLE_DELAY_HELP=IMAP folder idle monitor delay in minutes, leave empty to disable IDLE support
+EXCEPTION_EWS_NOT_AVAILABLE=EWS end point not available
+EXCEPTION_FOLDER_NOT_FOUND=Folder {0} not found
+UNKNOWN_ATTRIBUTE=Unknown attribute: {0}
+NEEDS-ACTION=
+ACCEPTED=Accepted:
+TENTATIVE=Tentative:
+DECLINED=Declined: 
+UI_ENABLE_EWS=Exchange Protocol
+UI_ENABLE_EWS_HELP=Choose EWS on Exchange 2010 or Exchange 2007 with Webdav disabled
+UI_CALDAV_NOTIFICATION=DavMail: Caldav scheduling notification
+UI_BUTTON_SEND=Send
+UI_TO=To:
+UI_TO_HELP=Recipients
+UI_CC=Cc: 
+UI_CC_HELP=Copy recipients
+UI_SUBJECT=Subject: 
+UI_SUBJECT_HELP=Caldav notification subject
+UI_NOTIFICATION_BODY=Caldav notification comment
+UI_CALDAV_EDIT_NOTIFICATIONS=Edit Caldav notifications:
+UI_CALDAV_EDIT_NOTIFICATIONS_HELP=Enable interactive Caldav edit notification window
+LOG_SEARCH_RESULT=Found {0} item(s)
+UI_LOG_FILE_SIZE=Log file size:
+UI_SAVE_IN_SENT=SMTP save in sent:
+UI_SAVE_IN_SENT_HELP=Save messages sent over SMTP in server Sent folder
+UI_CERTIFICATE_ALIAS_PROMPT=Select a certificate
\ No newline at end of file
diff --git a/src/java/davmailmessages_fr.properties b/src/java/davmailmessages_fr.properties
new file mode 100644
index 0000000..e9c10b3
--- /dev/null
+++ b/src/java/davmailmessages_fr.properties
@@ -0,0 +1,295 @@
+EXCEPTION_AUTHENTICATION_FAILED=Echec d''authentification : identifiant ou mot de passe invalide
+EXCEPTION_AUTHENTICATION_FAILED_PASSWORD_EXPIRED=Echec d''authentification : mot de passe expiré ?
+EXCEPTION_AUTHENTICATION_FAILED_RETRY=Echec d''authentification : identifiant ou mot de passe invalide, réessayer avec domaine\\utilisateur ou utiliser le paramètre domaine par défaut
+EXCEPTION_CONNECTION_FAILED=Connection OWA à {0} impossible, code retour {1}, vérifier la configuration
+EXCEPTION_DAVMAIL_CONFIGURATION=Erreur de configuration DavMail :\n{0}
+EXCEPTION_END_OF_STREAM=Fin de flux âtteint pendant la lecture du contenu
+EXCEPTION_ITEM_NOT_FOUND=Elément non trouvé
+EXCEPTION_EXCHANGE_LOGIN_FAILED=Exception lors de la connexion Exchange : {0}
+EXCEPTION_INVALID_CALDAV_REQUEST=Reuqête Caldav invalide : {0}
+EXCEPTION_INVALID_CONTENT_LENGTH=Longueur du contenu invalide : {0}
+EXCEPTION_INVALID_CONTENT_TYPE=Type de contenu invalide : {0}
+EXCEPTION_INVALID_CREDENTIALS=Identifiant ou mot de passe invalide
+EXCEPTION_INVALID_DATE=Date invalide {0}
+EXCEPTION_INVALID_DATES=Dates invalides : {0}
+EXCEPTION_INVALID_FOLDER_URL=URL du dossier invalide : {0}
+EXCEPTION_INVALID_HEADER=Entête invalide, connexion HTTPS sur le service HTTP ?
+EXCEPTION_INVALID_KEEPALIVE=Keep-Alive invalide : {0}
+EXCEPTION_INVALID_MAIL_PATH=Chemin de messagerie invalide : {0}
+EXCEPTION_INVALID_MESSAGE_CONTENT=Contenu du message invalide : {0}
+EXCEPTION_INVALID_MESSAGE_URL=URL de message invalide : {0}
+EXCEPTION_INVALID_RECIPIENT=Destinataire invalide : {0}
+EXCEPTION_INVALID_REQUEST=Requête invalide {0}
+EXCEPTION_INVALID_SEARCH_PARAMETERS=Paramètres de recherche invalides : {0}
+EXCEPTION_NETWORK_DOWN=Toutes les interfaces réseaux sont indisponibles ou serveur non joignable !
+EXCEPTION_UNABLE_TO_CREATE_MESSAGE=Impossible de créer le message {0} : {1}{2}{3}
+EXCEPTION_UNABLE_TO_GET_FOLDER=Impossible d''obtenir le dossier {0}
+EXCEPTION_UNABLE_TO_GET_MAIL_FOLDER=Impossible d''obtenir le dossier de messagerie à l''adresse {0}, Webdav non disponible sur le serveur Exchange
+EXCEPTION_UNABLE_TO_MOVE_FOLDER=Impossible de déplacer le dossier, la cible existe
+EXCEPTION_UNABLE_TO_MOVE_MESSAGE=Impossible de déplacer le message, la cible existe
+EXCEPTION_UNABLE_TO_COPY_MESSAGE=Impossible de copier le message, la cible existe
+EXCEPTION_UNABLE_TO_PATCH_MESSAGE=Impossible de mettre ) jour le message {0} : {1}{2}{3}
+EXCEPTION_UNABLE_TO_UPDATE_MESSAGE=Impossible de mettre à jour les propriétés du message
+EXCEPTION_CONNECT=Exception lors de la connexion : {0} {1}
+EXCEPTION_UNSUPPORTED_AUTHORIZATION_MODE=Mode d'authentification invalide : {0}
+EXCEPTION_UNSUPPORTED_VALUE=Valeur non supportée : {0}
+LOG_CLIENT_CLOSED_CONNECTION=Connection fermée par le client
+LOG_CLOSE_CONNECTION_ON_TIMEOUT=Connection fermée sur expiration
+LOG_CONNECTION_CLOSED=Connection fermée
+LOG_CONNECTION_FROM=Connection de {0} sur le port {1,number,#}
+LOG_DAVMAIL_GATEWAY_LISTENING=Passerelle DavMail {0} en écoute sur {1}
+LOG_DAVMAIL_STARTED=Passerelle DavMail démarrée
+LOG_ERROR_CLOSING_CONFIG_FILE=Erreur à la fermeture du fichier de configuration
+LOG_ERROR_LOADING_OSXADAPTER=Erreur au chargement de OSXAdapter
+LOG_ERROR_LOADING_SETTINGS=Erreur de chargement de la configuration
+LOG_ERROR_RETRIEVING_MESSAGE=Erreur lors la récupération du message
+LOG_ERROR_WAITING_FOR_SWT_INIT=Erreur d''initialisation SWT
+LOG_ITEM_NOT_AVAILABLE=Evènement {0} non disponible : {1}
+LOG_EXCEPTION_CLOSING_CLIENT_INPUT_STREAM=Erreur à la fermeture du flux d''entrée client
+LOG_EXCEPTION_CLOSING_CLIENT_OUTPUT_STREAM=Erreur à la fermeture du flux de sortie client
+LOG_EXCEPTION_CLOSING_CLIENT_SOCKET=Erreur à la fermeture de la connection client
+LOG_EXCEPTION_CLOSING_CONNECTION_ON_TIMEOUT=Erreur à la fermeture de la connexion sur expiration
+LOG_EXCEPTION_CLOSING_SERVER_SOCKET=Erreur à la fermeture du port d''écoute serveur
+LOG_EXCEPTION_CREATING_SERVER_SOCKET=Erreur lors de la création du port d''écoute serveur
+LOG_EXCEPTION_CREATING_SSL_SERVER_SOCKET=Impossible d''ouvrir le port d''écoute {1,number,#} pour {0} : Erreur lors de la création du port d''écoute serveur sécurisé : {2}
+LOG_EXCEPTION_GETTING_SOCKET_STREAMS=Erreur lors de l''établissement des flux de la connexion
+LOG_EXCEPTION_LISTENING_FOR_CONNECTIONS=Erreur pendant l''attente des connexion entrantes
+LOG_EXCEPTION_SENDING_ERROR_TO_CLIENT=Erreur d''envoi du message d''erreur au client
+LOG_EXCEPTION_WAITING_SERVER_THREAD_DIE=Erreur pendant l''attente de fin du processus serveur
+LOG_EXECUTE_FOLLOW_REDIRECTS=executeFollowRedirects({0})
+LOG_EXECUTE_FOLLOW_REDIRECTS_COUNT=executeFollowRedirects: {0} redirectCount: {1}
+LOG_EXTERNAL_CONNECTION_REFUSED=Connexion du client distant refusée
+LOG_FOUND_ACCEPTED_CERTIFICATE=Certificat définitivement accepté trouvé, hash {0}
+LOG_FOUND_CALENDAR_MESSAGES={0} messages trouvés dans le calendrier
+LOG_IMAP_COMMAND={0} sur {1}
+LOG_INVALID_DEPTH=Profondeur invalide : {0}
+LOG_INVALID_SETTING_VALUE=Paramètre de configuration {0} invalide
+LOG_INVALID_URL=URL invalide : {0}
+LOG_JAVA6_DESKTOP_UNAVAILABLE=Classe Java 6 Desktop non disponible
+LOG_LDAP_IGNORE_FILTER_ATTRIBUTE=Filtre d''attribut ignoré : {0}= {1}
+LOG_LDAP_REPLACED_UID_FILTER=Remplacé {0} par {1} dans le filtre uid
+LOG_LDAP_REQ_ABANDON_SEARCH=LDAP_REQ_ABANDON {0} pour la recherche {1}
+LOG_LDAP_REQ_UNBIND=LDAP_REQ_UNBIND {0}
+LOG_LDAP_REQ_BIND_ANONYMOUS=LDAP_REQ_BIND {0} anonyme
+LOG_LDAP_REQ_BIND_USER=LOG_LDAP_REQ_BIND_USER
+LOG_LDAP_REQ_SEARCH=LDAP_REQ_SEARCH {0} base={1} scope: {2} sizelimit: {3} timelimit: {4} filter: {5} returning attributes: {6}
+LOG_LDAP_REQ_SEARCH_ANONYMOUS_ACCESS_FORBIDDEN=LDAP_REQ_SEARCH {0} Accès anonyme à {1} interdit
+LOG_LDAP_REQ_SEARCH_END=LDAP_REQ_SEARCH {0} fin
+LOG_LDAP_REQ_SEARCH_FOUND_RESULTS=LDAP_REQ_SEARCH {0} retourne {1} résultats
+LOG_LDAP_REQ_SEARCH_INVALID_DN=LDAP_REQ_SEARCH {0} dn non reconnu {1}, utiliser le contexte ou=people ou o=od
+LOG_LDAP_REQ_SEARCH_SEND_PERSON=LDAP_REQ_SEARCH {0} envoi uid={1}{2} {3}
+LOG_LDAP_REQ_SEARCH_SIZE_LIMIT_EXCEEDED=LDAP_REQ_SEARCH {0} limite de taille dépassée
+LOG_LDAP_REQ_SEARCH_SUCCESS=LDAP_REQ_SEARCH {0} succès
+LOG_LDAP_SEND_COMPUTER_CONTEXT=Envoi contexte computer {0} {1}
+LOG_LDAP_SEND_ROOT_DSE=Envoi racine DSE
+LOG_LDAP_UNSUPPORTED_FILTER=Filtre non supporté : {0}
+LOG_LDAP_UNSUPPORTED_FILTER_ATTRIBUTE=Attribut de filtre non supporté : {0}= {1}
+LOG_LDAP_UNSUPPORTED_FILTER_VALUE=Valeur de filtre non supportée
+LOG_LDAP_UNSUPPORTED_OPERATION=Opération non supportée : {0}
+LOG_LISTING_ITEM=Liste élément {0}/{1}
+LOG_MESSAGE={0}
+LOG_NEW_VERSION_AVAILABLE=Une nouvelle version ({0}) de la Passerelle DavMail est disponible !
+LOG_OPEN_LINK_NOT_SUPPORTED=Ouverture de lien impossible (avec AWT Desktop et SWT Program)
+LOG_PROTOCOL_PORT=port {0} : {1,number,# }
+LOG_READ_CLIENT_AUTHORIZATION=< Authorization: ********
+LOG_READ_CLIENT_AUTH_PLAIN=< AUTH PLAIN ********
+LOG_READ_CLIENT_LINE=< {0}
+LOG_READ_CLIENT_LOGIN=< LOGIN ********
+LOG_READ_CLIENT_PASS=< PASS ********
+LOG_READ_CLIENT_PASSWORD=< ********
+LOG_REPORT_ITEM=Envoi élément {0}/{1}
+LOG_SEARCHING_CALENDAR_MESSAGES=Recherche des messages de calendrier...
+LOG_SEARCH_QUERY=Recherche : {0}
+LOG_SEND_CLIENT_MESSAGE=> {0}
+LOG_SEND_CLIENT_PREFIX_MESSAGE=> {0}{1}
+LOG_SET_SOCKET_TIMEOUT=Expiration de lecture de la connection positionnée à {0} secondes
+LOG_SOCKET_BIND_FAILED=Impossible d''ouvrir le port d''écoute {1,number,#} pour {0} : port non autorisé ou utilisé par un autre processus\n
+LOG_STARTING_DAVMAIL=Démarrage de la passerelle DavMail...
+LOG_STOPPING_DAVMAIL=Arrêt de la passerelle DavMail...
+LOG_SWT_NOT_AVAILABLE=SWT non disponible, bascule vers le support icône de notification de Java 1.6
+LOG_SYSTEM_TRAY_NOT_AVAILABLE=JDK 1.6 nécessaire pour le support de l''icône de notification
+LOG_UNABLE_TO_CREATE_ICON=Impossible de créer l''icône
+LOG_UNABLE_TO_CREATE_LOG_FILE_DIR=Impossible de créer le répertoire de traces
+LOG_UNABLE_TO_CREATE_TRAY=Impossible de créer l''icône de notification
+LOG_UNABLE_TO_GET_PARSEINTWITHTAG=Erreur d''accès à la méthode BerDecoder.parseIntWithTag
+LOG_UNABLE_TO_GET_RELEASED_VERSION=Impossible de récupérer le numéro de dernière version
+LOG_UNABLE_TO_LOAD_IMAGE=Impossible de charger l''image
+LOG_UNABLE_TO_LOAD_SETTINGS=Impossible de charger la configuration :
+LOG_UNABLE_TO_OPEN_LINK=Impossible d''ouvrir le lien
+LOG_UNABLE_TO_SET_ICON_IMAGE=Impossible de positionner l''icône de JDialog (non disponible en Java 1.5)
+LOG_UNABLE_TO_SET_LOG_FILE_PATH=Echec à la mise à jour du chemin du fichier de traces
+LOG_UNABLE_TO_SET_LOOK_AND_FEEL=Impossible de définir le style de l''interface
+LOG_UNABLE_TO_SET_SYSTEM_LOOK_AND_FEEL=Impossible de définir le style natif sur l''interface
+LOG_UNABLE_TO_STORE_SETTINGS=Impossible d''enregistrer la configuration
+LOG_UNSUPPORTED_REQUEST=Requête non supportée : {0}
+LOG_DOWNLOAD_PROGRESS={0} KOctets téléchargés de {1}
+LOG_WEBDAV_NOT_AVAILABLE=WebDav non disponible, réessayer en activant EWS
+UI_ABOUT=A propos...
+UI_ABOUT_DAVMAIL=A propos de la Passerelle DavMail
+UI_ABOUT_DAVMAIL_AUTHOR=<html><b>Passerelle DavMail</b><br>Par Mickaël Guessant<br><br>
+UI_ACCEPT_CERTIFICATE=DavMail : Accepter le certificat ?
+UI_ALLOW_REMOTE_CONNECTION=Autoriser connexions distantes :
+UI_ALLOW_REMOTE_CONNECTION_HELP=Autoriser les connexions distantes à la passerelle (mode serveur)
+UI_ANSWER_NO=n
+UI_ANSWER_YES=o
+UI_BIND_ADDRESS=Adresse d''écoute :
+UI_BIND_ADDRESS_HELP=Ecouter seulement sur l''adresse définie
+UI_BUTTON_ACCEPT=Accepter
+UI_BUTTON_CANCEL=Annuler
+UI_BUTTON_DENY=Refuser
+UI_BUTTON_HELP=Aide
+UI_BUTTON_OK=OK
+UI_BUTTON_SAVE=Enregistrer
+UI_BUTTON_DEFAULT=Défaut
+UI_BUTTON_DEFAULT_HELP=Réinitialiser les niveaux de trace par défaut
+UI_CALDAV_PORT=Port HTTP Caldav :
+UI_CALDAV_PORT_HELP=Port local Caldav à configurer dans le client Caldav (agenda)
+UI_CALENDAR_PAST_EVENTS=Jours passés du calendrier (Caldav) :
+UI_CALENDAR_PAST_EVENTS_HELP=Limiter les évènements remontés
+UI_CURRENT_VERSION=Version actuelle : {0}<br>
+UI_DAVMAIL_GATEWAY=Passerelle DavMail
+UI_DAVMAIL_SETTINGS=Configuration Passerelle DavMail
+UI_DELAYS=Délais
+UI_DISABLE_UPDATE_CHECK=Désactivation contrôle version :
+UI_DISABLE_UPDATE_CHECK_HELP=Désactiver le contrôle de nouvelle version disponible
+UI_ENABLE_PROXY=Activer proxy :
+UI_ERROR_WAITING_FOR_CERTIFICATE_CHECK=Erreur lors de l''attente de validation du certificat
+UI_EXIT=Quitter
+UI_FINGERPRINT=Empreinte
+UI_GATEWAY=Passerelle
+UI_HELP_INSTRUCTIONS=<br>Aide et instructions disponibles sur :<br><a href=\"http://davmail.sourceforge.net\">http://davmail.sourceforge.net</a><br><br>Pour envoyer des commentaires ou signaler des anomalies, <br>utiliser <a href=\"http://sourceforge.net/tracker/?group_id=184600\">DavMail Sourceforge trackers</a><br>ou me contacter à l''adresse <a href=\"mailto:mguessan at free.fr\">mguessan at free.fr</a></html>
+UI_IMAP_PORT=Port IMAP local :
+UI_IMAP_PORT_HELP=Port IMAP local à configurer dans le client de messagerie
+UI_ISSUED_BY=Emis par
+UI_ISSUED_TO=Emis pour
+UI_KEEP_DELAY=Délai de rétention corbeille (POP) :
+UI_KEEP_DELAY_HELP=Nombre de jours de conservation des messages dans la corbeille
+UI_KEY_PASSWORD=Mot de passe clé :
+UI_KEY_PASSWORD_HELP=Mot de passe clé SSL contenue dans le fichier des clés
+UI_KEY_STORE=Fichier clés :
+UI_KEY_STORE_HELP=Chemin du fichier contenant les clés et certificats SSL
+UI_KEY_STORE_PASSWORD=Mot de passe fichier clés :
+UI_KEY_STORE_PASSWORD_HELP=Mot de passe du fichier des clés
+UI_KEY_STORE_TYPE=Type de fichier clés :
+UI_KEY_STORE_TYPE_HELP=Choix du type de fichier de clés
+UI_LAST_LOG=Dernière trace
+UI_LAST_MESSAGE=Dernier message
+UI_LATEST_VERSION=Dernière version disponible : {0} <br>Une nouvelle version de la Passerelle DavMail est disponible.<br><a href=\"http://sourceforge.net/project/platformdownload.php?group_id=184600\">Télécharcher la dernière version</a><br>
+UI_LDAP_PORT=Port LDAP local :
+UI_LDAP_PORT_HELP=Port LDAP local à configurer dans le client annuaire (carnet d''adresse)
+UI_LOGGING_LEVELS=Niveaux de traces
+UI_LOGS=Traces
+UI_LOG_DAVMAIL=DavMail :
+UI_LOG_DEFAULT=Défaut :
+UI_LOG_HTTPCLIENT=HttpClient :
+UI_LOG_WIRE=Réseau :
+UI_NETWORK=Réseau
+UI_NO_SSL=Pas de SSL
+UI_OWA_URL=URL OWA (Exchange) :
+UI_OWA_URL_HELP=URL de connexion Outlook Web Access
+UI_POP_PORT=Port POP local :
+UI_POP_PORT_HELP=Port POP local à configurer dans le client de messagerie
+UI_PROXY=Proxy
+UI_PROXY_PASSWORD=Mot de passe proxy :
+UI_PROXY_PORT=Port du serveur proxy :
+UI_PROXY_SERVER=Serveur proxy :
+UI_PROXY_USER=Identifiant proxy :
+UI_SENT_KEEP_DELAY=Délai de rétention envoyés (POP) :
+UI_SENT_KEEP_DELAY_HELP=Nombre de jours de conservation des messages dans le dossier des messages envoyés
+UI_SERIAL=Numéro de série
+UI_DAVMAIL_SERVER_CERTIFICATE=Certificat Serveur (Client vers DavMail)
+UI_SERVER_CERTIFICATE=Certificat Serveur Exchange
+UI_SERVER_CERTIFICATE_HASH=Hash du certificat serveur :
+UI_SERVER_CERTIFICATE_HASH_HELP=Hash du certificat accepté manuellement
+UI_SETTINGS=Configuration...
+UI_SHOW_LOGS=Afficher les traces...
+UI_SMTP_PORT=Port SMTP local :
+UI_SMTP_PORT_HELP=Port SMTP local à configurer dans le client de messagerie
+UI_TAB_ADVANCED=Avancé
+UI_TAB_ENCRYPTION=Chiffrement
+UI_TAB_MAIN=Général
+UI_TAB_NETWORK=Réseau
+UI_UNTRUSTED_CERTIFICATE=Le certificat fourni par le serveur n''est certifié par aucune autorité de confiance,\n vous pouvez choisir d''accepter ou de rejeter l''accès
+UI_UNTRUSTED_CERTIFICATE_HTML=<html><b>Le certificat fourni par le serveur n''est certifié par aucune autorité de confiance,<br> vous pouvez choisir d''accepter ou de rejeter l''accès</b></html>
+UI_VALID_FROM=Emis le
+UI_VALID_UNTIL=Expire le
+UI_PASSWORD_PROMPT=DavMail : Entrer le mot de passe
+UI_PKCS11_LIBRARY_HELP=Chemin de la librarie PKCS11 (carte à puce) (.so or .dll)
+UI_PKCS11_LIBRARY=Librairie PKCS11 :
+UI_PKCS11_CONFIG_HELP=Configuration PKCS11 complémentaire optionnelle (slot, nssArgs, ...)
+UI_PKCS11_CONFIG=Configuration PKCS11 :
+UI_CLIENT_SO_TIMEOUT=Délai d'attente client:
+UI_CLIENT_SO_TIMEOUT_HELP=Délai d'attente client en secondes, 0 pour désactiver, vide pour 5 minutes
+UI_CLIENT_CERTIFICATE=Certificat client (DavMail vers Exchange)
+UI_LOG_FILE_PATH=Chemin du fichier de traces :
+UI_LOG_FILE_SIZE=Taille du fichier de traces :
+LOG_GATEWAY_INTERRUPTED=Arrêt de la passerelle DavMail en cours
+LOG_GATEWAY_STOP=Passerelle DavMail arrêtée
+LOG_INVALID_TIMEZONE=Fuseau horaire invalide : {0}
+MEETING_REQUEST=Invitation
+LOG_ACCESS_FORBIDDEN=Accès à {0} non autorisé: {1}
+LOG_LDAP_REQ_BIND_INVALID_CREDENTIALS=LDAP_REQ_BIND Utilisateur ou mot de passe invalide
+LOG_LDAP_REQ_BIND_SUCCESS=LOG_LDAP_REQ_BIND Authentification réussie
+LOG_EXCEPTION_CLOSING_KEYSTORE_INPUT_STREAM=Erreur à la fermeture du flux d''entrée du fichier de clés
+LOG_SUBFOLDER_ACCESS_FORBIDDEN=Accès interdit au sous dossiers de {0}
+LOG_FOLDER_ACCESS_FORBIDDEN=Accès interdit au dossier {0}
+LOG_FOLDER_NOT_FOUND=Dossier {0} introuvable
+LOG_FOLDER_ACCESS_ERROR=Erreur lors de l''accès au dossier {0} : {1}
+UI_OTP_PASSWORD_PROMPT=Mot de passe du jeton :
+UI_CAPTCHA_PROMPT=Code PIN :
+EXCEPTION_SESSION_EXPIRED=Session Exchange expirée
+UI_CLIENT_KEY_STORE_TYPE=Type de stockage :
+UI_CLIENT_KEY_STORE_TYPE_HELP=Choisir le type de stockage du certificat client, PKCS11 pour une carte à puce
+UI_CLIENT_KEY_STORE=Fichier certificat client :
+UI_CLIENT_KEY_STORE_HELP=Chemin du fichier contenant le certificat client SSL
+UI_CLIENT_KEY_STORE_PASSWORD=Mot de passe certificat client :
+UI_CLIENT_KEY_STORE_PASSWORD_HELP=Mot de passe du certificat client, laisser vide pour fournir le mot de passe mode interactif
+UI_TAB_LOGGING=Traces
+UI_TAB_OSX=OSX
+UI_OSX=OSX
+UI_OSX_HIDE_FROM_DOCK=Supprimer du Dock
+UI_OSX_HIDE_FROM_DOCK_HELP=Supprimer l''application du Dock (redémarrage nécessaire)
+UI_OTHER=Autres
+UI_CALDAV_ALARM_SOUND=Son des alarmes Caldav :
+UI_CALDAV_ALARM_SOUND_HELP=Convertir les alarmes Caldav en alarmes sonores supportées par iCal, par exemple Basso
+UI_FORCE_ACTIVESYNC_UPDATE=Forcer ActiveSync :
+UI_FORCE_ACTIVESYNC_UPDATE_HELP=Forcer la mise à jour des évènements Caldav pour les appareils connectés via ActiveSync
+UI_DEFAULT_DOMAIN=Domaine windows par défaut :
+UI_DEFAULT_DOMAIN_HELP=Nom du domaine windows par défaut
+EXCEPTION_UNSUPPORTED_PARAMETER=Paramètre non supporté : {0}
+EXCEPTION_INVALID_PARAMETER=Paramètre invalide : {0}
+UI_USE_SYSTEM_PROXIES=Utiliser la configuration système :
+UI_SHOW_STARTUP_BANNER=Notification au lancement :
+UI_SHOW_STARTUP_BANNER_HELP=Afficher ou non la fenêtre de notification au démarrage
+UI_DISABLE_GUI_NOTIFICATIONS=Désactiver notifications graphiques :
+UI_DISABLE_GUI_NOTIFICATIONS_HELP=Supprimer toutes les notifications graphiques
+LOG_READ_CLIENT_AUTH_LOGIN=< AUTH LOGIN ********
+UI_IMAP_IDLE_DELAY=Délai de surveillance dossier (IMAP) :
+UI_IMAP_IDLE_DELAY_HELP=Délai de surveillance du dossier IMAP en minutes, laisser vide pour désactiver le support IDLE
+UI_IMAP_AUTO_EXPUNGE=IMAP suppression immédiate :
+UI_IMAP_AUTO_EXPUNGE_HELP=Supprimer immédiatement les messages du serveur via IMAP
+UI_POP_MARK_READ=POP marquer lu :
+UI_POP_MARK_READ_HELP=Marquer les messages lus sur le serveur immédiatement après chargement
+EXCEPTION_EWS_NOT_AVAILABLE=Point d''accès EWS non disponible
+EXCEPTION_FOLDER_NOT_FOUND=Dossier {0} non trouvé
+UNKNOWN_ATTRIBUTE=Attribut inconnu: {0}
+ACCEPTED=Accepté : 
+TENTATIVE=Provisoire : 
+DECLINED=Refusé : 
+UI_ENABLE_EWS=Protocole Exchange :
+UI_ENABLE_EWS_HELP=Activer  EWS sur Exchange 2010 ou Exchange 2007 sans support Webdav
+UI_CALDAV_NOTIFICATION=DavMail : Notification Caldav
+UI_BUTTON_SEND=Envoyer
+UI_SUBJECT=Sujet :
+UI_SUBJECT_HELP=Sujet notification Caldav
+UI_TO=Pour :  
+UI_TO_HELP=Destinataires
+UI_NOTIFICATION_BODY=Commentaire notification Caldav
+UI_CC=Copie à : 
+UI_CC_HELP=Destinataires en copie
+UI_CALDAV_EDIT_NOTIFICATIONS=Edition notifications Caldav :
+UI_CALDAV_EDIT_NOTIFICATIONS_HELP=Activer le fenêtre d'édition interactive des notifications
+LOG_SEARCH_RESULT={0} élément(s) trouvé(s)
+UI_SAVE_IN_SENT=SMTP copie dans Envoyés :
+UI_SAVE_IN_SENT_HELP=Créer une copie des messages envoyés via SMTP dans le dossier serveur "Envoyés"
+UI_CERTIFICATE_ALIAS_PROMPT=Choisir un certificat
\ No newline at end of file
diff --git a/src/java/log4j.properties b/src/java/log4j.properties
new file mode 100644
index 0000000..6d3b4cc
--- /dev/null
+++ b/src/java/log4j.properties
@@ -0,0 +1,19 @@
+# Warning : actual log levels set in davmail.properties 
+log4j.rootLogger=WARN, ConsoleAppender
+log4j.logger.davmail=DEBUG
+log4j.logger.httpclient.wire=WARN
+log4j.logger.org.apache.commons.httpclient=WARN
+
+# ConsoleAppender is set to be a ConsoleAppender.
+log4j.appender.ConsoleAppender=org.apache.log4j.ConsoleAppender
+
+# ConsoleAppender uses PatternLayout.
+log4j.appender.ConsoleAppender.layout=org.apache.log4j.PatternLayout
+log4j.appender.ConsoleAppender.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c %x - %m%n
+
+#log4j.logger.org.apache.commons.httpclient.util.IdleConnectionHandler=DEBUG
+#log4j.logger.org.apache.commons.httpclient.util.MultiThreadedHttpConnectionManager=DEBUG
+
+#log4j.appender.defaultSocketAppender=org.apache.log4j.net.SocketAppender
+#log4j.appender.defaultSocketAppender.RemoteHost=localhost
+#log4j.appender.defaultSocketAppender.port=4560
diff --git a/src/java/osxtray.png b/src/java/osxtray.png
new file mode 100644
index 0000000..fe1f313
Binary files /dev/null and b/src/java/osxtray.png differ
diff --git a/src/java/osxtray2.png b/src/java/osxtray2.png
new file mode 100644
index 0000000..18ee2b7
Binary files /dev/null and b/src/java/osxtray2.png differ
diff --git a/src/java/osxtrayinactive.png b/src/java/osxtrayinactive.png
new file mode 100644
index 0000000..dbf9b5e
Binary files /dev/null and b/src/java/osxtrayinactive.png differ
diff --git a/src/java/timezoneids.properties b/src/java/timezoneids.properties
new file mode 100644
index 0000000..8a54d6b
--- /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/java/timezones.properties b/src/java/timezones.properties
new file mode 100644
index 0000000..f264624
--- /dev/null
+++ b/src/java/timezones.properties
@@ -0,0 +1,148 @@
+# Timezone rename table to help iCal find the correct timezone
+(GMT)\ Monrovia/Casablanca=Africa/Casablanca
+(GMT)\ Monrovia/Reykjavik=Africa/Casablanca
+(GMT)\ Casablanca=Africa/Casablanca
+(GMT)\ Greenwich\ Mean\ Time/Dublin/Edinburgh/London=Europe/London
+(GMT+01.00)\ Berlin/Stockholm/Rome/Bern/Vienna=Europe/Berlin
+(GMT+01.00)\ Paris/Madrid/Brussels/Copenhagen=Europe/Paris
+(GMT+01\:00)\ Brussels,\ Copenhagen,\ Madrid,\ Paris=Europe/Paris
+(GMT+01.00)\ Prague/Central\ Europe=Europe/Prague
+(GMT+01.00)\ Sarajevo/Warsaw/Zagreb=Europe/Sarajevo
+(GMT+01.00)\ West\ Central\ Africa=Africa/Algiers
+(GMT+02.00)\ Athens/Istanbul/Minsk=Europe/Athens
+(GMT+02.00)\ Athens/Bucharest/Istanbul=Europe/Athens
+(GMT+02.00)\ Bucharest/Eastern\ Europe=Europe/Bucharest
+(GMT+02.00)\ Minsk/Eastern\ Europe=Europe/Bucharest
+(GMT+02.00)\ Cairo=Africa/Cairo
+(GMT+02.00)\ Harare/Pretoria=Africa/Harare
+(GMT+02.00)\ Helsinki/Riga/Tallinn=Europe/Helsinki
+(GMT+02.00)\ Israel/Jerusalem\ Standard\ Time=Asia/Jerusalem
+(GMT+02.00)\ Amman=Asia/Amman
+(GMT+02.00)\ Athens/Bucharest=Europe/Athens
+(GMT+02.00)\ Damascus=Asia/Damascus
+(GMT+02.00)\ Istanbul=Asia/Istanbul
+(GMT+02.00)\ Windhoek=Africa/Windhoek
+(GMT+03.00)\ Baghdad=Asia/Baghdad
+(GMT+03.00)\ Arab/Kuwait/Riyadh=Asia/Kuwait
+(GMT+03.00)\ Moscow/St.\ Petersburg/Volgograd=Europe/Moscow
+(GMT+03.00)\ East\ Africa/Nairobi=Africa/Nairobi
+(GMT+03.00)\ Kaliningrad=Europe/Kaliningrad
+(GMT+03.00)\ Kuwait/Riyadh=Asia/Kuwait
+(GMT+03.00)\ Tbilisi=Asia/Tbilisi
+(GMT+03.30)\ Tehran=Asia/Tehran
+(GMT+04.00)\ Abu\ Dhabi/Muscat=Asia/Dubai
+(GMT+04.00)\ Caucasus/Baku/Tbilisi/Yerevan=Asia/Tbilisi
+(GMT+04.00)\ Baku=Asia/Baku
+(GMT+04.00)\ Caucasus\ Standard\ Time=Asia/Baku
+(GMT+04.00)\ Port\ Louis=Asia/Baku
+(GMT+04.00)\ Yerevan=Asia/Baku
+(GMT+04.30)\ Kabul=Asia/Kabul
+(GMT+05.00)\ Ekaterinburg=Asia/Yekaterinburg
+(GMT+05.00)\ Islamabad/Karachi/Sverdlovsk/Tashkent=Asia/Tashkent
+(GMT+05.00)\ Islamabad/Karachi=Asia/Karachi
+(GMT+05.00)\ Sverdlovsk/Tashkent=Asia/Tashkent
+(GMT+05.30)\ Calcutta/Chennai/Mumbai/New\ Delhi/India\ Standard\ Time=Asia/Kolkata
+(GMT+05.30)\ Kolkata/Chennai/Mumbai/New\ Delhi/India\ Standard\ Time=Asia/Kolkata
+(GMT+05.30)\ Sri\ Jayawardenepura/Sri\ Lanka=Asia/Kolkata
+(GMT+05.45)\ Kathmandu/Nepal=Asia/Katmandu
+(GMT+06.00)\ Almaty/North\ Central\ Asia/Novosibirsk=Asia/Almaty
+(GMT+06.00)\ Astana/Dhaka=Asia/Dhaka
+(GMT+06.00)\ Sri\ Jayawardenepura/Sri\ Lanka=Asia/Kolkata
+(GMT+06.00)\ Astana=Asia/Dhaka
+(GMT+06.00)\ Dhaka=Asia/Dhaka
+(GMT+06.00)\ Novosibirsk=Asia/Novosibirsk
+(GMT+06.30)\ Rangoon=Asia/Rangoon
+(GMT+07.00)\ Bangkok/Jakarta/Hanoi=Asia/Bangkok
+(GMT+07.00)\ Krasnoyarsk/North\ Asia=Asia/Krasnoyarsk
+(GMT+08.00)\ Beijing/Chongqing/Hong\ Kong/Urumqi=Asia/Hong_Kong
+(GMT+08.00)\ Irkutsk/Ulaan\ Bataar=Asia/Irkutsk
+(GMT+08.00)\ Kuala\ Lumpur/Singapore=Asia/Kuala_Lumpur
+(GMT+08.00)\ Perth/Western\ Australia=Australia/Perth
+(GMT+08.00)\ Taipei=Asia/Taipei
+(GMT+08.00)\ Beijing/Chongqing/Hong\ Kong\ SAR/Urumqi=Asia/Beijing
+(GMT+08.00)\ Irkutsk=Asia/Irkutsk
+(GMT+08.00)\ Ulaanbaatar=Asia/Ulaanbaatar
+(GMT+09.00)\ Tokyo/Osaka/Sapporo=Asia/Tokyo
+(GMT+09.00)\ Seoul/Korea\ Standard\ Time=Asia/Seoul
+(GMT+09.00)\ Yakutsk=Asia/Yakutsk
+(GMT+09.30)\ Adelaide/Central\ Australia=Australia/Adelaide
+(GMT+09.30)\ Darwin=Australia/Darwin
+(GMT+09.30)\ Adelaide\ (Commonwealth\ Games)=Australia/Adelaide
+(GMT+10.00)\ Canberra,\ Melbourne,\ Sydney,\ Hobart\ (Year\ 2000\ only)=Australia/Melbourne
+(GMT+10.00)\ Canberra,\ Melbourne,\ Sydney\ (Commonwealth\ Games)=Australia/Melbourne
+(GMT+10.00)\ Hobart\ (Commonwealth\ Games)=Australia/Melbourne
+(GMT+10.00)\ Brisbane/East\ Australia=Australia/Brisbane
+(GMT+10.00)\ Canberra/Melbourne/Sydney/Hobart\ (Year\ 2000\ only)=Australia/Melbourne
+(GMT+10.00)\ Guam/Port\ Moresby=Pacific/Guam
+(GMT+10.00)\ Hobart/Tasmania=Australia/Hobart
+(GMT+10.00)\ Vladivostok=Asia/Vladivostok
+(GMT+10.00)\ Melbourne/Sydney=Australia/Melbourne
+(GMT+11.00)\ Magadan/Solomon\ Is./New\ Caledonia=Asia/Magadan
+(GMT+11.00)\ Solomon\ Is./New\ Caledonia=Pacific/Noumea
+(GMT+12.00)\ Wellington/Auckland=Pacific/Auckland
+(GMT+12.00)\ Fiji/Kamchatka/Marshall\ Is.=Pacific/Fiji
+(GMT+12.00)\ Coordinated\ Universal\ Time+12=Pacific/Fiji
+(GMT+12.00)\ Fiji=Pacific/Fiji
+(GMT+12.00)\ Magadan=Asia/Magadan
+(GMT+12.00)\ Petropavlovsk-Kamchatsky\ -\ Old=
+(GMT+13.00)\ Tonga/Nuku'alofa=Pacific/Tongatapu
+(GMT+13.00)\ Nuku'alofa=Pacific/Tongatapu
+(GMT-01.00)\ Azores=Atlantic/Azores
+(GMT-01.00)\ Cape\ Verde\ Is.=Atlantic/Cape_Verde
+(GMT-02.00)\ Mid-Atlantic=Atlantic/South_Georgia
+(GMT-02.00)\ Coordinated\ Universal\ Time-02=Atlantic/South_Georgia
+(GMT-03.00)\ Brasilia=America/Sao_Paulo
+(GMT-03.00)\ Georgetown=America/Argentina/Buenos_Aires
+(GMT-03.00)\ Buenos\ Aires/Georgetown=America/Argentina/Buenos_Aires
+(GMT-03.00)\ Greenland=America/Godthab
+(GMT-03.00)\ Buenos\ Aires=America/Argentina/Buenos_Aires
+(GMT-03.00)\ Cayenne/Fortaleza=America/Cayenne
+(GMT-03.00)\ Montevideo=America/Montevideo
+(GMT-03.30)\ Newfoundland=America/St_Johns
+(GMT-04.00)\ Atlantic\ Time\ (Canada)=America/Halifax
+(GMT-04.00)\ Caracas/La\ Paz=America/Caracas
+(GMT-04.00)\ Santiago=America/Santiago
+(GMT-04.00)\ Asuncion=America/Asuncion
+(GMT-04.00)\ Cuiaba=America/Cuiaba
+(GMT-04.00)\ Georgetown/La\ Paz/Manaus/San\ Juan=America/Manaus
+(GMT-04.30)\ Caracas=America/Caracas
+(GMT-05.00)\ Bogota/Lima=America/Lima
+(GMT-05.00)\ Bogota/Lima/Quito=America/Lima
+SA\ Pacific\ Standard\ Time=America/Lima
+(GMT-05.00)\ Eastern\ Time\ (US\ &\ Canada)=America/New_York
+(GMT-05.00)\ Indiana\ (East)=America/Indiana/Knox
+(GMT-06.00)\ Central\ America=America/Chicago
+(GMT-06.00)\ Central\ Time\ (US\ &\ Canada)=America/Chicago
+(GMT-06.00)\ Mexico\ City/Tegucigalpa=America/Mexico_City
+(GMT-06.00)\ Saskatchewan=America/Regina
+(GMT-07.00)\ Arizona=America/Phoenix
+(GMT-07.00)\ Mountain\ Time\ (US\ &\ Canada)=America/Denver
+(GMT-07.00)\ Chihuahua/La\ Paza/Mazatlan=America/Chihuahua
+(GMT-08.00)\ Pacific\ Time\ (US\ &\ Canada)/Tijuana=America/Los_Angeles
+(GMT-08.00)\ Pacific\ Time\ (US\ &\ Canada)=America/Los_Angeles
+(GMT-08.00)\ Baja\ California=America/Los_Angeles
+(GMT-0800)\ Pacific\ Standard\ Time=America/Los_Angeles
+Pacific\ Standard\ Time=America/Los_Angeles
+(GMT-09.00)\ Alaska=America/Anchorage
+(GMT-10.00)\ Hawaii=Pacific/Honolulu
+(GMT-11.00)\ Midway\ Island/Samoa=Pacific/Pago_Pago
+(GMT-11.00)\ Coordinated\ Universal\ Time-11=Pacific/Samoa
+(GMT-11.00)\ Samoa=Pacific/Samoa
+(GMT-12.00)\ Eniwetok/Kwajalein/Dateline\ Time=Pacific/Kwajalein
+GMT\ -0000\ (Standard)\ /\ GMT\ -0000\ (Daylight)=Africa/Casablanca
+GMT\ -0000\ (Standard)\ /\ GMT\ +0100\ (Daylight)=Europe/London
+GMT\ +0100\ (Standard)\ /\ GMT\ +0200\ (Daylight)=Europe/Paris
+GMT\ +0200\ (Standard)\ /\ GMT\ +0200\ (Daylight)=Africa/Harare
+GMT\ +0200\ (Standard)\ /\ GMT\ +0300\ (Daylight)=Europe/Bucharest
+GMT\ +0400\ (Standard)\ /\ GMT\ +0400\ (Daylight)=Asia/Dubai
+GMT\ +0800\ (Standard)\ /\ GMT\ +0900\ (Daylight)=Asia/Kuala_Lumpur
+GMT\ +1000\ (Standard)\ /\ GMT\ +1100\ (Daylight)=Australia/Melbourne
+GMT\ -0300\ (Standard)\ /\ GMT\ -0200\ (Daylight)=America/Sao_Paulo
+GMT\ -0500\ (Standard)\ /\ GMT\ -0400\ (Daylight)=America/New_York
+GMT\ -0600\ (Standard)\ /\ GMT\ -0500\ (Daylight)=America/Chicago
+GMT\ -0700\ (Standard)\ /\ GMT\ -0600\ (Daylight)=America/Denver
+GMT\ -0800\ (Standard)\ /\ GMT\ -0700\ (Daylight)=America/Los_Angeles
+GMT\ Standard\ Time=GMT Standard Time
+Greenwich\ Standard\ Time=Greenwich Standard Time
+Mexico\ Standard\ Time=America/Mexico_City
+(GMT)\ Coordinated\ Universal\ Time=Greenwich Standard Time
diff --git a/src/java/tray.png b/src/java/tray.png
new file mode 100644
index 0000000..9d3f243
Binary files /dev/null and b/src/java/tray.png differ
diff --git a/src/java/tray2.png b/src/java/tray2.png
new file mode 100644
index 0000000..9168427
Binary files /dev/null and b/src/java/tray2.png differ
diff --git a/src/java/tray32.png b/src/java/tray32.png
new file mode 100644
index 0000000..0ecb943
Binary files /dev/null and b/src/java/tray32.png differ
diff --git a/src/java/tray48.png b/src/java/tray48.png
new file mode 100644
index 0000000..7129e47
Binary files /dev/null and b/src/java/tray48.png differ
diff --git a/src/java/trayinactive.png b/src/java/trayinactive.png
new file mode 100644
index 0000000..71b79c2
Binary files /dev/null and b/src/java/trayinactive.png differ
diff --git a/src/jsmooth/skeletons/autodownload-wrapper/autodownload.exe b/src/jsmooth/skeletons/autodownload-wrapper/autodownload.exe
new file mode 100644
index 0000000..d97c73d
Binary files /dev/null and b/src/jsmooth/skeletons/autodownload-wrapper/autodownload.exe differ
diff --git a/src/jsmooth/skeletons/autodownload-wrapper/autodownload.skel b/src/jsmooth/skeletons/autodownload-wrapper/autodownload.skel
new file mode 100644
index 0000000..c764afe
--- /dev/null
+++ b/src/jsmooth/skeletons/autodownload-wrapper/autodownload.skel
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<jsmoothskeleton>
+<debug>false</debug>
+<description>SKEL_AUTODOWNLOAD_PROPERTY_DESCRIPTION</description>
+<executableName>autodownload.exe</executableName>
+<resourceCategory>JAVA</resourceCategory>
+<resourceJarId>102</resourceJarId>
+<resourcePropsId>103</resourcePropsId>
+<shortName>Autodownload Wrapper</shortName>
+<skeletonProperties>
+<description>SKEL_AUTODOWNLOAD_PROPERTY_MESSAGE_DESCRIPTION</description>
+<idName>Message</idName>
+<label>SKEL_AUTODOWNLOAD_PROPERTY_MESSAGE_LABEL</label>
+<type>textarea</type>
+<value>Java has not been found on your computer. Do you want to download it?</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_AUTODOWNLOAD_PROPERTY_DLURL_DESCRIPTION</description>
+<idName>DownloadURL</idName>
+<label>SKEL_AUTODOWNLOAD_PROPERTY_DLURL_LABEL</label>
+<type>autodownloadurl</type>
+<value>http://java.sun.com/update/1.5.0/jinstall-1_5_0_11-windows-i586.cab</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_PROPERTY_SINGLEPROCESS_DESCRIPTION</description>
+<idName>SingleProcess</idName>
+<label>SKEL_GENERIC_PROPERTY_SINGLEPROCESS_LABEL</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_SINGLEINSTANCE_DESCRIPTION</description>
+<idName>SingleInstance</idName>
+<label>SKEL_GENERIC_SINGLEINSTANCE</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_JNISMOOTH_DESCRIPTION</description>
+<idName>JniSmooth</idName>
+<label>SKEL_GENERIC_JNISMOOTH</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_PROPERTY_DEBUG_DESCRIPTION</description>
+<idName>Debug</idName>
+<label>SKEL_GENERIC_PROPERTY_DEBUG_LABEL</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+</jsmoothskeleton>
diff --git a/src/jsmooth/skeletons/autodownload-wrapper/customdownload.skel b/src/jsmooth/skeletons/autodownload-wrapper/customdownload.skel
new file mode 100644
index 0000000..9ee12a1
--- /dev/null
+++ b/src/jsmooth/skeletons/autodownload-wrapper/customdownload.skel
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<jsmoothskeleton>
+<debug>false</debug>
+<description>SKEL_CUSTOMDOWNLOAD_DESCRIPTION</description>
+<executableName>autodownload.exe</executableName>
+<resourceCategory>JAVA</resourceCategory>
+<resourceJarId>102</resourceJarId>
+<resourcePropsId>103</resourcePropsId>
+<shortName>Custom Web Download Wrapper</shortName>
+<skeletonProperties>
+<description>SKEL_CUSTOMDOWNLOAD_PROPERTY_MESSAGE_DESCRIPTION</description>
+<idName>Message</idName>
+<label>SKEL_CUSTOMDOWNLOAD_PROPERTY_MESSAGE_LABEL</label>
+<type>textarea</type>
+<value>Java has not been found on your computer. Do you want to download it?</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_CUSTOMDOWNLOAD_PROPERTY_URL_DESCRIPTION</description>
+<idName>DownloadURL</idName>
+<label>SKEL_CUSTOMDOWNLOAD_PROPERTY_URL_LABEL</label>
+<type>string</type>
+<value>http://</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_PROPERTY_SINGLEPROCESS_DESCRIPTION</description>
+<idName>SingleProcess</idName>
+<label>SKEL_GENERIC_PROPERTY_SINGLEPROCESS_LABEL</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_SINGLEINSTANCE_DESCRIPTION</description>
+<idName>SingleInstance</idName>
+<label>SKEL_GENERIC_SINGLEINSTANCE</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_JNISMOOTH_DESCRIPTION</description>
+<idName>JniSmooth</idName>
+<label>SKEL_GENERIC_JNISMOOTH</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_PROPERTY_DEBUG_DESCRIPTION</description>
+<idName>Debug</idName>
+<label>SKEL_GENERIC_PROPERTY_DEBUG_LABEL</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+</jsmoothskeleton>
diff --git a/src/jsmooth/skeletons/console-wrapper/consolewrapper.exe b/src/jsmooth/skeletons/console-wrapper/consolewrapper.exe
new file mode 100644
index 0000000..abc3ac9
Binary files /dev/null and b/src/jsmooth/skeletons/console-wrapper/consolewrapper.exe differ
diff --git a/src/jsmooth/skeletons/console-wrapper/description.skel b/src/jsmooth/skeletons/console-wrapper/description.skel
new file mode 100644
index 0000000..4a8b8ac
--- /dev/null
+++ b/src/jsmooth/skeletons/console-wrapper/description.skel
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<jsmoothskeleton>
+<debug>false</debug>
+<description>SKEL_CONSOLEWRAPPER_DESCRIPTION</description>
+<executableName>consolewrapper.exe</executableName>
+<resourceCategory>JAVA</resourceCategory>
+<resourceJarId>102</resourceJarId>
+<resourcePropsId>103</resourcePropsId>
+<shortName>Console Wrapper</shortName>
+<skeletonProperties>
+<description>SKEL_CUSTOMWRAPPER_PROPERTY_MESSAGE_DESCRIPTION</description>
+<idName>Message</idName>
+<label>SKEL_CUSTOMWRAPPER_PROPERTY_MESSAGE_LABEL</label>
+<type>textarea</type>
+<value>This program needs Java to run.
+Please download it at http://www.java.com</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_CUSTOMWRAPPER_PROPERTY_KEYPRESS_DESCRIPTION</description>
+<idName>PressKey</idName>
+<label>SKEL_CUSTOMWRAPPER_PROPERTY_KEYPRESS_LABEL</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_PROPERTY_DEBUG_DESCRIPTION</description>
+<idName>Debug</idName>
+<label>SKEL_GENERIC_PROPERTY_DEBUG_LABEL</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+</jsmoothskeleton>
diff --git a/src/jsmooth/skeletons/windowed-wrapper64/description64.skel b/src/jsmooth/skeletons/windowed-wrapper64/description64.skel
new file mode 100644
index 0000000..6772244
--- /dev/null
+++ b/src/jsmooth/skeletons/windowed-wrapper64/description64.skel
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<jsmoothskeleton>
+<debug>false</debug>
+<description>SKEL_SIMPLEWRAPPER_DESCRIPTION</description>
+<executableName>windowed-wrapper64.exe</executableName>
+<resourceCategory>JAVA</resourceCategory>
+<resourceJarId>102</resourceJarId>
+<resourcePropsId>103</resourcePropsId>
+<shortName>Windowed Wrapper x64</shortName>
+<skeletonProperties>
+<description>SKEL_SIMPLEWRAPPER_PROPERTY_MESSAGE_DESCRIPTION</description>
+<idName>Message</idName>
+<label>SKEL_SIMPLEWRAPPER_PROPERTY_MESSAGE_LABEL</label>
+<type>textarea</type>
+<value>Java has not been found on your computer. Do you want to download it?</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_SIMPLEWRAPPER_PROPERTY_URL_DESCRIPTION</description>
+<idName>URL</idName>
+<label>SKEL_SIMPLEWRAPPER_PROPERTY_URL_LABEL</label>
+<type>string</type>
+<value>http://www.java.com</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_PROPERTY_SINGLEPROCESS_DESCRIPTION</description>
+<idName>SingleProcess</idName>
+<label>SKEL_GENERIC_PROPERTY_SINGLEPROCESS_LABEL</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_SINGLEINSTANCE_DESCRIPTION</description>
+<idName>SingleInstance</idName>
+<label>SKEL_GENERIC_SINGLEINSTANCE</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_JNISMOOTH_DESCRIPTION</description>
+<idName>JniSmooth</idName>
+<label>SKEL_GENERIC_JNISMOOTH</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_PROPERTY_DEBUG_DESCRIPTION</description>
+<idName>Debug</idName>
+<label>SKEL_GENERIC_PROPERTY_DEBUG_LABEL</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+</jsmoothskeleton>
diff --git a/src/jsmooth/skeletons/windowed-wrapper64/windowed-wrapper64.exe b/src/jsmooth/skeletons/windowed-wrapper64/windowed-wrapper64.exe
new file mode 100644
index 0000000..3078cb9
Binary files /dev/null and b/src/jsmooth/skeletons/windowed-wrapper64/windowed-wrapper64.exe differ
diff --git a/src/jsmooth/skeletons/winservice-wrapper/description.skel b/src/jsmooth/skeletons/winservice-wrapper/description.skel
new file mode 100644
index 0000000..11bfa94
--- /dev/null
+++ b/src/jsmooth/skeletons/winservice-wrapper/description.skel
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<jsmoothskeleton>
+<debug>false</debug>
+<description>SKEL_WINSERVICE_DESCRIPTION</description>
+<executableName>winservice.exe</executableName>
+<resourceCategory>JAVA</resourceCategory>
+<resourceJarId>102</resourceJarId>
+<resourcePropsId>103</resourcePropsId>
+<shortName>WinService Wrapper</shortName>
+<skeletonProperties>
+<description>SKEL_WINSERVICE_NAME_DESCRIPTION</description>
+<idName>ServiceName</idName>
+<label>SKEL_WINSERVICE_NAME_LABEL</label>
+<type>string</type>
+<value>myservice</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_WINSERVICE_DISPLAYNAME_DESCRIPTION</description>
+<idName>ServiceDisplayName</idName>
+<label>SKEL_WINSERVICE_DISPLAYNAME_LABEL</label>
+<type>string</type>
+<value>My Service</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_WINSERVICE_PROPERTY_MESSAGE_DESCRIPTION</description>
+<idName>Message</idName>
+<label>SKEL_WINSERVICE_PROPERTY_MESSAGE_LABEL</label>
+<type>textarea</type>
+<value>This program needs Java to run. Please download it at http://www.java.comf</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_WINSERVICE_AUTOSTART_DESCRIPTION</description>
+<idName>Autostart</idName>
+<label>SKEL_WINSERVICE_AUTOSTART_LABEL</label>
+<type>boolean</type>
+<value>1</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_WINSERVICE_LOGFILE_DESCRIPTION</description>
+<idName>Logfile</idName>
+<label>SKEL_WINSERVICE_LOGFILE_LABEL</label>
+<type>string</type>
+<value>service.log</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_WINSERVICE_INTERACTIVE_DESCRIPTION</description>
+<idName>Interactive</idName>
+<label>SKEL_WINSERVICE_INTERACTIVE_LABEL</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_JNISMOOTH_DESCRIPTION</description>
+<idName>JniSmooth</idName>
+<label>SKEL_GENERIC_JNISMOOTH</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_PROPERTY_DEBUG_DESCRIPTION</description>
+<idName>Debug</idName>
+<label>SKEL_GENERIC_PROPERTY_DEBUG_LABEL</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+</jsmoothskeleton>
diff --git a/src/jsmooth/skeletons/winservice-wrapper/winservice.exe b/src/jsmooth/skeletons/winservice-wrapper/winservice.exe
new file mode 100644
index 0000000..de5c0ac
Binary files /dev/null and b/src/jsmooth/skeletons/winservice-wrapper/winservice.exe differ
diff --git a/src/license.txt b/src/license.txt
new file mode 100644
index 0000000..d511905
--- /dev/null
+++ b/src/license.txt
@@ -0,0 +1,339 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/src/osx/davmail b/src/osx/davmail
new file mode 100644
index 0000000..2fe00d3
Binary files /dev/null and b/src/osx/davmail differ
diff --git a/src/osx/tray.icns b/src/osx/tray.icns
new file mode 100644
index 0000000..c0b3093
Binary files /dev/null and b/src/osx/tray.icns differ
diff --git a/src/site/resources/css/site.css b/src/site/resources/css/site.css
new file mode 100644
index 0000000..7c9be4d
--- /dev/null
+++ b/src/site/resources/css/site.css
@@ -0,0 +1,78 @@
+/* You can override this file with your own styles */
+body {
+        background:#F1FAFF url(../images/inner-gd.png) repeat-x scroll 0 0;
+}
+#breadcrumbs {
+  background-color: white;
+  border:0;
+}
+.externalLink img {
+	vertical-align: middle;
+}
+.section h2, .section h3 {
+	border:0;
+	background: transparent;
+	color:black;
+	font-size:17px;
+}
+.section h2 {
+	border-bottom:1px solid;
+	padding:1px;
+}
+.section h3 {
+	margin-top: 0;
+    margin-bottom: 0;
+	font-size:16px;
+	font-weight:bold;
+}
+.section p {
+	padding-left:15px;
+}
+
+
+
+.section table {
+	margin-left:10px;
+	border-collapse: collapse;
+}
+.small {
+	font-size:9px;
+}
+.source {
+	overflow:auto;
+}
+.floatLeft {
+	float:left;
+}
+.notype {
+	list-style-type:none;
+	margin-left:10px;
+	padding-left:0;
+}
+.about td{
+  vertical-align: top;
+}
+a.download {
+ display: block;
+  background: #5ACE1D url(../images/download.png) 95% center no-repeat;
+  color: white;
+  font-weight: bold;
+  padding: 8px;
+  -moz-border-radius: 8px;
+  -webkit-border-radius: 10px;
+  margin: 5px;
+}
+a.donate {
+ display: block;
+  background: #5ACE1D url(../images/donate.png) 95% center no-repeat;
+  color: white;
+  font-weight: bold;
+  padding: 8px;
+  -moz-border-radius: 8px;
+  -webkit-border-radius: 10px;
+  margin: 5px;
+}
+.about p {
+  padding: 0;
+  margin-top: 0;
+}
diff --git a/src/site/resources/favicon.ico b/src/site/resources/favicon.ico
new file mode 100644
index 0000000..508c444
Binary files /dev/null and b/src/site/resources/favicon.ico differ
diff --git a/src/site/resources/images/davmailArchitecture.png b/src/site/resources/images/davmailArchitecture.png
new file mode 100644
index 0000000..ba3580c
Binary files /dev/null and b/src/site/resources/images/davmailArchitecture.png differ
diff --git a/src/site/resources/images/davmailLogo.png b/src/site/resources/images/davmailLogo.png
new file mode 100644
index 0000000..c4c4ea7
Binary files /dev/null and b/src/site/resources/images/davmailLogo.png differ
diff --git a/src/site/resources/images/davmailSettings.png b/src/site/resources/images/davmailSettings.png
new file mode 100644
index 0000000..290af23
Binary files /dev/null and b/src/site/resources/images/davmailSettings.png differ
diff --git a/src/site/resources/images/donate.png b/src/site/resources/images/donate.png
new file mode 100644
index 0000000..98caae4
Binary files /dev/null and b/src/site/resources/images/donate.png differ
diff --git a/src/site/resources/images/download.png b/src/site/resources/images/download.png
new file mode 100644
index 0000000..27d3999
Binary files /dev/null and b/src/site/resources/images/download.png differ
diff --git a/src/site/resources/images/inner-gd.png b/src/site/resources/images/inner-gd.png
new file mode 100644
index 0000000..0e711e7
Binary files /dev/null and b/src/site/resources/images/inner-gd.png differ
diff --git a/src/site/resources/images/iphone/iphoneAccount1.png b/src/site/resources/images/iphone/iphoneAccount1.png
new file mode 100644
index 0000000..e246cb3
Binary files /dev/null and b/src/site/resources/images/iphone/iphoneAccount1.png differ
diff --git a/src/site/resources/images/iphone/iphoneAccount2.png b/src/site/resources/images/iphone/iphoneAccount2.png
new file mode 100644
index 0000000..07d9db3
Binary files /dev/null and b/src/site/resources/images/iphone/iphoneAccount2.png differ
diff --git a/src/site/resources/images/iphone/iphoneAccount3.png b/src/site/resources/images/iphone/iphoneAccount3.png
new file mode 100644
index 0000000..633e43d
Binary files /dev/null and b/src/site/resources/images/iphone/iphoneAccount3.png differ
diff --git a/src/site/resources/images/iphone/iphoneCaldav1.png b/src/site/resources/images/iphone/iphoneCaldav1.png
new file mode 100644
index 0000000..2db45c2
Binary files /dev/null and b/src/site/resources/images/iphone/iphoneCaldav1.png differ
diff --git a/src/site/resources/images/iphone/iphoneCaldav2.png b/src/site/resources/images/iphone/iphoneCaldav2.png
new file mode 100644
index 0000000..6de5eea
Binary files /dev/null and b/src/site/resources/images/iphone/iphoneCaldav2.png differ
diff --git a/src/site/resources/images/iphone/iphoneCaldav3.png b/src/site/resources/images/iphone/iphoneCaldav3.png
new file mode 100644
index 0000000..259f3f3
Binary files /dev/null and b/src/site/resources/images/iphone/iphoneCaldav3.png differ
diff --git a/src/site/resources/images/iphone/iphoneCaldav4.png b/src/site/resources/images/iphone/iphoneCaldav4.png
new file mode 100644
index 0000000..9b52137
Binary files /dev/null and b/src/site/resources/images/iphone/iphoneCaldav4.png differ
diff --git a/src/site/resources/images/iphone/iphoneCarddav1.png b/src/site/resources/images/iphone/iphoneCarddav1.png
new file mode 100644
index 0000000..c991c93
Binary files /dev/null and b/src/site/resources/images/iphone/iphoneCarddav1.png differ
diff --git a/src/site/resources/images/iphone/iphoneCarddav2.png b/src/site/resources/images/iphone/iphoneCarddav2.png
new file mode 100644
index 0000000..280bdc7
Binary files /dev/null and b/src/site/resources/images/iphone/iphoneCarddav2.png differ
diff --git a/src/site/resources/images/iphone/iphoneCarddav3.png b/src/site/resources/images/iphone/iphoneCarddav3.png
new file mode 100644
index 0000000..3d6c484
Binary files /dev/null and b/src/site/resources/images/iphone/iphoneCarddav3.png differ
diff --git a/src/site/resources/images/iphone/iphoneCarddav4.png b/src/site/resources/images/iphone/iphoneCarddav4.png
new file mode 100644
index 0000000..f3fa88b
Binary files /dev/null and b/src/site/resources/images/iphone/iphoneCarddav4.png differ
diff --git a/src/site/resources/images/iphone/iphoneMail01.png b/src/site/resources/images/iphone/iphoneMail01.png
new file mode 100644
index 0000000..6154d79
Binary files /dev/null and b/src/site/resources/images/iphone/iphoneMail01.png differ
diff --git a/src/site/resources/images/iphone/iphoneMail02.png b/src/site/resources/images/iphone/iphoneMail02.png
new file mode 100644
index 0000000..e5f8f57
Binary files /dev/null and b/src/site/resources/images/iphone/iphoneMail02.png differ
diff --git a/src/site/resources/images/iphone/iphoneMail03.png b/src/site/resources/images/iphone/iphoneMail03.png
new file mode 100644
index 0000000..4950fbf
Binary files /dev/null and b/src/site/resources/images/iphone/iphoneMail03.png differ
diff --git a/src/site/resources/images/iphone/iphoneMail04.png b/src/site/resources/images/iphone/iphoneMail04.png
new file mode 100644
index 0000000..20e8aa4
Binary files /dev/null and b/src/site/resources/images/iphone/iphoneMail04.png differ
diff --git a/src/site/resources/images/iphone/iphoneMail05.png b/src/site/resources/images/iphone/iphoneMail05.png
new file mode 100644
index 0000000..0a49c60
Binary files /dev/null and b/src/site/resources/images/iphone/iphoneMail05.png differ
diff --git a/src/site/resources/images/iphone/iphoneMail06.png b/src/site/resources/images/iphone/iphoneMail06.png
new file mode 100644
index 0000000..f659bc4
Binary files /dev/null and b/src/site/resources/images/iphone/iphoneMail06.png differ
diff --git a/src/site/resources/images/iphone/iphoneMail07.png b/src/site/resources/images/iphone/iphoneMail07.png
new file mode 100644
index 0000000..d81ec0a
Binary files /dev/null and b/src/site/resources/images/iphone/iphoneMail07.png differ
diff --git a/src/site/resources/images/iphone/iphoneMail08.png b/src/site/resources/images/iphone/iphoneMail08.png
new file mode 100644
index 0000000..83deb84
Binary files /dev/null and b/src/site/resources/images/iphone/iphoneMail08.png differ
diff --git a/src/site/resources/images/iphone/iphoneMail09.png b/src/site/resources/images/iphone/iphoneMail09.png
new file mode 100644
index 0000000..b3b58ec
Binary files /dev/null and b/src/site/resources/images/iphone/iphoneMail09.png differ
diff --git a/src/site/resources/images/iphone/iphoneMail10.png b/src/site/resources/images/iphone/iphoneMail10.png
new file mode 100644
index 0000000..b391d4e
Binary files /dev/null and b/src/site/resources/images/iphone/iphoneMail10.png differ
diff --git a/src/site/resources/images/iphone/iphoneMail11.png b/src/site/resources/images/iphone/iphoneMail11.png
new file mode 100644
index 0000000..5062d5d
Binary files /dev/null and b/src/site/resources/images/iphone/iphoneMail11.png differ
diff --git a/src/site/resources/images/iphone/iphoneMail12.png b/src/site/resources/images/iphone/iphoneMail12.png
new file mode 100644
index 0000000..4d40323
Binary files /dev/null and b/src/site/resources/images/iphone/iphoneMail12.png differ
diff --git a/src/site/resources/images/iphone/iphoneMail13.png b/src/site/resources/images/iphone/iphoneMail13.png
new file mode 100644
index 0000000..e9804bd
Binary files /dev/null and b/src/site/resources/images/iphone/iphoneMail13.png differ
diff --git a/src/site/resources/images/iphone/iphoneMail14.png b/src/site/resources/images/iphone/iphoneMail14.png
new file mode 100644
index 0000000..ee54ea5
Binary files /dev/null and b/src/site/resources/images/iphone/iphoneMail14.png differ
diff --git a/src/site/resources/images/iphone/iphoneMail15.png b/src/site/resources/images/iphone/iphoneMail15.png
new file mode 100644
index 0000000..99302df
Binary files /dev/null and b/src/site/resources/images/iphone/iphoneMail15.png differ
diff --git a/src/site/resources/images/iphone/iphoneMail16.png b/src/site/resources/images/iphone/iphoneMail16.png
new file mode 100644
index 0000000..637c7c8
Binary files /dev/null and b/src/site/resources/images/iphone/iphoneMail16.png differ
diff --git a/src/site/resources/images/osxDirectoryUtility1.png b/src/site/resources/images/osxDirectoryUtility1.png
new file mode 100644
index 0000000..ada8994
Binary files /dev/null and b/src/site/resources/images/osxDirectoryUtility1.png differ
diff --git a/src/site/resources/images/osxDirectoryUtility2.png b/src/site/resources/images/osxDirectoryUtility2.png
new file mode 100644
index 0000000..9a09371
Binary files /dev/null and b/src/site/resources/images/osxDirectoryUtility2.png differ
diff --git a/src/site/resources/images/osxDirectoryUtility3.png b/src/site/resources/images/osxDirectoryUtility3.png
new file mode 100644
index 0000000..7291f28
Binary files /dev/null and b/src/site/resources/images/osxDirectoryUtility3.png differ
diff --git a/src/site/resources/images/osxDirectoryUtility4.png b/src/site/resources/images/osxDirectoryUtility4.png
new file mode 100644
index 0000000..c392433
Binary files /dev/null and b/src/site/resources/images/osxDirectoryUtility4.png differ
diff --git a/src/site/resources/images/osxDirectoryUtility5.png b/src/site/resources/images/osxDirectoryUtility5.png
new file mode 100644
index 0000000..f9a5540
Binary files /dev/null and b/src/site/resources/images/osxDirectoryUtility5.png differ
diff --git a/src/site/resources/images/osxDirectoryUtility6.png b/src/site/resources/images/osxDirectoryUtility6.png
new file mode 100644
index 0000000..b6c47d8
Binary files /dev/null and b/src/site/resources/images/osxDirectoryUtility6.png differ
diff --git a/src/site/resources/images/osxDirectoryUtility7.png b/src/site/resources/images/osxDirectoryUtility7.png
new file mode 100644
index 0000000..0d77abd
Binary files /dev/null and b/src/site/resources/images/osxDirectoryUtility7.png differ
diff --git a/src/site/resources/images/osxDirectoryUtility8.png b/src/site/resources/images/osxDirectoryUtility8.png
new file mode 100644
index 0000000..6def3e3
Binary files /dev/null and b/src/site/resources/images/osxDirectoryUtility8.png differ
diff --git a/src/site/resources/images/osxDirectoryUtility9.png b/src/site/resources/images/osxDirectoryUtility9.png
new file mode 100644
index 0000000..737e376
Binary files /dev/null and b/src/site/resources/images/osxDirectoryUtility9.png differ
diff --git a/src/site/resources/images/osxIcal1.png b/src/site/resources/images/osxIcal1.png
new file mode 100644
index 0000000..91b06c8
Binary files /dev/null and b/src/site/resources/images/osxIcal1.png differ
diff --git a/src/site/resources/images/osxIcal2.png b/src/site/resources/images/osxIcal2.png
new file mode 100644
index 0000000..41ecd2c
Binary files /dev/null and b/src/site/resources/images/osxIcal2.png differ
diff --git a/src/site/resources/images/osxIcal3.png b/src/site/resources/images/osxIcal3.png
new file mode 100644
index 0000000..c0eab88
Binary files /dev/null and b/src/site/resources/images/osxIcal3.png differ
diff --git a/src/site/resources/images/osxMailImap1.png b/src/site/resources/images/osxMailImap1.png
new file mode 100644
index 0000000..42e1cc9
Binary files /dev/null and b/src/site/resources/images/osxMailImap1.png differ
diff --git a/src/site/resources/images/osxMailImap10.png b/src/site/resources/images/osxMailImap10.png
new file mode 100644
index 0000000..ffaeec3
Binary files /dev/null and b/src/site/resources/images/osxMailImap10.png differ
diff --git a/src/site/resources/images/osxMailImap11.png b/src/site/resources/images/osxMailImap11.png
new file mode 100644
index 0000000..6b28662
Binary files /dev/null and b/src/site/resources/images/osxMailImap11.png differ
diff --git a/src/site/resources/images/osxMailImap12.png b/src/site/resources/images/osxMailImap12.png
new file mode 100644
index 0000000..5aa7ce7
Binary files /dev/null and b/src/site/resources/images/osxMailImap12.png differ
diff --git a/src/site/resources/images/osxMailImap13.png b/src/site/resources/images/osxMailImap13.png
new file mode 100644
index 0000000..1592a06
Binary files /dev/null and b/src/site/resources/images/osxMailImap13.png differ
diff --git a/src/site/resources/images/osxMailImap2.png b/src/site/resources/images/osxMailImap2.png
new file mode 100644
index 0000000..9c0c00d
Binary files /dev/null and b/src/site/resources/images/osxMailImap2.png differ
diff --git a/src/site/resources/images/osxMailImap3.png b/src/site/resources/images/osxMailImap3.png
new file mode 100644
index 0000000..0d4a504
Binary files /dev/null and b/src/site/resources/images/osxMailImap3.png differ
diff --git a/src/site/resources/images/osxMailImap4.png b/src/site/resources/images/osxMailImap4.png
new file mode 100644
index 0000000..4fd5619
Binary files /dev/null and b/src/site/resources/images/osxMailImap4.png differ
diff --git a/src/site/resources/images/osxMailImap5.png b/src/site/resources/images/osxMailImap5.png
new file mode 100644
index 0000000..54154d8
Binary files /dev/null and b/src/site/resources/images/osxMailImap5.png differ
diff --git a/src/site/resources/images/osxMailImap6.png b/src/site/resources/images/osxMailImap6.png
new file mode 100644
index 0000000..7866f48
Binary files /dev/null and b/src/site/resources/images/osxMailImap6.png differ
diff --git a/src/site/resources/images/osxMailImap7.png b/src/site/resources/images/osxMailImap7.png
new file mode 100644
index 0000000..12c9803
Binary files /dev/null and b/src/site/resources/images/osxMailImap7.png differ
diff --git a/src/site/resources/images/osxMailImap8.png b/src/site/resources/images/osxMailImap8.png
new file mode 100644
index 0000000..bb0fd4c
Binary files /dev/null and b/src/site/resources/images/osxMailImap8.png differ
diff --git a/src/site/resources/images/osxMailImap9.png b/src/site/resources/images/osxMailImap9.png
new file mode 100644
index 0000000..6abe5c9
Binary files /dev/null and b/src/site/resources/images/osxMailImap9.png differ
diff --git a/src/site/resources/images/osxconfirm.png b/src/site/resources/images/osxconfirm.png
new file mode 100644
index 0000000..7686f29
Binary files /dev/null and b/src/site/resources/images/osxconfirm.png differ
diff --git a/src/site/resources/images/osxdesktop.jpg b/src/site/resources/images/osxdesktop.jpg
new file mode 100644
index 0000000..baaad18
Binary files /dev/null and b/src/site/resources/images/osxdesktop.jpg differ
diff --git a/src/site/resources/images/osxdownload.png b/src/site/resources/images/osxdownload.png
new file mode 100644
index 0000000..d1dd969
Binary files /dev/null and b/src/site/resources/images/osxdownload.png differ
diff --git a/src/site/resources/images/osxgrowl.png b/src/site/resources/images/osxgrowl.png
new file mode 100644
index 0000000..9b1b5b3
Binary files /dev/null and b/src/site/resources/images/osxgrowl.png differ
diff --git a/src/site/resources/images/osxsettings.png b/src/site/resources/images/osxsettings.png
new file mode 100644
index 0000000..c3b0c29
Binary files /dev/null and b/src/site/resources/images/osxsettings.png differ
diff --git a/src/site/resources/images/setup1.png b/src/site/resources/images/setup1.png
new file mode 100644
index 0000000..3de60de
Binary files /dev/null and b/src/site/resources/images/setup1.png differ
diff --git a/src/site/resources/images/setup2.png b/src/site/resources/images/setup2.png
new file mode 100644
index 0000000..452ab01
Binary files /dev/null and b/src/site/resources/images/setup2.png differ
diff --git a/src/site/resources/images/setup3.png b/src/site/resources/images/setup3.png
new file mode 100644
index 0000000..ba074d2
Binary files /dev/null and b/src/site/resources/images/setup3.png differ
diff --git a/src/site/resources/images/setup4.png b/src/site/resources/images/setup4.png
new file mode 100644
index 0000000..3bb9d1a
Binary files /dev/null and b/src/site/resources/images/setup4.png differ
diff --git a/src/site/resources/images/setup5.png b/src/site/resources/images/setup5.png
new file mode 100644
index 0000000..2344140
Binary files /dev/null and b/src/site/resources/images/setup5.png differ
diff --git a/src/site/resources/images/thunderbirdAccount1.png b/src/site/resources/images/thunderbirdAccount1.png
new file mode 100644
index 0000000..0fcd42f
Binary files /dev/null and b/src/site/resources/images/thunderbirdAccount1.png differ
diff --git a/src/site/resources/images/thunderbirdAccount2.png b/src/site/resources/images/thunderbirdAccount2.png
new file mode 100644
index 0000000..7bbc947
Binary files /dev/null and b/src/site/resources/images/thunderbirdAccount2.png differ
diff --git a/src/site/resources/images/thunderbirdAccount3.png b/src/site/resources/images/thunderbirdAccount3.png
new file mode 100644
index 0000000..7376b5c
Binary files /dev/null and b/src/site/resources/images/thunderbirdAccount3.png differ
diff --git a/src/site/resources/images/thunderbirdAccount4.png b/src/site/resources/images/thunderbirdAccount4.png
new file mode 100644
index 0000000..7410258
Binary files /dev/null and b/src/site/resources/images/thunderbirdAccount4.png differ
diff --git a/src/site/resources/images/thunderbirdAccountPop3.png b/src/site/resources/images/thunderbirdAccountPop3.png
new file mode 100644
index 0000000..60798b9
Binary files /dev/null and b/src/site/resources/images/thunderbirdAccountPop3.png differ
diff --git a/src/site/resources/images/thunderbirdCalendar1.png b/src/site/resources/images/thunderbirdCalendar1.png
new file mode 100644
index 0000000..c94ada6
Binary files /dev/null and b/src/site/resources/images/thunderbirdCalendar1.png differ
diff --git a/src/site/resources/images/thunderbirdCalendar2.png b/src/site/resources/images/thunderbirdCalendar2.png
new file mode 100644
index 0000000..146242a
Binary files /dev/null and b/src/site/resources/images/thunderbirdCalendar2.png differ
diff --git a/src/site/resources/images/thunderbirdCalendar3.png b/src/site/resources/images/thunderbirdCalendar3.png
new file mode 100644
index 0000000..75968bf
Binary files /dev/null and b/src/site/resources/images/thunderbirdCalendar3.png differ
diff --git a/src/site/resources/images/thunderbirdCalendar4.png b/src/site/resources/images/thunderbirdCalendar4.png
new file mode 100644
index 0000000..2c2b242
Binary files /dev/null and b/src/site/resources/images/thunderbirdCalendar4.png differ
diff --git a/src/site/resources/images/thunderbirdCalendar5.png b/src/site/resources/images/thunderbirdCalendar5.png
new file mode 100644
index 0000000..13be2be
Binary files /dev/null and b/src/site/resources/images/thunderbirdCalendar5.png differ
diff --git a/src/site/resources/images/thunderbirdCarddav1.png b/src/site/resources/images/thunderbirdCarddav1.png
new file mode 100644
index 0000000..60d0f16
Binary files /dev/null and b/src/site/resources/images/thunderbirdCarddav1.png differ
diff --git a/src/site/resources/images/thunderbirdCarddav2.png b/src/site/resources/images/thunderbirdCarddav2.png
new file mode 100644
index 0000000..9aa162f
Binary files /dev/null and b/src/site/resources/images/thunderbirdCarddav2.png differ
diff --git a/src/site/resources/images/thunderbirdCarddav3.png b/src/site/resources/images/thunderbirdCarddav3.png
new file mode 100644
index 0000000..4f11860
Binary files /dev/null and b/src/site/resources/images/thunderbirdCarddav3.png differ
diff --git a/src/site/resources/images/thunderbirdDirectory1.png b/src/site/resources/images/thunderbirdDirectory1.png
new file mode 100644
index 0000000..0b41d8a
Binary files /dev/null and b/src/site/resources/images/thunderbirdDirectory1.png differ
diff --git a/src/site/resources/images/thunderbirdDirectory2.png b/src/site/resources/images/thunderbirdDirectory2.png
new file mode 100644
index 0000000..6ff3519
Binary files /dev/null and b/src/site/resources/images/thunderbirdDirectory2.png differ
diff --git a/src/site/resources/images/thunderbirdSmtp.png b/src/site/resources/images/thunderbirdSmtp.png
new file mode 100644
index 0000000..76335fa
Binary files /dev/null and b/src/site/resources/images/thunderbirdSmtp.png differ
diff --git a/src/site/resources/images/ubuntutray.png b/src/site/resources/images/ubuntutray.png
new file mode 100644
index 0000000..0d01199
Binary files /dev/null and b/src/site/resources/images/ubuntutray.png differ
diff --git a/src/site/resources/images/ubuntutraysettings.png b/src/site/resources/images/ubuntutraysettings.png
new file mode 100644
index 0000000..05160f4
Binary files /dev/null and b/src/site/resources/images/ubuntutraysettings.png differ
diff --git a/src/site/resources/setup.html b/src/site/resources/setup.html
new file mode 100644
index 0000000..46de401
--- /dev/null
+++ b/src/site/resources/setup.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+  <head>
+    <title>Maven - 
+        Setup DavMail</title>
+
+    <meta http-equiv="refresh" content="0; url=windowssetup.html" />
+  <body>
+    Redirecting to <a href="windowssetup.html" />...
+  </body>
+</html>
diff --git a/src/site/site.xml b/src/site/site.xml
new file mode 100644
index 0000000..2d6fc13
--- /dev/null
+++ b/src/site/site.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<project name="Maven" xmlns="http://maven.apache.org/DECORATION/1.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/DECORATION/1.0.0 http://maven.apache.org/xsd/decoration-1.0.0.xsd">
+    <version position="left"/>
+
+    <bannerLeft>
+        <name>DavMail Gateway</name>
+        <src>images/davmailLogo.png</src>
+        <href>http://davmail.sourceforge.net</href>
+    </bannerLeft>
+    <bannerRight>
+        <name>SourceForge</name>
+        <src>http://sflogo.sourceforge.net/sflogo.php?group_id=184600&type=13</src>
+        <href>http://sourceforge.net/projects/davmail</href>
+    </bannerRight>
+    <poweredBy>
+        <logo name="Donate" href="https://sourceforge.net/donate/index.php?group_id=184600"
+              img="https://sourceforge.net/images/project-support.jpg"/>
+    </poweredBy>
+    <skin>
+        <groupId>org.apache.maven.skins</groupId>
+        <artifactId>maven-default-skin</artifactId>
+    </skin>
+    <body>
+        <head>
+            <link href="/favicon.ico" rel="shortcut icon"/>
+        </head>
+        <menu name="Quick Links">
+            <item name="Home" href="/index.html"/>
+            <item name="Download" href="/download.html"/>
+            <item name="Roadmap" href="/roadmap.html"/>
+            <item name="Build" href="/build.html"/>
+            <item name="FAQ" href="/faq.html"/>
+            <item name="SourceForge site" href="http://sourceforge.net/projects/davmail"/>
+            <item name="Reviews" href="/reviews.html"/>
+        </menu>
+
+        <menu name="DavMail Setup">
+            <item name="Windows Setup" href="/windowssetup.html"/>
+            <item name="Linux Setup" href="/linuxsetup.html"/>
+            <item name="Mac OS X" href="/macosxsetup.html"/>
+            <item name="Server Setup" href="/serversetup.html"/>
+            <item name="Getting Started" href="/gettingstarted.html"/>
+            <item name="Advanced Settings" href="/advanced.html"/>
+            <item name="SSL Setup" href="/sslsetup.html"/>
+        </menu>
+
+        <menu name="Thunderbird Setup">
+            <item name="POP Mail setup" href="/thunderbirdmailsetup.html"/>
+            <item name="IMAP Mail setup" href="/thunderbirdimapmailsetup.html"/>
+            <item name="Calendar setup" href="/thunderbirdcalendarsetup.html"/>
+            <item name="Directory setup" href="/thunderbirddirectorysetup.html"/>
+            <item name="Carddav setup" href="/thunderbirdcarddavsetup.html"/>
+        </menu>
+        <menu name="OSX Setup">
+            <item name="iCal setup" href="/osxicalsetup.html"/>
+            <item name="Directory setup" href="/osxdirectorysetup.html"/>
+            <item name="IMAP Mail setup" href="/osximapmailsetup.html"/>
+            <item name="Address book setup" href="/osxaddressbooksetup.html"/>
+        </menu>
+        <menu name="iPhone Setup">
+            <item name="iPhone setup" href="/iphonesetup.html"/>
+            <item name="Mail setup" href="/iphonemailsetup.html"/>
+            <item name="Contact setup" href="/iphonecarddavsetup.html"/>
+            <item name="Calendar setup" href="/iphonecaldavsetup.html"/>
+        </menu>
+        <menu ref="reports"/>
+    </body>
+</project>
diff --git a/src/site/xdoc/advanced.xml b/src/site/xdoc/advanced.xml
new file mode 100644
index 0000000..0e0fb8a
--- /dev/null
+++ b/src/site/xdoc/advanced.xml
@@ -0,0 +1,270 @@
+<?xml version="1.0"?>
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+
+    <properties>
+        <title>DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway - Advanced settings</title>
+        <author email="mguessan at free.fr">Mickael Guessant</author>
+    </properties>
+
+    <body>
+
+        <section name="Advanced settings">
+            <p>In addition to basic settings described in
+                <a href="gettingstarted.html">Getting started</a>
+                DavMail also supports the following settings:
+            </p>
+            <subsection name="Global settings">
+                <table>
+                    <tr>
+                        <th>Parameter</th>
+                        <th>Description</th>
+                        <th>Sample value</th>
+                    </tr>
+                    <tr>
+                        <td>Enable EWS</td>
+                        <td>Enable EWS mode on Exchange 2010 or Exchange 2007 with Webdav disabled</td>
+                        <td>false</td>
+                    </tr>
+                    <tr>
+                        <td>Default domain</td>
+                        <td>Default windows domain name</td>
+                        <td>DOMAIN</td>
+                    </tr>
+                    <tr>
+                        <td>Display startup banner</td>
+                        <td>Whether to show the initial startup notification window or not</td>
+                        <td>true</td>
+                    </tr>
+                    <tr>
+                        <td>Disable balloon notifications</td>
+                        <td>Disable all graphical notifications</td>
+                        <td>false</td>
+                    </tr>
+                    <tr>
+                        <td>Disable update check</td>
+                        <td>Disable DavMail check for new version</td>
+                        <td>false</td>
+                    </tr>
+                </table>
+            </subsection>
+            <subsection name="Network">
+                <table>
+                    <tr>
+                        <th>Parameter</th>
+                        <th>Description</th>
+                        <th>Sample value</th>
+                    </tr>
+                    <tr>
+                        <td>Allow remote connections</td>
+                        <td>Allow remote connections to the gateway (server mode)</td>
+                        <td>false</td>
+                    </tr>
+                    <tr>
+                        <td>Bind Address</td>
+                        <td>Bind only to the specified network address, leave empty to listen on all network
+                            interfaces
+                        </td>
+                        <td>10.0.1.2</td>
+                    </tr>
+                    <tr>
+                        <td>Client connection timeout</td>
+                        <td>Client connection timeout in seconds, 0 to disable timeout, empty for 5 minutes</td>
+                        <td>300</td>
+                    </tr>
+                </table>
+            </subsection>
+            <subsection name="Encryption">
+                <table>
+                    <tr>
+                        <th>Parameter</th>
+                        <th>Description</th>
+                        <th>Sample value</th>
+                    </tr>
+                    <tr>
+                        <td>Server certificate hash</td>
+                        <td>Manually accepted server certificate hash, contains the SHA1 hash of
+                            a manually accepted certificate (invalid or self signed)
+                        </td>
+                        <td>9F:CC:59:82:1F:C:CD:29:7C:70:F0:D8:37:B1:77:3F:48:84:AE:C4</td>
+                    </tr>
+                    <tr>
+                        <td>Key store type</td>
+                        <td>To encrypt communication between client and DavMail, create a server certificate,
+                            choose key store type and set key store path
+                        </td>
+                        <td>JKS</td>
+                    </tr>
+                    <tr>
+                        <td>Key store</td>
+                        <td>SSL certificate key store file path</td>
+                        <td>path/to/keystore</td>
+                    </tr>
+                    <tr>
+                        <td>Key store password</td>
+                        <td>Key store password</td>
+                        <td>password</td>
+                    </tr>
+                    <tr>
+                        <td>Key password</td>
+                        <td>SSL key password inside key store</td>
+                        <td>password</td>
+                    </tr>
+                    <tr>
+                        <td>Client key store type</td>
+                        <td>When the Exchange server requires mutual authentication,
+                            choose client certificate key store type, PKCS11 for smartcard,
+                            PKCS12 or JKS for certificate file
+                        </td>
+                        <td>PKCS11</td>
+                    </tr>
+                    <tr>
+                        <td>Client key store</td>
+                        <td>SSL client certificate key store file path</td>
+                        <td>path/to/keystore</td>
+                    </tr>
+                    <tr>
+                        <td>Client key store password</td>
+                        <td>Client key store password, leave empty for runtime prompt</td>
+                        <td>password</td>
+                    </tr>
+                    <tr>
+                        <td>PKCS11 library</td>
+                        <td>PKCS11 (smartcard) library path (.so or .dll)</td>
+                        <td>softokn3.dll</td>
+                    </tr>
+                    <tr>
+                        <td>PKCS11 config</td>
+                        <td>Optional additional PKCS11 settings (slot, nssArgs, ...)</td>
+                        <td>slot=2</td>
+                    </tr>
+                </table>
+            </subsection>
+            <subsection name="POP">
+                <table>
+                    <tr>
+                        <th>Parameter</th>
+                        <th>Description</th>
+                        <th>Sample value</th>
+                    </tr>
+                    <tr>
+                        <td>Keep Delay (POP)</td>
+                        <td>Number of days to keep messages in Exchange trash folder before actual deletion,
+                            only for POP service
+                        </td>
+                        <td>30</td>
+                    </tr>
+                    <tr>
+                        <td>Sent Keep Delay (POP)</td>
+                        <td>Number of days to keep sent messages in Exchange sent folder,
+                            only for POP service
+                        </td>
+                        <td>90</td>
+                    </tr>
+                    <tr>
+                        <td>POP mark read</td>
+                        <td>Mark messages read on server immediately after retrieval</td>
+                        <td>true</td>
+                    </tr>
+                </table>
+            </subsection>
+            <subsection name="Caldav (Calendar)">
+                <table>
+                    <tr>
+                        <th>Parameter</th>
+                        <th>Description</th>
+                        <th>Sample value</th>
+                    </tr>
+                    <tr>
+                        <td>Calendar past events (Caldav)</td>
+                        <td>Get events in the past not older than specified days count, leave empty for no limits</td>
+                        <td>90</td>
+                    </tr>
+                    <tr>
+                        <td>Edit Caldav notifications</td>
+                        <td>Enable interactive Caldav edit notification window</td>
+                        <td>false</td>
+                    </tr>
+                    <tr>
+                        <td>Force Active Sync update</td>
+                        <td>Use double event update to trigger ActiveSync mobile phones sync, only in WebDav mode</td>
+                        <td>false</td>
+                    </tr>
+                    <tr>
+                        <td>Caldav alarm sound</td>
+                        <td>Convert Caldav alarm to sound alarm supported by iCal, e.g. Basso. Leave empty for no
+                            conversion
+                        </td>
+                        <td>Basso</td>
+                    </tr>
+                </table>
+            </subsection>
+            <subsection name="IMAP">
+                <table>
+                    <tr>
+                        <th>Parameter</th>
+                        <th>Description</th>
+                        <th>Sample value</th>
+                    </tr>
+                    <tr>
+                        <td>IDLE folder monitor delay (IMAP):</td>
+                        <td>IMAP folder idle monitor delay in minutes, leave empty to disable IDLE support</td>
+                        <td>1</td>
+                    </tr>
+                    <tr>
+                        <td>IMAP auto expunge</td>
+                        <td>Delete messages immediately on the server over IMAP, i.e. expunge message on \Deleted flag set</td>
+                        <td>true</td>
+                    </tr>
+                </table>
+            </subsection>
+            <subsection name="SMTP">
+                <table>
+                    <tr>
+                        <th>Parameter</th>
+                        <th>Description</th>
+                        <th>Sample value</th>
+                    </tr>
+                    <tr>
+                        <td>SMTP save in sent:</td>
+                        <td>Save messages sent over SMTP in server Sent folder</td>
+                        <td>true</td>
+                    </tr>
+                </table>
+            </subsection>
+            <subsection name="Logging">
+                <table>
+                    <tr>
+                        <th>Parameter</th>
+                        <th>Description</th>
+                        <th>Sample value</th>
+                    </tr>
+                    <tr>
+                        <td>Logging levels</td>
+                        <td>Default, DavMail and HttpClient logging levels, see Log4J documentation for more details
+                        </td>
+                        <td>WARN</td>
+                    </tr>
+                    <tr>
+                        <td>Log file Path</td>
+                        <td>DavMail log file path (default is davmail.log in working directory on Unix and Windows,
+                            ~/Library/Logs/DavMail/davmail.log on OSX)
+                        </td>
+                        <td>davmail.log</td>
+                    </tr>
+                    <tr>
+                        <td>Log file Size</td>
+                        <td>Maximum log file size, use Log4J syntax, see
+                            <a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/RollingFileAppender.html#setMaxFileSize%28java.lang.String%29">
+                                RollingFileAppender
+                            </a>
+                        </td>
+                        <td>1MB</td>
+                    </tr>
+                </table>
+            </subsection>
+        </section>
+
+    </body>
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/build.xml b/src/site/xdoc/build.xml
new file mode 100644
index 0000000..41547c4
--- /dev/null
+++ b/src/site/xdoc/build.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0"?>
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+
+    <properties>
+        <title>DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway - Frequently asked questions</title>
+        <author email="mguessan at free.fr">Mickael Guessant</author>
+    </properties>
+
+    <body>
+
+        <section name="Building DavMail from source">
+            <p>Building DavMail is quite simple:</p>
+            <ul>
+                <li>Make sure you have Java 6 or 7 installed and set as current version by checking
+                    JAVA_HOME:
+                    <br/>
+                    <ul>
+                        <li>Unix/OSX:
+                            <source>echo $JAVA_HOME</source>
+                        </li>
+                        <li>Windows:
+                            <source>echo %JAVA_HOME%</source>
+                        </li>
+                    </ul>
+                </li>
+                <li>Check if you already have Apache Ant installed :<br/>
+                    <source>ant -version</source><br/>
+                    If you don't, download and unzip ant from&#x20;<a href="http://ant.apache.org/">ant.apache.org</a>,
+                    add ant/bin full path to the PATH environment variable.
+                </li>
+                <li>Get DavMail source package from sourceforge and uncompress it:
+                    <source>tar xvzf davmail-src-*.tgz</source>
+                    <br/>
+                    or checkout trunk:
+                    <source>svn co http://davmail.svn.sourceforge.net/svnroot/davmail/trunk</source>
+                    <br/>
+                    or get and uncompress tarball from
+                    <a href="http://davmail.svn.sourceforge.net/viewvc/davmail/trunk">
+                        http://davmail.svn.sourceforge.net/viewvc/davmail/trunk
+                    </a>
+                </li>
+                <li>Additional steps on Windows:
+                    <ul>
+                        <li>Download and install NSIS from
+                            <a href="http://nsis.sourceforge.net">http://nsis.sourceforge.net</a>
+                        </li>
+                        <li>Make sure NSIS install path (C:\Program Files\NSIS) is available in system path</li>
+                        <li>Then get KillProcess plugin from
+                            <a href="http://www.esanu.name/software/index.php/nsis-kill-process/">
+                                http://www.esanu.name/software/index.php/nsis-kill-process/
+                            </a>
+                            or the nsis directory in DavMail source.
+                        </li>
+                        <li>Copy the processwork.dll library to NSIS plugin directory.</li>
+                    </ul>
+                </li>
+                <li>Go into davmail directory root directory (which contains the build.xml file) and
+                    type:
+                    <source>ant</source>
+                </li>
+            </ul>
+            <p>Packages are then available under dist
+            </p>
+        </section>
+    </body>
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/download.xml b/src/site/xdoc/download.xml
new file mode 100644
index 0000000..a9b6202
--- /dev/null
+++ b/src/site/xdoc/download.xml
@@ -0,0 +1,122 @@
+<?xml version="1.0"?>
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+
+    <properties>
+        <title>DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway - Download</title>
+        <author email="mguessan at free.fr">Mickael Guessant</author>
+    </properties>
+
+    <body>
+
+        <section name="Download latest DavMail Gateway release">
+            <p>The right DavMail package choice depends on your operating system and
+                deployment (server or workstation).
+            </p>
+            <p>Please consider making a donation to help this project at
+                <a href="https://sourceforge.net/donate/index.php?group_id=184600"
+                   title="Donate">
+                    SourceForge Donation page
+                </a>
+            </p>
+
+            <subsection name="Workstation (personal) mode">
+                <ul>
+                    <li>
+                        <strong>Windows:</strong>
+                        Download installation package
+                        <a href="http://sourceforge.net/projects/davmail/files/">davmail-<em>version</em>-setup.exe
+                        </a>
+                        <br/>
+                        and follow instructions at
+                        <a href="windowssetup.html">DavMail Setup on windows</a>
+                    </li>
+                    <li>
+                        <strong>Mac OS X:</strong>
+                        Download
+                        <a href="http://sourceforge.net/projects/davmail/files/">DavMail-MacOSX-<em>version</em>.app.zip
+                        </a>
+                        <br/>
+                        and follow instructions at
+                        <a href="macosxsetup.html">DavMail Setup on Mac OS X</a>
+                    </li>
+                    <li>
+                        <strong>Debian Linux (Ubuntu):</strong>
+                        Download debian package
+                        <a href="http://sourceforge.net/projects/davmail/files/">davmail_<em>version</em>-1_all.deb
+                        </a>
+                        <br/>
+                        and follow instructions at
+                        <a href="linuxsetup.html">DavMail Setup on linux</a>
+                    </li>
+                    <li>
+                        <strong>Linux (other):</strong>
+                        Download linux package
+                        <a href="http://sourceforge.net/projects/davmail/files/">davmail-linux-x86-<em>version</em>.tgz
+                        </a>
+                        , or 64 bits package
+                        <a href="http://sourceforge.net/projects/davmail/files/">davmail-linux-x86_64-
+                            <em>version</em>
+                            .tgz
+                        </a>
+                        <br/>
+                        and follow instructions at
+                        <a href="linuxsetup.html">DavMail Setup on linux</a>
+                        <br/>
+                        Note: the only difference between 32 and 64 bits packages is the included SWT library.
+                    </li>
+                    <li>
+                        <strong>Other:</strong>
+                        Download platform independent package
+                        <a href="http://sourceforge.net/projects/davmail/files/">davmail-<em>version</em>.zip
+                        </a>
+                        <br/>
+                        and follow instructions at
+                        <a href="linuxsetup.html">DavMail Setup on linux</a>
+                        <br/>
+                    </li>
+                </ul>
+            </subsection>
+
+            <subsection name="Server (shared) mode">
+                <ul>
+                    <li>
+                        <strong>Standalone:</strong>
+                        Download platform independent package
+                        <a href="http://sourceforge.net/projects/davmail/files/">davmail-<em>version</em>.zip
+                        </a>
+                        <br/>
+                        and follow instructions at
+                        <a href="serversetup.html">DavMail Setup as a standalone server</a>
+                        <br/>
+                    </li>
+                    <li>
+                        <strong>Webapp:</strong>
+                        If you intend to run DavMail inside an application server, download webapp package
+                        <a href="http://sourceforge.net/projects/davmail/files/">davmail-<em>version</em>.war
+                        </a>
+                        <br/>
+                        and follow instructions at
+                        <a href="serversetup.html">DavMail Setup as a JEE Web Application</a>
+                        (last section)
+                        <br/>
+                    </li>
+                </ul>
+            </subsection>
+
+            <subsection name="Build from source">
+                <p>
+                    Download source tarball
+                    <a href="http://sourceforge.net/projects/davmail/files/">davmail-src-<em>version</em>.tgz
+                    </a>
+                    <br/>
+                    and follow instructions at
+                    <a href="build.html">Building DavMail from source</a>
+                    <br/>
+                </p>
+            </subsection>
+        </section>
+
+    </body>
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/faq.xml b/src/site/xdoc/faq.xml
new file mode 100644
index 0000000..08754e3
--- /dev/null
+++ b/src/site/xdoc/faq.xml
@@ -0,0 +1,318 @@
+<?xml version="1.0"?>
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+
+    <properties>
+        <title>DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway - Frequently asked questions</title>
+        <author email="mguessan at free.fr">Mickael Guessant</author>
+    </properties>
+
+    <body>
+
+        <section name="Frequently asked questions">
+            <p>Frequently asked questions on DavMail forums, mailing list or direct email.
+            </p>
+            <subsection name="Feedback">
+                <p>
+                    <strong>How do I create a WIRE debug log ?</strong>
+                </p>
+                <p>In DavMail trackers and forums, I often ask for a WIRE debug log. To create this
+                    log file, you will need to change DavMail settings under the Logging tab.
+                    Set the WIRE logging category to DEBUG level.
+                </p>
+                <p>You can then reproduce your issue and send the log file to
+                    <a href="mailto:mguessan at free.fr">mguessan at free.fr</a>
+                </p>
+                <p>The davmail.log file is available in DavMail working directory on Unix and Windows,
+                    ~/Library/Logs/DavMail/davmail.log on OSX. This default path can be customized with
+                    the
+                    <code>Log File Path</code>
+                    setting.
+                </p>
+                <p>Note: on OSX Lion the Library folder is hidden, a simple way to access it is to launch
+                Console (Finder, Go to Utilities), right click on davmail.log and choose Reveal in Finder.</p>
+            </subsection>
+            <subsection name="General">
+                <p>
+                    <strong>EWS endpoint not available</strong>
+                </p>
+                <p>Unfortunately, this probably means your Exchange administrators blocked EWS access.</p>
+                <p>To check this, try to connect to
+                    <code>https://<i>mail.company.com</i>/ews/exchange.asmx
+                    </code>
+                    in your favorite web browser: you should get an authentication popup. On authentication success,
+                    you should get the EWS wsdl definition. If you don't, please contact the Exchange administrators
+                    to let them fix the broken Exchange server setup.
+                </p>
+                <p>Note: Microsoft Outlook 2011 on OSX and native OSX applications also rely on EWS to connect to
+                    Exchange. Mobile devices (Android phones, iPhones, Windows mobile) use ActiveSync on a different
+                    endpoint.
+                </p>
+                <p>
+                    <strong>Authentication fails with invalid user or password message</strong>
+                </p>
+                <p>Authentication fails with the following message:
+                    <code>Authentication failed: invalid user or password, retry with domain\user</code>
+                </p>
+                <p>Exchange before 2007 expects domain qualified user name, you may not have
+                    to provide the domain name on the logon form if javascript is used to add it,
+                    but DavMail can not execute javascript. Thus you need to prefix your user name
+                    with the Active Directory domain followed by \
+                </p>
+                <p>Note to Mac users: OSX applications do not like username with backslash, you have to set windows
+                    domain name in DavMail advanced settings and use the simple username in client application.
+                </p>
+
+                <p>
+                    <strong>Authentication fails with error in parsing the status line</strong>
+                </p>
+                <p>Authentication fails with the following message:
+                    <code>error in parsing the status line from the response: unable to find line starting with "HTTP"
+                    </code>
+                </p>
+                <p>Your Exchange server expects HTTPS (secured) connections, use https instead of http in
+                    OWA url.
+                </p>
+                <p>
+                    <strong>Where can I find DavMail settings file ?</strong>
+                </p>
+                <p>The default location for DavMail settings is a file named .davmail.properties in user home
+                    folder. This file is hidden under Linux and OSX. On windows, the user home folder is under
+                    <code>Document and Settings</code>
+                </p>
+                <p>
+                    <strong>How to run multiple instances of DavMail ?</strong>
+                </p>
+                <p>Just create multiple configuration files and provide them as a command line option, see previous
+                    question:
+                </p>
+                <source>
+davmail server1.properties
+davmail server2.properties
+                </source>
+
+            </subsection>
+
+            <subsection name="Exchange setup">
+                <p>
+                    <strong>What are the prerequisites on Exchange server side ?</strong>
+                </p>
+                <p>With Exchange 2003, you only need to have access to OWA (Outlook Web Access), Webdav, Address Book
+                    and public folders are always available. Unfortunately, this is not the case with Exchange 2007.
+                    You must make sure Webdav support is enabled on your server. You can test this by accessing
+                    https://mail.company.com/exchange, which should redirect to /owa. Another important service is the
+                    galfind (address book) access at /public/?Cmd=galfind
+                </p>
+                <p>Additional information for Exchange administrators to enable WebDav:</p>
+                <p>After running through the pre-requisite checker for each server (Front End CAS server and
+                    Back End Mailbox server) and adding the appropriate roles and features, add the following
+                    via the ServerManager:
+                </p>
+                <ul>
+                    <li>Select the IIS role, then add Role Services</li>
+                    <li>Add WebDav Publishing (both Cas server and Mailbox Server)</li>
+                    <li>On the Mailbox server, add the ISAPI extensions Role Service to the IIS role</li>
+                    <li>On the Default Web Site (each server), enable WebDav</li>
+                    <li>Should not have to enable any additional WebDav rules</li>
+                    <li>Install the server roles</li>
+                    <li>Create a test mailbox (see Exchange Docs)</li>
+                </ul>
+                <p>To Test:</p>
+                <ul>
+                    <li>Ensure that Outlook Web Access works by using the https://<i>exchangeServer</i>/owa URL
+                    </li>
+                    <li>Try using the pass through legacy URL: https://<i>exchangeServer</i>/exchange,
+                        if you receive an error 500 “Internal Server Error”, then WebDav is not working.
+                    </li>
+                </ul>
+                <p>
+                    <strong>Is Exchange 2010 supported ?</strong>
+                </p>
+                <p>As Microsoft decided to drop WebDav support in Exchange 2010, I had to implement a new backend from
+                    scratch to support the new Exchange Web Services interface. This support is still experimental,
+                    but is working quite well. However, you still need to enable it manually in .davmail.properties
+                    with the following line:
+                    <br/>
+                    <source>davmail.enableEws=true</source>
+                    <br/>
+                </p>
+                <p>
+                    <strong>Is DavMail based on Outlook protocol ?</strong>
+                </p>
+                <p>Outlook anywhere uses MAPI RPC over HTTPS to access company LAN through the firewall.
+                    DavMail does not use MAPI at all but relies on WebDav (Exchange 2003/2007) or EWS (Exchange
+                    2007/2010) to access Exchange. This means you need direct access over HTTPS to either the OWA WebDav
+                    url (/exchange/mail at company.com) or EWS (/ews/exchange.asmx) to access Exchange with DavMail.
+                </p>
+            </subsection>
+
+            <subsection name="OSX">
+                <p>
+                    <strong>How do I make DavMail start automatically ?</strong>
+                </p>
+                <p>Add DavMail.app to "Login Items", see
+                    <a href="http://docs.info.apple.com/article.html?path=Mac/10.6/en/15189.html">
+                        Opening items automatically when you log in
+                    </a>
+                </p>
+            </subsection>
+
+            <subsection name="Linux">
+                <p>
+                    <strong>Why do I get black on black notifications on Ubuntu ?</strong>
+                </p>
+                <p>This is an SWT bug, upgrade to SWT 3.6, see
+                    <a href="https://sourceforge.net/tracker/?func=detail&atid=909904&aid=3138023&group_id=184600">
+                        On Ubuntu, notify text conflicts with default theme.
+                    </a>
+                    http://users.ox.ac.uk/~richardc/guides/LinuxNexus.html
+                </p>
+                <p>
+                    <strong>DavMail hangs on 64-bit Linux</strong>
+                </p>
+                <p>Disable use system proxies option in DavMail settings
+                </p>
+            </subsection>
+
+
+            <subsection name="Security">
+                <p>
+                    <strong>How do I secure DavMail connections ?</strong>
+                </p>
+                <p>Communication between DavMail and your Exchange server is secured by default as long as you access
+                    OWA over HTTPS.
+                </p>
+                <p>Communication between the messaging client and DavMail is
+                    <em>not</em>
+                    secured by default. This is not an issue in standalone mode as all communication is local, but you
+                    may want to improve this in server (shared) mode.
+                </p>
+                <p>This is quite simple: you just need to get a server certificate in PKCS12 or JKS format and add it in
+                    the key store section in DavMail settings, see&#x20;<a href="sslsetup.html">SSL Setup</a>.
+                </p>
+
+                <p>
+                    <strong>Are my credentials safe ?</strong>
+                </p>
+                <p>DavMail does not store Exchange username or password, they are provided by the messaging client over
+                    IMAP, HTTP, POP, SMTP or LDAP
+                </p>
+            </subsection>
+
+            <subsection name="Caldav (calendar)">
+                <p>
+                    <strong>How do I detect errors in Thunderbird/Lightning calendar ?</strong>
+                </p>
+                <p>Unfortunately, Lightning does not provide much feedback of what is going
+                    on. To detect errors at this level, you may activate calendar log settings
+                    under Tools/Options/Advanced:
+                </p>
+                <source><![CDATA[calendar.debug.log=true
+calendar.debug.log.verbose=true]]></source>
+
+                <p>
+                    <strong>Invalid notifications</strong>
+                </p>
+                <p>Lightning 0.9 mail notifications are broken, activate
+                    Caldav notifications to outbox instead:
+                </p>
+                <p>Check the following parameter in Thunderbird, it should be set to true
+                </p>
+                <source>calendar.caldav.sched.enabled=true</source>
+
+                <p>
+                    <strong>Public calendars</strong>
+                </p>
+                <p>To access public folders in Lightning, the URL scheme is:
+                </p>
+                <source>http://localhost:<i>1080</i>/public/<i>path/to/calendar</i></source>
+                <p>To access public folders in iCal, the URL scheme is:
+                </p>
+                <source>http://localhost:<i>1080</i>/principals/public/<i>path/to/calendar</i></source>
+                <p>Note: iCal does not support folder names with spaces or special characters</p>
+
+                <p>
+                    <strong>Shared calendars</strong>
+                </p>
+                <p>To access a shared calendar in Lightning, the URL scheme is:
+                </p>
+                <source>http://localhost:<i>1080</i>/users/<i>user at company.com</i></source>
+                <p>To access a shared calendar in iCal, the URL scheme is:
+                </p>
+                <source>http://localhost:<i>1080</i>/principals/users/<i>user at company.com</i></source>
+
+            </subsection>
+
+            <subsection name="Mail">
+                <p>
+                    <strong>Timeout errors during IMAP sync (broken pipe)</strong>
+                </p>
+                <p>If you Exchange server is too slow, you may get timeout errors during
+                    message fetch.
+                </p>
+                <p>To fix the problem in OSX Mail, go to your account setup under preferences.
+                    On the IMAP account having difficulty, go to the advanced tab. Where it says
+                    "keep copies for offline viewing", change it to "only messages I've read" or
+                    don't keep copies at all.
+                </p>
+                <p>Under Thunderbird, you can try to change IMAP timeout values
+                    (
+                    <code>mail.server.server<em>index</em>.timeout
+                    </code>
+                    and&#x20;<code>mailnews.tcptimeout</code>) or avoid full
+                    mailbox sync: disable global search and indexer under advanced options and change
+                    Synchronization & Storage options under Account Settings.
+                </p>
+                <p>
+                    <strong>Message deleted over IMAP still visible through OWA</strong>
+                </p>
+                <p>Message move does not exist in IMAP, thus the move is a copy followed by a delete.
+                    And a delete is not immediate with IMAP: you need to EXPUNGE the folder to actually
+                    delete a message.
+                </p>
+                <p>A workaround with Thunderbird is to set the property
+                    <code>mail.imap.expunge_after_delete=true</code>
+                </p>
+                <p>New: a new setting is available in DavMail to delete messages immediately over IMAP:
+                    <code>davmail.imapAutoExpunge=true</code>
+                </p>
+                <p>
+                    <strong>Duplicate messages in Sent folder</strong>
+                </p>
+                <p>Messages sent through DavMail appear twice in Sent folder when the IMAP client is setup
+                    to store sent messages on the server as Exchange already saves sent messages in this folder.
+                    Change IMAP client settings or change Save in Sent option in DavMail settings to avoid this issue.
+                </p>
+                <p>
+                    <strong>Sent messages encoding issue with Thunderbird</strong>
+                </p>
+                <p>If special characters appear replaced with ? in sent messages, set
+                    <code>mail.strictly_mime</code>
+                    advanced option.
+                    Go to Tools/Options/Advanced/General/Config Editor and look for a preference
+                    with name<code>mail.strictly_mime</code>, set value to
+                    <code>true</code>
+                    to send messages as quoted-printable.
+                </p>
+                <p>
+                    <strong>Access another user mailbox</strong>
+                </p>
+                <p>Either set base IMAP path in client settings or use the following username:
+                    <code>domain\user\otheruser at company.com</code> where otheruser at company.com it target mailbox
+                    name.
+                </p>
+
+            </subsection>
+
+            <subsection name="Build">
+                <p>
+                    <strong>How to build DavMail ?</strong>
+                </p>
+                <p>See
+                    <a href="build.html">Building DavMail from source</a>
+                </p>
+            </subsection>
+        </section>
+    </body>
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/gettingstarted.xml b/src/site/xdoc/gettingstarted.xml
new file mode 100644
index 0000000..23024a2
--- /dev/null
+++ b/src/site/xdoc/gettingstarted.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0"?>
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+
+    <properties>
+        <title>DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway - Getting started</title>
+        <author email="mguessan at free.fr">Mickael Guessant</author>
+    </properties>
+
+    <body>
+
+        <section name="DavMail configuration">
+            <p>In order to change DavMail settings, double click on the tray icon or
+                right click on it and choose
+                <code>Settings...</code>
+            </p>
+            <div style="width: 100%;text-align: center">
+                <img src="images/davmailSettings.png" alt=""/>
+            </div>
+            <p>The following table describes the main options :</p>
+            <table>
+                <tr>
+                    <th>Parameter</th>
+                    <th>Description</th>
+                    <th>Sample value</th>
+                </tr>
+                <tr>
+                    <td>OWA url</td>
+                    <td>Outlook Web Access URL to access the exchange server, i.e. the base webmail URL
+                        <br/>
+                        The path depends on Exchange version and configuration, with or without a reverse proxy.
+                        DavMail must be able to find the authentication form at the provided URL.<br/>
+                        Usual paths for different Exchange versions:<br/>
+                        <ul>
+                          <li>Exchange 2003: <code>https://<i>mail.company.com</i>/exchange/</code></li>
+                          <li>Exchange 2007 Webdav mode: <code>https://<i>mail.company.com</i>/owa/</code></li>
+                          <li>Exchange 2007 EWS mode: <code>https://<i>mail.company.com</i>/owa/</code></li>
+                          <li>Exchange 2010 EWS mode: <code>https://<i>mail.company.com</i>/owa/</code></li>
+                          <li>Exchange 2010 EWS mode with unsupported authentication form e.g. Windows Live login:
+                              <code>https://<i>mail.company.com</i>/ews/exchange.asmx</code></li>
+                        </ul>
+                    </td>
+                    <td>https://exchangeServer/owa/</td>
+                </tr>
+                <tr>
+                    <td>Local POP port</td>
+                    <td>Local POP server port to configure in POP client</td>
+                    <td>110</td>
+                </tr>
+                <tr>
+                    <td>Local IMAP port</td>
+                    <td>Local IMAP server port to configure in IMAP client</td>
+                    <td>143</td>
+                </tr>
+                <tr>
+                    <td>Local SMTP port</td>
+                    <td>Local SMTP server port to configure in SMTP client</td>
+                    <td>25</td>
+                </tr>
+                <tr>
+                    <td>Local Caldav/Carddav HTTP port</td>
+                    <td>Local Caldav/Carddav server port to configure in Caldav (calendar)
+                        or Carddav (address book) client</td>
+                    <td>80</td>
+                </tr>
+                <tr>
+                    <td>Local LDAP port</td>
+                    <td>Local LDAP server port to configure in directory (address book) client</td>
+                    <td>389</td>
+                </tr>
+            </table>
+            <p>Uncheck a port to disable matching service.</p>
+            <p>Activate panel under Proxy tab to set an HTTP proxy and associated credentials if needed</p>
+            <p>If you need to adjust other advanced settings, check <a href="advanced.html">Advanced settings</a></p>
+            <p>Proceed to
+                <a href="thunderbirdmailsetup.html">Thunderbird mail setup</a>
+                or
+                <a href="osxicalsetup.html">OSX iCal setup</a>
+            </p>
+        </section>
+
+    </body>
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/index.xml b/src/site/xdoc/index.xml
new file mode 100644
index 0000000..b07f980
--- /dev/null
+++ b/src/site/xdoc/index.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0"?>
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+
+    <properties>
+        <title>DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway</title>
+        <author email="mguessan at free.fr">Mickael Guessant</author>
+    </properties>
+
+    <body>
+        <section name="DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway">
+            <table class="about" border="0" style="width: 98%">
+                <tr>
+                    <td>
+                        <p>Ever wanted to get rid of Outlook ? DavMail is a POP/IMAP/SMTP/Caldav/Carddav/LDAP exchange
+                            gateway allowing users to use any mail/calendar client (e.g. Thunderbird with Lightning or
+                            Apple iCal) with an Exchange server, even from the internet or behind a firewall through
+                            Outlook Web Access.
+                            DavMail now includes an LDAP gateway to Exchange global address book and user personal
+                            contacts to allow recipient address completion in mail compose window and full calendar
+                            support with attendees free/busy display.
+                        </p>
+                        <p>DavMail also supports the CardDav protocol to sync address books. This new feature
+                            is sponsored by
+                            <a href="http://www.defense.gouv.fr/dga/">French Defense / DGA</a>
+                            through project
+                            <a href="http://www.trustedbird.org">Trustedbird</a>
+                        </p>
+                        <div style="width: 100%;text-align: center">
+                            <img src="images/davmailArchitecture.png" alt="DavMail Architecture" width="550"
+                                 height="364"/>
+                        </div>
+                        <p>The main goal of DavMail is to provide standard compliant protocols in front of proprietary
+                            Exchange. This means LDAP for global address book, SMTP to send messages, IMAP to browse
+                            messages on the server in any folder, POP to retrieve inbox messages only, Caldav for
+                            calendar support and Carddav for personal contacts sync.
+                            Thus any standard compliant client can be used with Microsoft Exchange.
+                        </p>
+                        <p>DavMail gateway is implemented in java and should run on any platform. Releases are tested
+                            on Windows, Linux (Ubuntu) and Mac OSX. Tested successfully with the Iphone
+                            (gateway running on a server).
+                        </p>
+                    </td>
+                    <td>
+                        <div style="text-align: center">
+                            <script type="text/javascript"
+                                    src="http://www.ohloh.net/p/106265/widgets/project_thin_badge.js">
+                            </script>
+                        </div>
+                        <a class="donate" href="https://sourceforge.net/donate/index.php?group_id=184600"
+                           title="Donate">
+                            Donate
+                        </a>
+                        <a class="download" href="download.html">
+                            Download DavMail Gateway
+                        </a>
+                    </td>
+                </tr>
+            </table>
+        </section>
+    </body>
+</document>
+        
\ No newline at end of file
diff --git a/src/site/xdoc/iphonecaldavsetup.xml b/src/site/xdoc/iphonecaldavsetup.xml
new file mode 100644
index 0000000..ddcd578
--- /dev/null
+++ b/src/site/xdoc/iphonecaldavsetup.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0"?>
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+
+    <properties>
+        <title>DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway - iPhone Caldav setup</title>
+        <author email="mguessan at free.fr">Mickael Guessant</author>
+    </properties>
+
+    <body>
+
+        <section name="iPhone Caldav setup">
+            <p>Note: you can't install DavMail directly on an iPhone, you need a separate DavMail server,
+                see&#x20;<a href="iphonesetup.html">iPhone setup</a>.
+            </p>
+
+            <subsection name="Create a new account">
+                <p>Open iPhone Settings application and choose Mail, Contacts, Calendar:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneAccount1.png" alt=""/>
+                </div>
+
+                <p>Then select Add Account...:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneAccount2.png" alt=""/>
+                </div>
+
+                <p>DavMail is not an Exchange ActiveSync server, press Other:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneAccount3.png" alt=""/>
+                </div>
+
+                <p>Choose Add CalDAV Account:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneCaldav1.png" alt=""/>
+                </div>
+
+                <p>Enter your DavMail server hostname in the Server field,
+                    append ':' and port if DavMail HTTP (Caldav and Carddav) port is not 80,
+                    provide your username and password then press Next:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneCaldav2.png" alt=""/>
+                </div>
+
+                <p>On the account page, open Advanced Settings:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneCaldav3.png" alt=""/>
+                </div>
+
+                <p>Enable SSL and check port, make sure Account URL is
+                    <em>https://davmailhost:1080/principals/users/username@company.com</em>:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneCaldav4.png" alt=""/>
+                </div>
+
+            </subsection>
+        </section>
+    </body>
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/iphonecarddavsetup.xml b/src/site/xdoc/iphonecarddavsetup.xml
new file mode 100644
index 0000000..3d7ed53
--- /dev/null
+++ b/src/site/xdoc/iphonecarddavsetup.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0"?>
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+
+    <properties>
+        <title>DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway - iPhone Carddav setup</title>
+        <author email="mguessan at free.fr">Mickael Guessant</author>
+    </properties>
+
+    <body>
+
+        <section name="iPhone Carddav setup">
+            <p>Note: you can't install DavMail directly on an iPhone, you need a separate DavMail server,
+                see&#x20;<a href="iphonesetup.html">iPhone setup</a>.
+            </p>
+
+            <subsection name="Create a new account">
+                <p>Open iPhone Settings application and choose Mail, Contacts, Calendar:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneAccount1.png" alt=""/>
+                </div>
+
+                <p>Then select Add Account...:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneAccount2.png" alt=""/>
+                </div>
+
+                <p>DavMail is not an Exchange ActiveSync server, press Other:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneAccount3.png" alt=""/>
+                </div>
+
+                <p>Choose Add CardDAV Account:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneCarddav1.png" alt=""/>
+                </div>
+
+                <p>Enter your DavMail server hostname in the Server field,
+                    append ':' and port if DavMail HTTP (Caldav and Carddav) port is not 80,
+                    provide your username and password then press Next:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneCarddav2.png" alt=""/>
+                </div>
+
+                <p>On the account page, open Advanced Settings:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneCarddav3.png" alt=""/>
+                </div>
+
+                <p>Enable SSL and check port:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneCarddav4.png" alt=""/>
+                </div>
+
+                <p>Contact setup is complete, proceed to
+                    <a href="iphonecaldavsetup.html">iPhone Caldav setup</a>
+                </p>
+            </subsection>
+        </section>
+    </body>
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/iphonemailsetup.xml b/src/site/xdoc/iphonemailsetup.xml
new file mode 100644
index 0000000..10145b7
--- /dev/null
+++ b/src/site/xdoc/iphonemailsetup.xml
@@ -0,0 +1,145 @@
+<?xml version="1.0"?>
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+
+    <properties>
+        <title>DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway - iPhone Mail setup</title>
+        <author email="mguessan at free.fr">Mickael Guessant</author>
+    </properties>
+
+    <body>
+
+        <section name="iPhone Mail setup">
+            <p>Note: you can't install DavMail directly on an iPhone, you need a separate DavMail server,
+                see&#x20;<a href="iphonesetup.html">iPhone setup</a>.
+            </p>
+
+            <subsection name="Create a new account">
+                <p>Open iPhone Settings application and choose Mail, Contacts, Calendar:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneAccount1.png" alt=""/>
+                </div>
+
+                <p>Then select Add Account...:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneAccount2.png" alt=""/>
+                </div>
+
+                <p>DavMail is not an Exchange ActiveSync server, press Other:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneAccount3.png" alt=""/>
+                </div>
+
+                <p>Choose Add Mail Account:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneMail01.png" alt=""/>
+                </div>
+
+                <p>Enter your name, email address and password, then press Next:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneMail02.png" alt=""/>
+                </div>
+
+                <p>Select IMAP account type:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneMail03.png" alt=""/>
+                </div>
+
+                <p>In Incoming Mail Server section, enter your DavMail server hostname,
+                    append ':' and port if DavMail IMAP port is not 143:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneMail04.png" alt=""/>
+                </div>
+
+                <p>In Outgoing Mail Server section, enter your DavMail server hostname,
+                    <em>do not append port, this is not supported by current iOS version</em>, then press Next:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneMail05.png" alt=""/>
+                </div>
+
+                <p>Wait until your iPhone has finished verifying all possible port/protocol combination:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneMail06.png" alt=""/>
+                </div>
+
+                <p>If you get the
+                    <em>Cannot Connect Using SSL</em>
+                    message, just select Yes,
+                    you will just need to adjust account settings later.
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneMail07.png" alt=""/>
+                </div>
+
+                <p>Save the account:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneMail08.png" alt=""/>
+                </div>
+
+                <p>Confirm:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneMail09.png" alt=""/>
+                </div>
+
+                <p>Back to account settings page, still need to fix SMTP port
+                    if DavMail is not listening on default port 25,
+                    open account settings:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneMail10.png" alt=""/>
+                </div>
+
+                <p>Open Account Info:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneMail11.png" alt=""/>
+                </div>
+                <p>Open SMTP page:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneMail12.png" alt=""/>
+                </div>
+                <p>Choose primary server:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneMail13.png" alt=""/>
+                </div>
+                <p>Adjust SMTP port:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneMail14.png" alt=""/>
+                </div>
+                <p>Back to Account Info, open Advanced settings:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneMail15.png" alt=""/>
+                </div>
+                <p>Back to Account Info, open Advanced settings:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneMail15.png" alt=""/>
+                </div>
+                <p>Enable SSL on IMAP port:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/iphone/iphoneMail16.png" alt=""/>
+                </div>
+                <p>Mail setup is complete, proceed to
+                    <a href="iphonecarddavsetup.html">iPhone Carddav setup</a>
+                </p>
+            </subsection>
+        </section>
+    </body>
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/iphonesetup.xml b/src/site/xdoc/iphonesetup.xml
new file mode 100644
index 0000000..e3d9578
--- /dev/null
+++ b/src/site/xdoc/iphonesetup.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+
+    <properties>
+        <title>DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway - iPhone setup</title>
+        <author email="mguessan at free.fr">Mickael Guessant</author>
+    </properties>
+
+    <body>
+
+        <section name="iPhone setup">
+            <p>In order to use DavMail with an iPhone, you need to install DavMail in server mode.
+                This server should be available directly on the internet or through a Wifi connection.
+                See
+                <a href="serversetup.html">Server setup</a>
+                for more information.
+            </p>
+
+            <p>As mail data between your iPhone and DavMail travels on the internet, you probably want
+                to switch DavMail to encrypted mode, see
+                <a href="sslsetup.html">SSL setup</a>
+            </p>
+
+            <p>Once you have a working DavMail server, proceed to:
+                <a href="iphonemailsetup.html">iPhone Mail setup</a>
+            </p>
+            <p>Then sync your Exchange contacts:
+                <a href="iphonecarddavsetup.html">iPhone Carddav setup</a>
+            </p>
+            <p>And add your calendar:
+                <a href="iphonecaldavsetup.html">iPhone Caldav setup</a>
+            </p>
+        </section>
+
+    </body>
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/linuxsetup.xml b/src/site/xdoc/linuxsetup.xml
new file mode 100644
index 0000000..f69e643
--- /dev/null
+++ b/src/site/xdoc/linuxsetup.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0"?>
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+
+    <properties>
+        <title>DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway - Linux setup</title>
+        <author email="mguessan at free.fr">Mickael Guessant</author>
+    </properties>
+
+    <body>
+
+        <section name="DavMail Setup on linux">
+            <subsection name="Debian package">
+                <p>Download current debian package from Sourceforge, double click on it
+                    to launch package installer.
+                </p>
+                <p>
+                    Select Install Package, installer will automatically download and
+                    install Java and the appropriate SWT package. After installation,
+                    DavMail is available in the Applications menu.
+                </p>
+                <p>Note to Ubuntu 12 Natty users: they eventually fixed Oneiric issue, use gsettings command below</p>
+                <p>Note to Ubuntu 11.10 Oneiric users: Unity does not support system tray defined by FreeDesktop, you will need
+                to switch to standard Gnome Panel to use DavMail (and any other application using system tray)</p>
+                <p>Note to Ubuntu 10.04 users: all tray icons are hidden by default, use the following command to restore normal mode:</p>
+                <code>gsettings set com.canonical.Unity.Panel systray-whitelist "['all']"</code>
+            </subsection>
+            <subsection name="Manual setup">
+                <p>Prerequisite: OpenJDK 6 or 7 or Sun JRE 6. Tray icon is now implemented with SWT and compatible with
+                    Java 5.
+                </p>
+                <p>Note: some users reported issues with OpenJDK 6, please upgrade to OpenJDK 7 in this case.</p>
+
+                <p>You should first download and install Java, with the graphical package manager or through
+                    command line.
+                </p>
+                <p>Under Ubuntu, launch System/Administration/Synaptic Package Manager, quick search
+                    default-jre, mark for installation and click Apply
+                </p>
+                <p>Or use the following command:</p>
+                <code>sudo apt-get install default-jre</code>
+
+                <p>Download the linux x86 DavMail package from Sourceforge and uncompress it with
+                    your favorite tool. The standard package will run natively on x86, to use DavMail
+                    on any other hardware platform, replace the SWT with the right one from
+                    <a href="http://www.eclipse.org/swt/">http://www.eclipse.org/swt/</a>
+                    or use the platform independent package.
+                </p>
+                <p>On Ubuntu and other Gnome or Kde distributions, just use the desktop launcher.
+                    On other distributions, try
+                    <code>davmail.sh</code>.
+                    You should now see the DavMail gateway icon in the tray :
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img align="middle" src="images/ubuntutray.png" alt=""/>
+                </div>
+                <p>Adjust DavMail settings :
+                    <a href="gettingstarted.html">Getting started</a>
+                </p>
+            </subsection>
+        </section>
+
+    </body>
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/macosxsetup.xml b/src/site/xdoc/macosxsetup.xml
new file mode 100644
index 0000000..20c294b
--- /dev/null
+++ b/src/site/xdoc/macosxsetup.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+
+    <properties>
+        <title>DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway - Mac OS X setup</title>
+        <author email="mguessan at free.fr">Mickael Guessant</author>
+    </properties>
+
+    <body>
+
+        <section name="DavMail Setup on Mac OS X">
+            <p>Older OSX releases include Java 5, this version does not support Tray icon,
+                and DavMail uses a dedicated frame instead. An upgrade to Java 1.6 will enable tray icon.
+                Recent OSX versions either include Java 6 or can install it automatically on first Java application
+                launch.
+            </p>
+
+            <p>Download DavMail Mac OSX package from Sourceforge:
+            </p>
+            <div style="width: 100%;text-align: center">
+                <img src="images/osxdownload.png" alt="OSX Download"/>
+            </div>
+            <p>Safari should automatically extract the application archive. If not, just
+                double click on the zip file to trigger decompression. The extracted
+                DavMail.app directory is then recognised as a valid OSX application.
+            </p>
+            <p>Just double click on the application icon to launch DavMail. On
+                first start the application displays the Settings (Preferences) frame.
+            </p>
+            <div style="width: 100%;text-align: center">
+                <img src="images/osxdesktop.jpg" alt="OSX Desktop"/>
+            </div>
+            <p>In order to get balloon notifications, you will need to install Growl 1.2.2 from
+            <a href="http://growl.info/">http://growl.info/</a>:</p>
+            <div style="width: 100%;text-align: center">
+                <img src="images/osxgrowl.png" alt="OSX Growl"/>
+            </div>
+            <p>Adjust DavMail settings:
+                <a href="gettingstarted.html">Getting started</a>
+            </p>
+        </section>
+
+    </body>
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/osxaddressbooksetup.xml b/src/site/xdoc/osxaddressbooksetup.xml
new file mode 100644
index 0000000..eb0c899
--- /dev/null
+++ b/src/site/xdoc/osxaddressbooksetup.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+
+    <properties>
+        <title>DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway - OSX Address Book setup</title>
+        <author email="mguessan at free.fr">Mickael Guessant</author>
+    </properties>
+
+    <body>
+
+        <section name="OSX Address Book setup">
+            <p>Launch OSX Address Book and create a new Carddav directory.
+            </p>
+
+            <p>Enter your credentials and
+                <code>localhost:1080</code>
+                as server location (Snow Leopard) or <code>http://localhost:1080</code> (OSX Lion)
+                to sync with the default personal address book.
+            </p>
+            <p>
+                <em>Note: uncheck SSL mode if DavMail is in cleartext (default) mode.</em>
+            </p>
+
+        </section>
+    </body>
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/osxdirectorysetup.xml b/src/site/xdoc/osxdirectorysetup.xml
new file mode 100644
index 0000000..4a530c6
--- /dev/null
+++ b/src/site/xdoc/osxdirectorysetup.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0"?>
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+
+    <properties>
+        <title>DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway - OSX directory setup</title>
+        <author email="mguessan at free.fr">Mickael Guessant</author>
+    </properties>
+
+    <body>
+
+        <section name="OSX directory setup">
+            <p>DavMail Directory support is now available to access Exchange address book through LDAP.
+                A special OpenDirectory naming context was implemented to enable iCal attendee completion.
+            </p>
+
+            <subsection name="Open Directory Utility">
+                <p>In OSX Finder, go to Utilities and launch Directory Utility. Under OSX 10.6
+                    Snow Leopard, Directory Utility is tucked away in /System/Library/CoreServices.
+                </p>
+                <p>Leopard only: choose Show Advanced Settings, switch to Services tab</p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/osxDirectoryUtility1.png" alt=""/>
+                </div>
+
+                <p>Select LDAPv3 and click the pencil icon (you may need to unlock settings with your
+                    admin password first):
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/osxDirectoryUtility2.png" alt=""/>
+                </div>
+
+                <p>Click New...:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/osxDirectoryUtility3.png" alt=""/>
+                </div>
+
+                <p>Enter DavMail server name (localhost) and click Manual:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/osxDirectoryUtility4.png" alt=""/>
+                </div>
+
+                <p>Enter Configuration Name and choose OpenDirectory in LDAP Mappings:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/osxDirectoryUtility5.png" alt=""/>
+                </div>
+
+                <p>Enter o=od as Search Base Suffix and click Ok:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/osxDirectoryUtility6.png" alt=""/>
+                </div>
+
+                <p>Now click  Edit...:</p>
+
+                <p>If DavMail LDAP listen port is not 389, check Use custom port and enter actual port (default is 1389):
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/osxDirectoryUtility7.png" alt=""/>
+                </div>
+
+                <p>Then activate authentication under the Security tab and
+                    enter your credentials, then press OK twice to close the settings:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/osxDirectoryUtility8.png" alt=""/>
+                </div>
+
+                <p>Under the Search Policy tab, choose Contacts, set Search to Custom Path
+                    and click the + to add /LDAPV3/localhost to the list of Directory Domains:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/osxDirectoryUtility9.png" alt=""/>
+                </div>
+
+                <p>Proceed to
+                    <a href="osximapmailsetup.html">OSX IMAP Mail setup</a>
+                </p>
+            </subsection>
+        </section>
+    </body>
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/osxicalsetup.xml b/src/site/xdoc/osxicalsetup.xml
new file mode 100644
index 0000000..780b663
--- /dev/null
+++ b/src/site/xdoc/osxicalsetup.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0"?>
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+
+    <properties>
+        <title>DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway - OSX iCal setup</title>
+        <author email="mguessan at free.fr">Mickael Guessant</author>
+    </properties>
+
+    <body>
+
+        <section name="OSX iCal setup">
+            <p>DavMail Calendar support is now available, tested with Thunderbird/Lightning and Apple iCal.
+                This should also work with other Caldav clients.
+            </p>
+
+            <subsection name="Open iCal Preferences">
+                <p>Open iCal Preferences, go to the Accounts tab and click the plus button:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/osxIcal1.png" alt=""/>
+                </div>
+
+                <p>On Snow Leopard, choose CalDAV as account type, enter you Exchange username and password and server
+                    address without path:
+                </p>
+                <code>http://localhost:1080
+                </code>
+                <p>On 10.5 choose a name, enter you Exchange username and password.
+                    Open the server options section to define the calendar URL (adjust port to your settings):
+                </p>
+                <code>http://localhost:1080/
+                </code>
+                <p>Note that iCal will build the full principal path automatically.
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/osxIcal2.png" alt=""/>
+                </div>
+
+                <p>Click Add, choose Connect Anyway in warning dialog:</p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/osxIcal3.png" alt=""/>
+                </div>
+
+                <p>Proceed to
+                    <a href="osxdirectorysetup.html">OSX directory setup</a>
+                    to enable attendee completion.
+                </p>
+            </subsection>
+        </section>
+    </body>
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/osximapmailsetup.xml b/src/site/xdoc/osximapmailsetup.xml
new file mode 100644
index 0000000..333ef26
--- /dev/null
+++ b/src/site/xdoc/osximapmailsetup.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0"?>
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+
+    <properties>
+        <title>DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway - OSX IMAP Mail setup</title>
+        <author email="mguessan at free.fr">Mickael Guessant</author>
+    </properties>
+
+    <body>
+
+        <section name="OSX IMAP Mail setup">
+            <p>There are two ways to access Exchange emails through DavMail Gateway: the good old
+                and efficient POP protocol, limited to Inbox access and the IMAP protocol that enables
+                full message folder tree access. IMAP support in DavMail is more recent and less optimised
+                than POP. This page describes OSX IMAP Mail setup.
+            </p>
+
+            <subsection name="Create a new account">
+                <p>Launch Apple Mail, open Preferences and click the plus button to create a new
+                    account. Enter you full name, email address and password:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/osxMailImap1.png" alt=""/>
+                </div>
+
+                <p>Choose IMAP account type, enter description, DavMail gateway address (localhost).
+                    Check username and password:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/osxMailImap2.png" alt=""/>
+                </div>
+
+                <p>Click continue, if DavMail is not listening on the default IMAP port (143), you
+                    will get the following error message, click Continue again to proceed:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/osxMailImap3.png" alt=""/>
+                </div>
+
+                <p>Leave SSL unchecked and click Continue:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/osxMailImap4.png" alt=""/>
+                </div>
+
+                <p>Enter Outgoing Mail Server (SMTP) description and DavMail gateway address (localhost).
+                    Check Use Authentication and enter your Exchange username and password:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/osxMailImap5.png" alt=""/>
+                </div>
+
+                <p>Click continue, if DavMail is not listening on the default SMTP port (25), you
+                    will get the following error message, click Continue again to proceed:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/osxMailImap6.png" alt=""/>
+                </div>
+
+                <p>Leave SSL unchecked and click Continue:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/osxMailImap7.png" alt=""/>
+                </div>
+
+                <p>Check summary and click Create:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/osxMailImap8.png" alt=""/>
+                </div>
+
+            </subsection>
+            <subsection name="Additional settings">
+                <p>Open the Mailbox Behaviors tab and uncheck Store sent messages on server, as Exchange already
+                    stores each sent message in this folder:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/osxMailImap9.png" alt=""/>
+                </div>
+                <p>Under the advanced tab, enter actual DavMail IMAP port:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/osxMailImap10.png" alt=""/>
+                </div>
+                <p>Under the Account Information tab, choose Edit Server List in Outgoing Mail Server (SMTP) field:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/osxMailImap11.png" alt=""/>
+                </div>
+                <p>Open the advanced tab, check use custom port and enter actual DavMail SMTP port:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/osxMailImap12.png" alt=""/>
+                </div>
+                <p>You may also want to set the Add invitations to iCal parameter to Never:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/osxMailImap13.png" alt=""/>
+                </div>
+
+                <p>Proceed to
+                    <a href="osxaddressbooksetup.html">OSX Address Book setup</a>
+                </p>
+
+            </subsection>
+        </section>
+    </body>
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/reviews.xml b/src/site/xdoc/reviews.xml
new file mode 100644
index 0000000..c7e113e
--- /dev/null
+++ b/src/site/xdoc/reviews.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0"?>
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+
+    <properties>
+        <title>DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway - Reviews</title>
+        <author email="mguessan at free.fr">Mickael Guessant</author>
+    </properties>
+
+    <body>
+
+        <section name="Reviews and additional documentation">
+            <p>In this page you find some independent reviews of DavMail and additional documentation on DavMail setup.
+            </p>
+
+            <ul>
+                <li>
+                    <a href="http://olex.openlogic.com/wazi/2009/escaping-microsoft-exchange-via-davmail-fetchmail-postfix-courier-imap/">
+                        Escaping Microsoft Exchange via Davmail + Fetchmail + Postfix + Courier IMAP
+                    </a>
+                </li>
+                <li>
+                    <a href="http://www.intrasection.com/pjmorr/2010/01/21/snow-leopard-and-exchange-2003/">
+                        Getting mac mail, ical and address book to talk to Exchange 2003
+                    </a>
+                </li>
+                <li>
+                    <a href="http://www.oucs.ox.ac.uk/nexus/calendar/iCal/davmail.xml">
+                        Nexus Calendaring using Davmail and iCal
+                    </a>
+                </li>
+                <li>
+                    <a href="http://wiki.rcs.manchester.ac.uk/community/ExchangeThunderbird">
+                        Exchange Email/Calendars via DavMail with Thunderbird
+                    </a>
+                </li>
+                <li>
+                    <a href="http://www.ronakg.in/2010/07/thunderbird_lightning_davmail_ubuntu/">
+                        How to setup Thunderbird + Lightning + DavMail on Ubuntu 10.04 (Lucid Lynx)
+                    </a>
+                </li>
+                <li>
+                    <a href="http://karuppuswamy.com/wordpress/2010/05/13/how-to-integrate-thunderbird-with-ms-exchange-to-replace-ms-outlook/">
+                        How to use Thunderbird to send and receive Exchange email replacing MS-Outlook
+                    </a>
+                </li>
+                <li>
+                    <a href="http://guycoen.wordpress.com/2010/12/09/outlook-2011-on-mac-and-exchange-2003/">
+                        Outlook 2011 on Mac and Exchange 2003
+                    </a>
+                </li>
+                <li>
+                    <a href="http://techruminations.blogspot.com/2010/01/using-davmail-gateway-to-allow.html">
+                        Using DavMail Gateway To Allow Thunderbird To Access Microsoft Exchange Server 2007 & Earlier
+                    </a>
+                </li>
+                <li>
+                    <a href="http://www.linux.com/learn/tutorials/265449-escaping-microsoft-exchange-via-davmail-fetchmail-postfix-courier-imap">
+                        Escaping Microsoft Exchange via DavMail Fetchmail Postfix Courier-imap
+                    </a>
+                </li>
+                <li>
+                    <a href="http://guzaho.wordpress.com/2011/10/12/thunderbird-as-client-for-microsoft-exchange-2010-server/">
+                        Thunderbird as client for Microsoft Exchange 2010 server
+                    </a>
+                </li>
+                <li>
+                    <a href="http://roger-almeida.blogspot.fr/2012/04/thunderbird-with-microsoft-owa-in.html">
+                        Thunderbird with Microsoft OWA in Ubuntu 11.10
+                    </a>
+                </li>
+                <li>
+                    <a href="http://www.docgreen.fr/?p=4">
+                        Utiliser Thunderbird avec un serveur Microsoft Exchange 2007 (French)
+                    </a>
+                </li>
+            </ul>
+        </section>
+    </body>
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/roadmap.xml b/src/site/xdoc/roadmap.xml
new file mode 100644
index 0000000..0bb78ad
--- /dev/null
+++ b/src/site/xdoc/roadmap.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0"?>
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+
+    <properties>
+        <title>DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway - RoadMap</title>
+        <author email="mguessan at free.fr">Mickael Guessant</author>
+    </properties>
+
+    <body>
+
+        <section name="DavMail RoadMap">
+            <p>DavMail features are now almost complete, yet there remains a lot of place
+                for improvement. The following section lists the expected new features
+                and enhancements in next DavMail versions.
+            </p>
+            <subsection name="3.9.9">
+                <p>
+                    <strong>Next minor release</strong>
+                </p>
+                <ul>
+                    <li>Improve IMAP sync performance</li>
+                </ul>
+            </subsection>
+            <subsection name="4.0">
+                <p>
+                    <strong>Next major version</strong>
+                </p>
+                <ul>
+                    <li>Implement split and kerberos authentication</li>
+                    <li>Implement optional ICS Todo/Task conversion to Outlook tasks: Done</li>
+                    <li>Exchange 2010 support through EWS: Done</li>
+                </ul>
+            </subsection>
+            <subsection name="4.0.1">
+                <p>
+                    <strong>Next minor version</strong>
+                </p>
+                <ul>
+                    <li>Switch from registry to link on windows</li>
+                    <li>Test windows install on seven without admin rights</li>
+                </ul>
+            </subsection>
+            <subsection name="4.x">
+                <p>
+                    <strong>Future version</strong>
+                </p>
+                <ul>
+                    <li>Implement Caldav attachments</li>
+                    <li>Test the RPM package</li>
+                    <li>Switch wrappers to WinRun4J</li>
+                    <li>Implement ActiveSync backend</li>
+                    <li>Implement EWS frontend for Exchange 2003 backend and Outlook 2011</li>
+                    <li>Implement custom authenticator interface to handle specific authentications
+                        (e.g. token based)
+                    </li>
+                </ul>
+            </subsection>
+        </section>
+    </body>
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/serversetup.xml b/src/site/xdoc/serversetup.xml
new file mode 100644
index 0000000..9f25547
--- /dev/null
+++ b/src/site/xdoc/serversetup.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0"?>
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+
+    <properties>
+        <title>DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway - Server setup</title>
+        <author email="mguessan at free.fr">Mickael Guessant</author>
+    </properties>
+
+    <body>
+
+        <section name="DavMail Setup as a standalone server">
+            <p>Prerequisite : Sun JRE 5, 6 or 7 or OpenJDK 6 or 7.
+            </p>
+
+            <p>Davmail Gateway can run in server mode as a gateway between the mail
+                client and the Outlook Web Access (Exchange) server.
+                In server mode Davmail can run on any Java supported platform.
+                This mode was tested successfully with the Iphone and should work with
+                any phone with POP/IMAP/SMTP/LDAP/Caldav/Carddav client.
+                In this mode many users can share the same DavMail instance.
+            </p>
+
+            <p>Download the generic DavMail package from Sourceforge and uncompress it with
+                your favorite tool, e.g. on Linux:&#x20;<code>unzip davmail-*.zip</code>.
+            </p>
+            <p>Prepare a davmail.properties file according to your local needs :
+            </p>
+            <source><![CDATA[
+davmail.url=http://exchangeServer/exchange/
+davmail.enableEws=false
+davmail.popPort=1110
+davmail.imapPort=1143
+davmail.smtpPort=1025
+davmail.caldavPort=1080
+davmail.ldapPort=1389
+davmail.keepDelay=30
+davmail.sentKeepDelay=90
+davmail.caldavPastDelay=90
+davmail.imapIdleDelay=
+davmail.useSystemProxies=false
+davmail.enableProxy=false
+davmail.proxyHost=
+davmail.proxyPort=
+davmail.proxyUser=
+davmail.proxyPassword=
+davmail.ssl.keystoreType=JKS
+davmail.ssl.keyPass=
+davmail.ssl.keystoreFile=
+davmail.ssl.keystorePass=
+davmail.smtpSaveInSent=true
+davmail.server=true
+davmail.server.certificate.hash=
+davmail.bindAddress=
+davmail.clientSoTimeout=
+davmail.allowRemote=true
+davmail.disableUpdateCheck=false
+log4j.rootLogger=WARN
+log4j.logger.davmail=DEBUG
+log4j.logger.org.apache.commons.httpclient=WARN
+log4j.logger.httpclient.wire=WARN
+davmail.logFilePath=/var/log/davmail.log]]>
+            </source>
+            <p>See
+                <a href="gettingstarted.html">Getting started</a>
+                for more information on
+                the options. Make sure davmail.server is set to true (no icon tray) and allow
+                remote connections: davmail.allowRemote=true.
+            </p>
+            <p>To disable a service, set an empty port value.</p>
+            <p>Launch Davmail with the following command:
+                <source>nohup davmail.sh davmail.properties &</source>.
+            </p>
+            <p>Then check messages:
+                <source>tail -f nohup.out</source>
+            </p>
+        </section>
+
+        <section name="Register DavMail windows service">
+            <p>A new WinRun4J davmailservice.exe wrapper is available in the windows
+                package.
+            </p>
+            <p>To register DavMail as a windows service, use:
+                <source>davmailservice --WinRun4J:RegisterService</source>
+                or
+                <source>sc create DavMail binPath= \path\to\davmailservice.exe type= own start= auto</source>
+            </p>
+        </section>
+
+        <section name="DavMail Setup as a JEE Web Application">
+            <p>Prerequisites : Sun JRE 5, 6 or 7 or OpenJDK 6 or 7 and any JEE compliant web container
+            </p>
+
+            <p>Davmail Gateway can now be deployed in any JEE application server using
+                the war package. In this mode, DavMail listener threads run inside the
+                application server and follow the web application lifecycle (start,
+                stop, deploy, undeploy). The following items describe Tomcat deployment,
+                details will vary according to the specific application server available.
+            </p>
+
+            <p>Download the war DavMail package from Sourceforge<code>davmail-*.war</code>,
+                and deploy it inside the application server. In Tomcat, this means copy the
+                war file to the webapps directory. If Tomcat is started and automatic
+                deployment enabled (this is the default configuration), the package is
+                automatically uncompressed and started.
+            </p>
+            <p>The davmail.properties configuration file is then available under
+                <code>davmail-*/WEB-INF/classes</code>.
+            </p>
+
+            <p>Note: DavMail does not use the standard Tomcat HTTP connector and uses the same listeners
+                in war and server modes. This means the HTTP port for Caldav url is specified in davmail.properties
+            </p>
+
+            <p>See above and
+                <a href="gettingstarted.html">Getting started</a>
+                for more information on
+                the options. Make sure davmail.server is set to true (no icon tray) and allow
+                remote connections: davmail.allowRemote=true.
+            </p>
+        </section>
+    </body>
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/sslsetup.xml b/src/site/xdoc/sslsetup.xml
new file mode 100644
index 0000000..fa555f4
--- /dev/null
+++ b/src/site/xdoc/sslsetup.xml
@@ -0,0 +1,116 @@
+<?xml version="1.0"?>
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+
+    <properties>
+        <title>DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway - SSL setup</title>
+        <author email="mguessan at free.fr">Mickael Guessant</author>
+    </properties>
+
+    <body>
+
+        <section name="SSL setup">
+            <subsection name="Server keystore (Client to DavMail)">
+                <p>SSL is not necessary when DavMail is used in workstation mode, as communication between clients and
+                    DavMail remain local. However, in server (shared) mode e.g. with a smartphone connecting to DavMail
+                    over the internet, you should make sure encryption is enabled.
+                </p>
+
+                <p>The simplest way to secure communication between mail/calendar clients and DavMail is to create a
+                    self signed certificate:
+                </p>
+                <source>keytool -genkey -keyalg rsa -keysize 2048 -storepass password -keystore davmail.p12 -storetype
+                    pkcs12 -validity 3650 -dname cn=davmailhostname.company.com,ou=davmail,o=sf,o=net
+                </source>
+
+                <p>Note to iPhone users: iOS does not support the default DSA algorithm, make sure you use an RSA key
+                    pair
+                </p>
+
+                <p>Another note : do not use blank passwords, both keystore and key passwords must be set</p>
+
+                <p>If you have an official certificate in PEM form, convert it to PKCS12 with the following command:</p>
+                <source>openssl pkcs12 -export -in cert-davmail.pem -inkey privatekey-davmail.key -certfile
+                    chain-davmail.pem -out davmail.p12
+                </source>
+
+                <p>Then add this keystore to DavMail settings:
+                </p>
+                <source><![CDATA[
+davmail.ssl.keystoreType=PKCS12
+davmail.ssl.keyPass=password
+davmail.ssl.keystoreFile=davmail.p12
+davmail.ssl.keystorePass=password]]></source>
+                <p>If your already have your keystore in JKS format, just set keystoreType to JKS in DavMail
+                    settings. keystorePass is the password used to open the KeyStore, keyPass protects the private key
+                    inside the KeyStore. With PKCS12, keyPass and keystorePass are often identical.
+                </p>
+                <p>Restart DavMail, all DavMail listeners will switch to secure mode: POP3S/IMAPS/SMTPS/HTTPS/LDAPS.
+                    You will also need to enable SSL in client applications and manually accept the certificate as it's
+                    not signed by a trusted Certification Authority.
+                </p>
+            </subsection>
+
+            <subsection name="DavMail to Exchange">
+                <p>
+                    <strong>Custom certificate authority</strong>
+                </p>
+                <p>Most users rely on the interactive accept certificate dialog to handle non public certificate authorities.
+                    However, this will not work with an Exchange server cluster with a different certificate on each server.
+                    In this case, you need to update global Java truststore with the custom certificate authority:
+                </p>
+                <source>keytool -import -alias root -keystore /path/to/jre/lib/security/cacerts -trustcacerts -file rootca.crt -storepass changeit -noprompt</source>
+                <p>
+                    <strong>Client certificate</strong>
+                </p>
+                <p>In most cases, using https in OWA url is enough to secure communication between DavMail and Exchange.
+                    However, with Exchange servers setup to require mutual authentication, you will have to register
+                    your client certificate in DavMail settings, either through PKCS11 (smartcard) or file certificate.
+                </p>
+                <p>To use a client certificate provided as a PKCS12 file, set the following keys in DavMail:</p>
+                <source><![CDATA[
+davmail.ssl.clientKeystoreType=PKCS12
+davmail.ssl.clientKeystoreFile=client.p12
+davmail.ssl.clientKeystorePass=password]]></source>
+                <p>For a smartcard, first make sure you PKCS11 module is correctly installed by testing mutual
+                    authentication through a browser. Then set the following properties in DavMail:</p>
+                <source><![CDATA[
+davmail.ssl.clientKeystoreType=PKCS11
+davmail.ssl.pkcs11Library=/full/path/to/pkcs11Module
+davmail.ssl.pkcs11Config=]]></source>
+                <p>PKCS11 library is the full path to the PKCS11 module (.so on Unix, .dll on windows) or simple
+                    library name if PATH (Windows) or LD_LIBRARY_PATH (Unix) already contains the full path. Add any
+                    additional PKCS11 parameter in PKCS11 Config parameter, e.g. <code>slot = 2</code>.
+                </p>
+                <p>To adjust your settings, you can try to access the smartcard with java keytool. First create a file
+                    named pkcs11.config with the following lines:</p>
+                <source><![CDATA[
+name = moduleName
+library = /path/to/pkcs11module]]></source>
+                <p>and list certificates with keytool:</p>
+                <source>keytool -keystore NONE -storetype PKCS11 -providerClass sun.security.pkcs11.SunPKCS11 -providerArg pkcs11.config -list -v</source>
+
+                <p>Sample pkcs11.config for NSS Soft token (Thunderbird/Firefox):</p>
+<source><![CDATA[
+name=NSS
+library=softokn3
+nssArgs="configdir='/path/to/firefox/profile' certPrefix='' keyPrefix='' secmod='secmod.db' flags=readOnly"
+slot = 2
+]]></source>
+
+                <p>Another one for Coolkey (see <a href="http://pkg-coolkey.alioth.debian.org/">Coolkey for Debian</a>
+                    and <a href="http://www7320.nrlssc.navy.mil/pubs/2006/CommonAccessCardLinux.pdf">United States Department of Defense Common Access Cards</a>):</p>
+<source><![CDATA[
+name=CoolKey
+library=/usr/cac/lib/pkcs11/libcoolkeypk11.so
+]]></source>
+
+                <p>More details on java PKCS11 setup in
+                    <a href="http://java.sun.com/javase/6/docs/technotes/guides/security/p11guide.html">Sun PKCS11 guide</a>
+                </p>
+
+            </subsection>
+        </section>
+    </body>
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/thunderbirdcalendarsetup.xml b/src/site/xdoc/thunderbirdcalendarsetup.xml
new file mode 100644
index 0000000..e1f6eeb
--- /dev/null
+++ b/src/site/xdoc/thunderbirdcalendarsetup.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0"?>
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+
+    <properties>
+        <title>DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway - Thunderbird calendar setup</title>
+        <author email="mguessan at free.fr">Mickael Guessant</author>
+    </properties>
+
+    <body>
+
+        <section name="Thunderbird calendar setup">
+            <p>DavMail Calendar support is now available, tested with Thunderbird/Lightning and Apple iCal.
+                This should also work with other Caldav clients.
+            </p>
+
+            <subsection name="Create a new network calendar">
+                <p>Obviously, you need to install the Thunderbird Lightning extension. You can
+                    then create a new network calendar:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/thunderbirdCalendar1.png" alt=""/>
+                </div>
+
+                <p>Choose CalDav format and specify location (adjust port to your settings):</p>
+                <code>http://localhost:1080/users/mail@company.com/calendar
+                </code>
+                <p>Replace mail at company.com with actual user email address.
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/thunderbirdCalendar2.png" alt=""/>
+                </div>
+
+                <p>Choose a name, color and set the associated email address (as set in mail setup).
+                </p>
+                <p>Note that Caldav notifications will not work if this email address field is empty.</p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/thunderbirdCalendar3.png" alt=""/>
+                </div>
+
+
+                <p>Provide your credentials, login is Active Directory account name, i.e. domain\account
+                    or email address with Exchange 2007
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/thunderbirdCalendar4.png" alt=""/>
+                </div>
+
+
+                <p>Finish:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/thunderbirdCalendar5.png" alt=""/>
+                </div>
+
+                <p>You may want to enable the (experimental) Lightning calendar cache feature to avoid
+                    the possibly long calendar load task delay on Thunderbird startup: double click
+                    on the calendar in Lightning left pane and activate cache. Restart Thunderbird.
+                </p>
+
+                <p>Proceed to
+                    <a href="thunderbirddirectorysetup.html">Thunderbird directory setup</a>
+                </p>
+            </subsection>
+        </section>
+    </body>
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/thunderbirdcarddavsetup.xml b/src/site/xdoc/thunderbirdcarddavsetup.xml
new file mode 100644
index 0000000..f1ee8d6
--- /dev/null
+++ b/src/site/xdoc/thunderbirdcarddavsetup.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+
+    <properties>
+        <title>DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway - Thunderbird Carddav setup</title>
+        <author email="mguessan at free.fr">Mickael Guessant</author>
+    </properties>
+
+    <body>
+
+        <section name="Thunderbird Carddav setup">
+            <p>Thunderbird does not support Carddav natively, you will need to download and install the SOGo Connector
+                plugin from
+                <a href="http://www.sogo.nu/fr/downloads/frontends.html">SOGo Connector Thunderbird extension</a>.
+            </p>
+
+            <subsection name="Create a new remote address book">
+                <p>After plugin installation, restart Thunderbird, open address book and choose
+                    File / New / Remote Address Book:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/thunderbirdCarddav1.png" alt=""/>
+                </div>
+
+                <p>Choose a name and enter contact folder location (adjust port to your settings):</p>
+                <code>http://localhost:1080/users/mail@company.com/contacts
+                </code>
+                <p>Replace mail at company.com with actual user email address.
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/thunderbirdCarddav2.png" alt=""/>
+                </div>
+
+                <p>Press OK and launch synchronisation with the toolbar
+                    <code>Synchronize</code>
+                    button.
+                </p>
+
+                <p>Provide your credentials, login is Active Directory account name, i.e. domain\account
+                    or email address with Exchange 2007
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/thunderbirdCarddav3.png" alt=""/>
+                </div>
+            </subsection>
+        </section>
+    </body>
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/thunderbirddirectorysetup.xml b/src/site/xdoc/thunderbirddirectorysetup.xml
new file mode 100644
index 0000000..34eb472
--- /dev/null
+++ b/src/site/xdoc/thunderbirddirectorysetup.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0"?>
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+
+    <properties>
+        <title>DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway - Thunderbird directory setup</title>
+        <author email="mguessan at free.fr">Mickael Guessant</author>
+    </properties>
+
+    <body>
+
+        <section name="Thunderbird directory setup">
+            <p>DavMail Directory support is now available to access Exchange address book through LDAP.
+            </p>
+
+            <subsection name="Create a new LDAP directory">
+                <p>Open Thunderbird address book and choose File/New/LDAP Directory:</p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/thunderbirdDirectory1.png" alt=""/>
+                </div>
+
+                <p>Choose a name, host is always localhost, LDAP base context&#x20;<code>ou=people</code>,
+                    adjust port to your settings and set user to your Active Directory account name,
+                    i.e. domain\account. Leave other options to default values.
+                </p>
+                <p>You may also want to use this directory for mail completion in compose window :
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/thunderbirdDirectory2.png" alt=""/>
+                </div>
+
+                <p>Proceed to
+                    <a href="thunderbirdcarddavsetup.html">Thunderbird Carddav setup</a>
+                </p>
+            </subsection>
+        </section>
+    </body>
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/thunderbirdimapmailsetup.xml b/src/site/xdoc/thunderbirdimapmailsetup.xml
new file mode 100644
index 0000000..f911ce5
--- /dev/null
+++ b/src/site/xdoc/thunderbirdimapmailsetup.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0"?>
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+
+    <properties>
+        <title>DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway - Thunderbird mail setup</title>
+        <author email="mguessan at free.fr">Mickael Guessant</author>
+    </properties>
+
+    <body>
+
+        <section name="IMAP Thunderbird mail setup">
+            <p>There are two ways to access Exchange emails through DavMail Gateway: the good old
+                and efficient POP protocol, limited to Inbox access and the IMAP protocol that enables
+                full message folder tree access. DavMail IMAP listener has a higher memory footprint.
+            </p>
+
+            <p>This page describes IMAP setup, if you don't need multiple folders access, proceed to
+                <a href="thunderbirdmailsetup.html">
+                    POP Thunderbird mail setup
+                </a>
+                instead.
+            </p>
+
+            <p>DavMail can be used with any IMAP/SMTP client by adjusting the following description designed for
+                Thunderbird. DavMail IMAP support is tested with Thunderbird, Outlook and Apple Mail.
+            </p>
+
+            <subsection name="Create a new account">
+                <p>Choose <code>Add Mail Account...</code> under Account settings and enter name, email address
+                    and password:</p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/thunderbirdAccount1.png" alt=""/>
+                </div>
+                <p>Click <code>Continue</code> and <code>Manual config</code> without waiting for automatic config:</p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/thunderbirdAccount2.png" alt=""/>
+                </div>
+
+                <p>Incoming server type is IMAP, server hostname is localhost, default port is 1143, no SSL and normal
+                    password authentication. Outgoing server is localhost, default port is 1025, no SSL and normal
+                    password authentication. Set your username (windows account name or email address) and click
+                    <code>Re-test</code> to validate account settings, then <code>Create Account</code>:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/thunderbirdAccountPop3.png" alt=""/>
+                </div>
+
+
+                <p>Without SSL you will get the following warning, check box and confirm account creation:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/thunderbirdAccount4.png" alt=""/>
+                </div>
+                <p>Note: as communication between Thunderbird and DavMail is local, cleartext mode is not an issue,
+                    except on shared machines (e.g. Terminal server).</p>
+
+                <p>Proceed to
+                    <a href="thunderbirdcalendarsetup.html">Thunderbird calendar setup</a>
+                </p>
+            </subsection>
+        </section>
+    </body>
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/thunderbirdmailsetup.xml b/src/site/xdoc/thunderbirdmailsetup.xml
new file mode 100644
index 0000000..243b7d2
--- /dev/null
+++ b/src/site/xdoc/thunderbirdmailsetup.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0"?>
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+
+    <properties>
+        <title>DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway - Thunderbird mail setup</title>
+        <author email="mguessan at free.fr">Mickael Guessant</author>
+    </properties>
+
+    <body>
+
+        <section name="POP Thunderbird mail setup">
+            <p>There are two ways to access Exchange emails through DavMail Gateway: the good old
+                and efficient POP protocol, limited to Inbox access and the IMAP protocol that enables
+                full message folder tree access. IMAP support in DavMail is more recent and less optimised
+                than POP.
+            </p>
+
+            <p>This page describes POP3 setup, if you need multiple folders access, proceed to
+                <a href="thunderbirdimapmailsetup.html">
+                    IMAP Thunderbird mail setup
+                </a>
+                instead.
+            </p>
+
+            <p>DavMail can be used with any POP3/SMTP client by adjusting the following description designed for
+                Thunderbird
+            </p>
+
+            <subsection name="Create a new account">
+                <p>Choose <code>Add Mail Account...</code> under Account settings and enter name, email address
+                    and password:</p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/thunderbirdAccount1.png" alt=""/>
+                </div>
+
+                <p>Click <code>Continue</code> and <code>Manual config</code> without waiting for automatic config:</p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/thunderbirdAccount2.png" alt=""/>
+                </div>
+
+                <p>Incoming server type is POP, server hostname is localhost, default port is 1110, no SSL and normal
+                    password authentication. Outgoing server is localhost, default port is 1025, no SSL and normal
+                    password authentication. Set your username (windows account name or email address) and click
+                    <code>Re-test</code> to validate account settings, then <code>Create Account</code>:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/thunderbirdAccount3.png" alt=""/>
+                </div>
+
+
+                <p>Without SSL you will get the following warning, check box and confirm account creation:
+                </p>
+                <div style="width: 100%;text-align: center">
+                    <img src="images/thunderbirdAccount4.png" alt=""/>
+                </div>
+                <p>Note: as communication between Thunderbird and DavMail is local, cleartext mode is not an issue,
+                    except on shared machines (e.g. Terminal server).</p>
+
+                <p>Proceed to
+                    <a href="thunderbirdcalendarsetup.html">Thunderbird calendar setup</a>
+                </p>
+            </subsection>
+        </section>
+    </body>
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/windowssetup.xml b/src/site/xdoc/windowssetup.xml
new file mode 100644
index 0000000..3395689
--- /dev/null
+++ b/src/site/xdoc/windowssetup.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0"?>
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+
+    <properties>
+        <title>DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway - Windows setup</title>
+        <author email="mguessan at free.fr">Mickael Guessant</author>
+    </properties>
+
+    <body>
+
+        <section name="DavMail Setup on windows">
+            <p>Prerequisite : Sun JRE 5, 6 or 7. Tray icon is now implemented with SWT and compatible with
+                Java 5. You may use DavMail with an older version, but the gateway will run as a
+                command line application.
+            </p>
+
+            <p>If Java is not available, DavMail Jsmooth launcher will trigger java download and
+                setup.
+            </p>
+
+            <p>DavMail setup is straightforward, just follow the setup wizard.
+            </p>
+            <p>Choose language:
+            </p>
+            <div style="width: 100%;text-align: center">
+                <img src="images/setup1.png" alt=""/>
+            </div>
+            <p>Click Next:
+            </p>
+            <div style="width: 100%;text-align: center">
+                <img src="images/setup2.png" alt=""/>
+            </div>
+            <p>Accept GPL licence:
+            </p>
+            <div style="width: 100%;text-align: center">
+                <img src="images/setup3.png" alt=""/>
+            </div>
+            <p>Choose path:
+            </p>
+            <div style="width: 100%;text-align: center">
+                <img src="images/setup4.png" alt=""/>
+            </div>
+            <p>Launch installation and start gateway:
+            </p>
+            <div style="width: 100%;text-align: center">
+                <img src="images/setup5.png" alt=""/>
+            </div>
+            <p>Adjust DavMail settings :
+                <a href="gettingstarted.html">Getting started</a>
+            </p>
+        </section>
+
+    </body>
+</document>
\ No newline at end of file
diff --git a/src/test/davmail/AbstractDavMailTestCase.java b/src/test/davmail/AbstractDavMailTestCase.java
new file mode 100644
index 0000000..e69a446
--- /dev/null
+++ b/src/test/davmail/AbstractDavMailTestCase.java
@@ -0,0 +1,91 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail;
+
+import davmail.exchange.ExchangeSession;
+import davmail.http.DavGatewaySSLProtocolSocketFactory;
+import junit.framework.TestCase;
+
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.internet.MimeMessage;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * DavMail generic test case.
+ * Loads DavMail settings
+ */
+public class AbstractDavMailTestCase extends TestCase {
+    protected static boolean loaded;
+    protected static String url;
+    protected static String certificateHash;
+    protected static String username;
+    protected static String password;
+    protected static ExchangeSession session;
+
+    @Override
+    public void setUp() throws IOException {
+        if (!loaded) {
+            loaded = true;
+
+            if (url == null) {
+                // try to load settings from current folder davmail.properties
+                File file = new File("davmail.properties");
+                if (file.exists()) {
+                      Settings.setConfigFilePath("davmail.properties");
+                }
+                // Load current settings
+                Settings.load();
+            } else {
+                Settings.setDefaultSettings();
+                Settings.setProperty("davmail.url", url);
+                Settings.setProperty("davmail.server.certificate.hash", certificateHash);
+                Settings.setProperty("davmail.username", username);
+                Settings.setProperty("davmail.password", password);
+            }
+
+
+            DavGatewaySSLProtocolSocketFactory.register();
+            // force server mode
+            Settings.setProperty("davmail.server", "true");
+
+            // enable WIRE debug log
+            //Settings.setLoggingLevel("httpclient.wire", Level.DEBUG);
+            // enable EWS support
+            //Settings.setProperty("davmail.enableEws", "false");
+
+        }
+    }
+
+    protected MimeMessage createMimeMessage() throws MessagingException {
+        MimeMessage mimeMessage = new MimeMessage((Session) null);
+        mimeMessage.addHeader("To", "test at test.local");
+        mimeMessage.setText("Test message\n");
+        mimeMessage.setSubject("Test subject");
+        return mimeMessage;
+    }
+
+    protected byte[] getMimeBody(MimeMessage mimeMessage) throws IOException, MessagingException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        mimeMessage.writeTo(baos);
+        return baos.toByteArray();
+    }
+}
diff --git a/src/test/davmail/caldav/TestCaldav.java b/src/test/davmail/caldav/TestCaldav.java
new file mode 100644
index 0000000..a0a1846
--- /dev/null
+++ b/src/test/davmail/caldav/TestCaldav.java
@@ -0,0 +1,393 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.caldav;
+
+import davmail.AbstractDavMailTestCase;
+import davmail.DavGateway;
+import davmail.Settings;
+import davmail.exchange.ExchangeSession;
+import davmail.exchange.ExchangeSessionFactory;
+import davmail.util.StringUtil;
+import org.apache.commons.httpclient.*;
+import org.apache.commons.httpclient.auth.AuthScope;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.httpclient.methods.RequestEntity;
+import org.apache.commons.httpclient.params.HttpMethodParams;
+import org.apache.commons.httpclient.util.URIUtil;
+import org.apache.jackrabbit.webdav.DavException;
+import org.apache.jackrabbit.webdav.MultiStatus;
+import org.apache.jackrabbit.webdav.MultiStatusResponse;
+import org.apache.jackrabbit.webdav.client.methods.DavMethodBase;
+import org.apache.jackrabbit.webdav.client.methods.MoveMethod;
+import org.apache.jackrabbit.webdav.client.methods.PropFindMethod;
+import org.apache.jackrabbit.webdav.property.DavPropertyName;
+import org.apache.jackrabbit.webdav.property.DavPropertyNameSet;
+import org.apache.jackrabbit.webdav.xml.Namespace;
+import org.apache.log4j.Level;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Test Caldav listener.
+ */
+public class TestCaldav extends AbstractDavMailTestCase {
+
+    class SearchReportMethod extends DavMethodBase {
+        SearchReportMethod(String path, String stringContent) throws UnsupportedEncodingException {
+            this(path, stringContent.getBytes("UTF-8"));
+        }
+
+        SearchReportMethod(String path, final byte[] content) {
+            super(path);
+            setRequestEntity(new RequestEntity() {
+
+                public boolean isRepeatable() {
+                    return true;
+                }
+
+                public void writeRequest(OutputStream outputStream) throws IOException {
+                    outputStream.write(content);
+                }
+
+                public long getContentLength() {
+                    return content.length;
+                }
+
+                public String getContentType() {
+                    return "text/xml;charset=UTF-8";
+                }
+            });
+        }
+
+        @Override
+        public String getName() {
+            return "REPORT";
+        }
+
+        @Override
+        protected boolean isSuccess(int statusCode) {
+            return statusCode == HttpStatus.SC_MULTI_STATUS;
+        }
+    }
+
+    HttpClient httpClient;
+
+    @Override
+    public void setUp() throws IOException {
+        super.setUp();
+        if (httpClient == null) {
+            // start gateway
+            DavGateway.start();
+            httpClient = new HttpClient();
+            HostConfiguration hostConfig = httpClient.getHostConfiguration();
+            URI httpURI = new URI("http://localhost:" + Settings.getProperty("davmail.caldavPort"), true);
+            hostConfig.setHost(httpURI);
+            AuthScope authScope = new AuthScope(null, -1);
+            httpClient.getState().setCredentials(authScope, new NTCredentials(Settings.getProperty("davmail.username"), Settings.getProperty("davmail.password"), "", ""));
+        }
+        if (session == null) {
+            session = ExchangeSessionFactory.getInstance(Settings.getProperty("davmail.username"), Settings.getProperty("davmail.password"));
+        }
+    }
+
+    public void testGetRoot() throws IOException {
+        GetMethod method = new GetMethod("/");
+        httpClient.executeMethod(method);
+        assertEquals(HttpStatus.SC_OK, method.getStatusCode());
+    }
+
+    public void testGetUserRoot() throws IOException {
+        GetMethod method = new GetMethod("/users/" + session.getEmail() + '/');
+        httpClient.executeMethod(method);
+        assertEquals(HttpStatus.SC_OK, method.getStatusCode());
+    }
+
+    public void testGetCalendar() throws IOException {
+        GetMethod method = new GetMethod("/users/" + session.getEmail() + "/calendar/");
+        httpClient.executeMethod(method);
+        assertEquals(HttpStatus.SC_OK, method.getStatusCode());
+    }
+
+    public void testGetInbox() throws IOException {
+        GetMethod method = new GetMethod("/users/" + session.getEmail() + "/inbox/");
+        httpClient.executeMethod(method);
+        assertEquals(HttpStatus.SC_OK, method.getStatusCode());
+    }
+
+    public void testGetContacts() throws IOException {
+        GetMethod method = new GetMethod("/users/" + session.getEmail() + "/contacts/");
+        httpClient.executeMethod(method);
+        assertEquals(HttpStatus.SC_OK, method.getStatusCode());
+    }
+
+    public void testPropfindCalendar() throws IOException {
+        Settings.setLoggingLevel("httpclient.wire", Level.DEBUG);
+        PropFindMethod method = new PropFindMethod("/users/" + session.getEmail() + "/calendar/", null, 1);
+        httpClient.executeMethod(method);
+        assertEquals(HttpStatus.SC_MULTI_STATUS, method.getStatusCode());
+    }
+
+
+    public void testGetOtherUserCalendar() throws IOException {
+        Settings.setLoggingLevel("httpclient.wire", Level.DEBUG);
+        PropFindMethod method = new PropFindMethod("/principals/users/" + Settings.getProperty("davmail.to") + "/calendar/");
+        httpClient.executeMethod(method);
+        assertEquals(HttpStatus.SC_OK, method.getStatusCode());
+    }
+
+    public void testReportCalendar() throws IOException, DavException {
+        SimpleDateFormat formatter = ExchangeSession.getZuluDateFormat();
+        Calendar cal = Calendar.getInstance();
+        Date end = cal.getTime();
+        cal.add(Calendar.MONTH, -1);
+        Date start = cal.getTime();
+
+        StringBuilder buffer = new StringBuilder();
+        buffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+        buffer.append("<C:calendar-query xmlns:C=\"urn:ietf:params:xml:ns:caldav\" xmlns:D=\"DAV:\">");
+        buffer.append("<D:prop>");
+        buffer.append("<C:calendar-data/>");
+        buffer.append("</D:prop>");
+        buffer.append("<C:comp-filter name=\"VCALENDAR\">");
+        buffer.append("<C:comp-filter name=\"VEVENT\">");
+        buffer.append("<C:time-range start=\"").append(formatter.format(start)).append("\" end=\"").append(formatter.format(end)).append("\"/>");
+        //buffer.append("<C:time-range start=\"").append(formatter.format(start)).append("\"/>");
+        buffer.append("</C:comp-filter>");
+        buffer.append("</C:comp-filter>");
+        buffer.append("<C:filter>");
+        buffer.append("</C:filter>");
+        buffer.append("</C:calendar-query>");
+        SearchReportMethod method = new SearchReportMethod("/users/" + session.getEmail() + "/calendar/", buffer.toString());
+        httpClient.executeMethod(method);
+        assertEquals(HttpStatus.SC_MULTI_STATUS, method.getStatusCode());
+        MultiStatus multiStatus = method.getResponseBodyAsMultiStatus();
+        MultiStatusResponse[] responses = multiStatus.getResponses();
+        ExchangeSession.Condition dateCondition = session.and(
+                session.gt("dtstart", session.formatSearchDate(start)),
+                session.lt("dtend", session.formatSearchDate(end))
+        );
+        List<ExchangeSession.Event> events = session.searchEvents("/users/" + session.getEmail() + "/calendar/",
+                session.or(session.isEqualTo("instancetype", 1),
+                        session.and(session.isEqualTo("instancetype", 0), dateCondition))
+
+        );
+
+        assertEquals(events.size(), responses.length);
+    }
+
+    public void testReportInbox() throws IOException, DavException {
+
+        StringBuilder buffer = new StringBuilder();
+        buffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+        buffer.append("<C:calendar-query xmlns:C=\"urn:ietf:params:xml:ns:caldav\" xmlns:D=\"DAV:\">");
+        buffer.append("<D:prop>");
+        buffer.append("<C:calendar-data/>");
+        buffer.append("</D:prop>");
+        buffer.append("<C:filter>");
+        buffer.append("</C:filter>");
+        buffer.append("</C:calendar-query>");
+        SearchReportMethod method = new SearchReportMethod("/users/" + session.getEmail() + "/inbox/", buffer.toString());
+        httpClient.executeMethod(method);
+        assertEquals(HttpStatus.SC_MULTI_STATUS, method.getStatusCode());
+        MultiStatus multiStatus = method.getResponseBodyAsMultiStatus();
+        MultiStatusResponse[] responses = multiStatus.getResponses();
+        /*List<ExchangeSession.Event> events = session.searchEvents("/users/" + session.getEmail() + "/calendar/",
+                session.or(session.isEqualTo("instancetype", 1),
+                        session.and(session.isEqualTo("instancetype", 0), dateCondition))
+
+        );*/
+
+        //assertEquals(events.size(), responses.length);
+    }
+
+    public void testReportTasks() throws IOException, DavException {
+        StringBuilder buffer = new StringBuilder();
+        buffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+        buffer.append("<C:calendar-query xmlns:C=\"urn:ietf:params:xml:ns:caldav\" xmlns:D=\"DAV:\">");
+        buffer.append("<D:prop>");
+        buffer.append("<C:calendar-data/>");
+        buffer.append("</D:prop>");
+        buffer.append("<C:comp-filter name=\"VCALENDAR\">");
+        buffer.append("<C:comp-filter name=\"VTODO\"/>");
+        buffer.append("</C:comp-filter>");
+        buffer.append("<C:filter>");
+        buffer.append("</C:filter>");
+        buffer.append("</C:calendar-query>");
+        SearchReportMethod method = new SearchReportMethod("/users/" + session.getEmail() + "/calendar/", buffer.toString());
+        httpClient.executeMethod(method);
+        assertEquals(HttpStatus.SC_MULTI_STATUS, method.getStatusCode());
+        MultiStatus multiStatus = method.getResponseBodyAsMultiStatus();
+        MultiStatusResponse[] responses = multiStatus.getResponses();
+    }
+
+    public void testReportEventsOnly() throws IOException, DavException {
+        StringBuilder buffer = new StringBuilder();
+        buffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+        buffer.append("<C:calendar-query xmlns:C=\"urn:ietf:params:xml:ns:caldav\" xmlns:D=\"DAV:\">");
+        buffer.append("<D:prop>");
+        buffer.append("<C:calendar-data/>");
+        buffer.append("</D:prop>");
+        buffer.append("<C:comp-filter name=\"VCALENDAR\">");
+        buffer.append("<C:comp-filter name=\"VEVENT\"/>");
+        buffer.append("</C:comp-filter>");
+        buffer.append("<C:filter>");
+        buffer.append("</C:filter>");
+        buffer.append("</C:calendar-query>");
+        SearchReportMethod method = new SearchReportMethod("/users/" + session.getEmail() + "/calendar/", buffer.toString());
+        httpClient.executeMethod(method);
+        assertEquals(HttpStatus.SC_MULTI_STATUS, method.getStatusCode());
+        MultiStatus multiStatus = method.getResponseBodyAsMultiStatus();
+        MultiStatusResponse[] responses = multiStatus.getResponses();
+    }
+
+    public void testCreateCalendar() throws IOException {
+        String folderName = "test & accentué";
+        String encodedFolderpath = URIUtil.encodePath("/users/" + session.getEmail() + "/calendar/" + folderName + '/');
+        // first delete calendar
+        session.deleteFolder("calendar/" + folderName);
+        String body =
+                "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" +
+                        "   <C:mkcalendar xmlns:D=\"DAV:\"\n" +
+                        "                 xmlns:C=\"urn:ietf:params:xml:ns:caldav\">\n" +
+                        "     <D:set>\n" +
+                        "       <D:prop>\n" +
+                        "         <D:displayname>" + StringUtil.xmlEncode(folderName) + "</D:displayname>\n" +
+                        "         <C:calendar-description xml:lang=\"en\">Calendar description</C:calendar-description>\n" +
+                        "         <C:supported-calendar-component-set>\n" +
+                        "           <C:comp name=\"VEVENT\"/>\n" +
+                        "         </C:supported-calendar-component-set>\n" +
+                        "       </D:prop>\n" +
+                        "     </D:set>\n" +
+                        "   </C:mkcalendar>";
+
+        SearchReportMethod method = new SearchReportMethod(encodedFolderpath, body) {
+            @Override
+            public String getName() {
+                return "MKCALENDAR";
+            }
+        };
+        httpClient.executeMethod(method);
+        assertEquals(HttpStatus.SC_CREATED, method.getStatusCode());
+
+        GetMethod getMethod = new GetMethod(encodedFolderpath);
+        httpClient.executeMethod(getMethod);
+        assertEquals(HttpStatus.SC_OK, getMethod.getStatusCode());
+    }
+
+    public void testPropfindPrincipal() throws IOException, DavException {
+        //Settings.setLoggingLevel("httpclient.wire", Level.DEBUG);
+
+        DavPropertyNameSet davPropertyNameSet = new DavPropertyNameSet();
+        davPropertyNameSet.add(DavPropertyName.create("calendar-home-set", Namespace.getNamespace("urn:ietf:params:xml:ns:caldav")));
+        davPropertyNameSet.add(DavPropertyName.create("calendar-user-address-set", Namespace.getNamespace("urn:ietf:params:xml:ns:caldav")));
+        davPropertyNameSet.add(DavPropertyName.create("schedule-inbox-URL", Namespace.getNamespace("urn:ietf:params:xml:ns:caldav")));
+        davPropertyNameSet.add(DavPropertyName.create("schedule-outbox-URL", Namespace.getNamespace("urn:ietf:params:xml:ns:caldav")));
+        PropFindMethod method = new PropFindMethod("/principals/users/" + session.getEmail() + "/", davPropertyNameSet, 0);
+        httpClient.executeMethod(method);
+        assertEquals(HttpStatus.SC_MULTI_STATUS, method.getStatusCode());
+        MultiStatus multiStatus = method.getResponseBodyAsMultiStatus();
+        MultiStatusResponse[] responses = multiStatus.getResponses();
+        assertEquals(1, responses.length);
+    }
+
+    public void testRenameCalendar() throws IOException {
+        String folderName = "testcalendarfolder";
+        String encodedFolderpath = URIUtil.encodePath("/users/" + session.getEmail() + "/calendar/" + folderName + '/');
+        // first delete calendar
+        session.deleteFolder("calendar/" + folderName);
+        String body =
+                "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" +
+                        "   <C:mkcalendar xmlns:D=\"DAV:\"\n" +
+                        "                 xmlns:C=\"urn:ietf:params:xml:ns:caldav\">\n" +
+                        "     <D:set>\n" +
+                        "       <D:prop>\n" +
+                        "         <D:displayname>" + StringUtil.xmlEncode(folderName) + "</D:displayname>\n" +
+                        "         <C:calendar-description xml:lang=\"en\">Calendar description</C:calendar-description>\n" +
+                        "         <C:supported-calendar-component-set>\n" +
+                        "           <C:comp name=\"VEVENT\"/>\n" +
+                        "         </C:supported-calendar-component-set>\n" +
+                        "       </D:prop>\n" +
+                        "     </D:set>\n" +
+                        "   </C:mkcalendar>";
+
+        SearchReportMethod method = new SearchReportMethod(encodedFolderpath, body) {
+            @Override
+            public String getName() {
+                return "MKCALENDAR";
+            }
+        };
+        httpClient.executeMethod(method);
+        assertEquals(HttpStatus.SC_CREATED, method.getStatusCode());
+        MoveMethod moveMethod = new MoveMethod(encodedFolderpath, "http://localhost:" + Settings.getProperty("davmail.caldavPort")+"/users/" + session.getEmail() + "/movedcalendarfolder", true);
+        httpClient.executeMethod(moveMethod);
+    }
+
+    public void testRenameMainCalendar() throws IOException {
+        MoveMethod moveMethod = new MoveMethod("/users/" + session.getEmail() + "/Calendrierzzz", "http://localhost:" + Settings.getProperty("davmail.caldavPort")+"/users/" + session.getEmail() + "/Calendrier", true);
+        httpClient.executeMethod(moveMethod);
+    }
+
+    public void testWellKnown() throws IOException, DavException {
+        DavPropertyNameSet davPropertyNameSet = new DavPropertyNameSet();
+        davPropertyNameSet.add(DavPropertyName.create("current-user-principal", Namespace.getNamespace("DAV:")));
+        davPropertyNameSet.add(DavPropertyName.create("principal-URL", Namespace.getNamespace("DAV:")));
+        davPropertyNameSet.add(DavPropertyName.create("resourcetype", Namespace.getNamespace("DAV:")));
+        PropFindMethod method = new PropFindMethod("/.well-known/caldav", davPropertyNameSet, 0);
+        httpClient.executeMethod(method);
+        assertEquals(HttpStatus.SC_MOVED_PERMANENTLY, method.getStatusCode());
+    }
+
+    public void testPrincipalUrl() throws IOException, DavException {
+        DavPropertyNameSet davPropertyNameSet = new DavPropertyNameSet();
+        davPropertyNameSet.add(DavPropertyName.create("principal-URL", Namespace.getNamespace("DAV:")));
+        PropFindMethod method = new PropFindMethod("/principals/users/"+session.getEmail(), davPropertyNameSet, 0);
+        httpClient.executeMethod(method);
+        method.getResponseBodyAsMultiStatus();
+        assertEquals(HttpStatus.SC_MULTI_STATUS, method.getStatusCode());
+    }
+
+    public void testPropfindRoot() throws IOException, DavException {
+        DavPropertyNameSet davPropertyNameSet = new DavPropertyNameSet();
+        davPropertyNameSet.add(DavPropertyName.create("current-user-principal", Namespace.getNamespace("DAV:")));
+        davPropertyNameSet.add(DavPropertyName.create("principal-URL", Namespace.getNamespace("DAV:")));
+        davPropertyNameSet.add(DavPropertyName.create("resourcetype", Namespace.getNamespace("DAV:")));
+        PropFindMethod method = new PropFindMethod("/", davPropertyNameSet, 0);
+        httpClient.executeMethod(method);
+        assertEquals(HttpStatus.SC_MULTI_STATUS, method.getStatusCode());
+        method.getResponseBodyAsMultiStatus();
+    }
+
+    public void testPropfindAddressBook() throws IOException, DavException {
+        DavPropertyNameSet davPropertyNameSet = new DavPropertyNameSet();
+        //davPropertyNameSet.add(DavPropertyName.create("getctag", Namespace.getNamespace("http://calendarserver.org/ns/")));
+        davPropertyNameSet.add(DavPropertyName.create("getetag", Namespace.getNamespace("DAV:")));
+        PropFindMethod method = new PropFindMethod("/users/" + session.getEmail()+"/addressbook/", davPropertyNameSet, 1);
+        httpClient.getParams().setParameter(HttpMethodParams.USER_AGENT, "Address%20Book/883 CFNetwork/454.12.4 Darwin/10.8.0 (i386) (MacBookPro3%2C1)");
+        httpClient.executeMethod(method);
+        assertEquals(HttpStatus.SC_MULTI_STATUS, method.getStatusCode());
+        method.getResponseBodyAsMultiStatus();
+    }
+
+}
diff --git a/src/test/davmail/exchange/AbstractExchangeSessionTestCase.java b/src/test/davmail/exchange/AbstractExchangeSessionTestCase.java
new file mode 100644
index 0000000..9c765f6
--- /dev/null
+++ b/src/test/davmail/exchange/AbstractExchangeSessionTestCase.java
@@ -0,0 +1,50 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange;
+
+import davmail.AbstractDavMailTestCase;
+import davmail.Settings;
+import davmail.exchange.dav.DavExchangeSession;
+import davmail.exchange.ews.EwsExchangeSession;
+
+import java.io.IOException;
+
+/**
+ * Exchange session test case.
+ * Open a session to default DavMail server as found in user davmail.properties,
+ * except if url is not null
+ */
+public class AbstractExchangeSessionTestCase extends AbstractDavMailTestCase {
+
+    @Override
+    public void setUp() throws IOException {
+        super.setUp();
+        if (session == null) {
+            // open session, get username and password from davmail.properties
+            // Note: those properties should *not* exist in normal production mode,
+            // they are not used by DavMail, just by this test case
+            if (Settings.getBooleanProperty("davmail.enableEws")) {
+                session = new EwsExchangeSession(Settings.getProperty("davmail.url"), Settings.getProperty("davmail.username"), Settings.getProperty("davmail.password"));
+            } else {
+                session = new DavExchangeSession(Settings.getProperty("davmail.url"), Settings.getProperty("davmail.username"), Settings.getProperty("davmail.password"));
+            }
+        }
+    }
+
+}
diff --git a/src/test/davmail/exchange/TestDoubleDotInputStream.java b/src/test/davmail/exchange/TestDoubleDotInputStream.java
new file mode 100644
index 0000000..04025a1
--- /dev/null
+++ b/src/test/davmail/exchange/TestDoubleDotInputStream.java
@@ -0,0 +1,107 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * Test double dot input stream.
+ */
+public class TestDoubleDotInputStream extends TestCase {
+    static final String END_OF_STREAM = "\r\n.\r\n";
+
+    protected String doubleDotRead(String value) throws IOException {
+        DoubleDotInputStream doubleDotInputStream = new DoubleDotInputStream(new ByteArrayInputStream(value.getBytes()));
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        int b;
+        while ((b = doubleDotInputStream.read()) != -1) {
+            baos.write(b);
+        }
+        return new String(baos.toByteArray());
+    }
+    
+    protected String doubleDotWrite(String value) throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        DoubleDotOutputStream doubleDotOutputStream = new DoubleDotOutputStream(baos);
+        doubleDotOutputStream.write(value.getBytes());
+        doubleDotOutputStream.close();
+        return new String(baos.toByteArray());
+    }
+
+    public void testSimple() throws IOException {
+        String value = "simple test";
+        assertEquals(value, doubleDotRead(value + END_OF_STREAM));
+    }
+
+    public void testNoEof() throws IOException {
+        String value = "simple test";
+        assertEquals(value, doubleDotRead(value));
+    }
+
+    public void testMultiLine() throws IOException {
+        String value = "simple test\r\nsecond line";
+        assertEquals(value, doubleDotRead(value+END_OF_STREAM));
+    }
+
+    public void testDoubleDot() throws IOException {
+        String value = "simple test\r\n..\r\nsecond line";
+        assertEquals(value.replaceAll("\\.\\.", "."), doubleDotRead(value+END_OF_STREAM));
+    }
+
+    public void testDoubleDotEnd() throws IOException {
+        String value = "simple test\r\n..";
+        assertEquals(value.replaceAll("\\.\\.", "."), doubleDotRead(value+END_OF_STREAM));
+        assertEquals("..", doubleDotRead(".."+END_OF_STREAM));
+    }
+
+    public void testWriteCRLF() throws IOException {
+        String value = "simple test\r\n.\r\nsecond line";
+        assertEquals(value.replaceAll("\\.", "..")+END_OF_STREAM, doubleDotWrite(value));
+    }
+
+    public void testEndsWithCRLF() throws IOException {
+        String value = "simple test\r\n";
+        assertEquals("simple test"+END_OF_STREAM, doubleDotWrite(value));
+    }
+
+    public void testEndsWithLF() throws IOException { 
+        String value = "simple test\n";
+        assertEquals("simple test\n"+END_OF_STREAM, doubleDotWrite(value));
+    }
+
+    public void testWriteOSXCR() throws IOException {
+        String value = "simple test\r.\rsecond line";
+        assertEquals(value.replaceAll("\\.", "..")+END_OF_STREAM, doubleDotWrite(value));
+    }
+
+    public void testWriteUnixLF() throws IOException {
+        String value = "simple test\n.\nsecond line";
+        assertEquals(value.replaceAll("\\.", "..")+END_OF_STREAM, doubleDotWrite(value));
+    }
+
+    public void testAnotherTest() throws IOException {
+        String value = "foo\r\n..bar";
+        assertEquals(value.replaceAll("\\.\\.", "."), doubleDotRead(value));
+    }
+
+}
diff --git a/src/test/davmail/exchange/TestExchangeAdapter.java b/src/test/davmail/exchange/TestExchangeAdapter.java
new file mode 100644
index 0000000..478d096
--- /dev/null
+++ b/src/test/davmail/exchange/TestExchangeAdapter.java
@@ -0,0 +1,122 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange;
+
+import davmail.Settings;
+import davmail.exchange.dav.DavExchangeSession;
+import davmail.exchange.ews.EwsExchangeSession;
+import davmail.http.DavGatewaySSLProtocolSocketFactory;
+import junit.framework.TestCase;
+import org.apache.log4j.Level;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Test Exchange adapter methods.
+ */
+public class TestExchangeAdapter extends TestCase {
+    ExchangeSession davSession;
+    ExchangeSession ewsSession;
+
+    @Override
+    public void setUp() throws IOException {
+        if (davSession == null) {
+            Settings.setConfigFilePath("davmail.properties");
+            Settings.load();
+            DavGatewaySSLProtocolSocketFactory.register();
+            davSession = new DavExchangeSession(Settings.getProperty("davmail.url"),
+                    Settings.getProperty("davmail.username"), Settings.getProperty("davmail.password"));
+            ewsSession = new EwsExchangeSession(Settings.getProperty("davmail.url"),
+                    Settings.getProperty("davmail.username"), Settings.getProperty("davmail.password"));
+            Settings.setLoggingLevel("httpclient.wire", Level.INFO);
+        }
+    }
+
+    public void assertEquals(ExchangeSession.Folder davFolder, ExchangeSession.Folder ewsFolder) {
+        assertNotNull(ewsFolder);
+        assertEquals(davFolder.folderPath, ewsFolder.folderPath);
+        assertEquals(davFolder.folderClass, ewsFolder.folderClass);
+        assertEquals(davFolder.hasChildren, ewsFolder.hasChildren);
+        assertEquals(davFolder.unreadCount, ewsFolder.unreadCount);
+
+        assertEquals(davFolder.isCalendar(), false);
+        assertEquals(ewsFolder.isCalendar(), false);
+
+        assertEquals(davFolder.isContact(), false);
+        assertEquals(ewsFolder.isContact(), false);
+
+        assertEquals(davFolder.noInferiors, false);
+        assertEquals(ewsFolder.noInferiors, false);
+
+        assertEquals(davFolder.getFlags(), ewsFolder.getFlags());
+        assertEquals(davFolder.etag.substring(0, ewsFolder.ctag.length()-1)+ 'Z', ewsFolder.etag);
+
+
+        assertNotNull(davFolder.ctag);
+        assertNotNull(ewsFolder.ctag);
+        // dav and ews ctags are still different: dav contentag has milliseconds info
+        assertEquals(davFolder.ctag.substring(0, ewsFolder.ctag.length()-1)+ 'Z', ewsFolder.ctag);
+        
+    }
+
+    public void testGetInbox() throws IOException {
+        ExchangeSession.Folder davFolder = davSession.getFolder("INBOX");
+        ExchangeSession.Folder ewsFolder = ewsSession.getFolder("INBOX");
+        assertEquals(davFolder, ewsFolder);
+    }
+
+    public void testGetSubFolder() throws IOException {
+        ExchangeSession.Folder ewsFolder = ewsSession.getFolder("INBOX/bbbb");
+    }
+
+    public void testFindFolder() throws IOException {
+        List<ExchangeSession.Folder> davFolders = davSession.getSubFolders("", false);
+        Settings.setLoggingLevel("httpclient.wire", Level.DEBUG);
+        List<ExchangeSession.Folder> ewsFolders = ewsSession.getSubFolders("", false);
+        assertEquals(davFolders.size(), ewsFolders.size());
+    }
+
+    public void testFindPublicFolder() throws IOException {
+        List<ExchangeSession.Folder> davFolders = davSession.getSubFolders("/public", false);
+        Settings.setLoggingLevel("httpclient.wire", Level.DEBUG);
+        List<ExchangeSession.Folder> ewsFolders = ewsSession.getSubFolders("/public", false);
+        assertEquals(davFolders.size(), ewsFolders.size());
+    }
+
+    public void testFindFolders() throws IOException {
+        List<ExchangeSession.Folder> davFolders = davSession.getSubFolders("/public", null, true);
+        System.out.println(davFolders);
+    }
+
+    public void testSearchMessages() throws IOException {
+        ExchangeSession.MessageList messages = davSession.searchMessages("INBOX");
+        for (ExchangeSession.Message message:messages) {
+            System.out.println(message);
+        }
+    }
+
+    public void testSearchEvents() throws IOException {
+        List<ExchangeSession.Event> events = davSession.getAllEvents("calendar");
+        for (ExchangeSession.Event event:events) {
+            System.out.println(event);
+        }
+    }
+
+}
diff --git a/src/test/davmail/exchange/TestExchangeSession.java b/src/test/davmail/exchange/TestExchangeSession.java
new file mode 100644
index 0000000..2306830
--- /dev/null
+++ b/src/test/davmail/exchange/TestExchangeSession.java
@@ -0,0 +1,41 @@
+package davmail.exchange;
+
+import davmail.Settings;
+import davmail.http.DavGatewaySSLProtocolSocketFactory;
+
+/**
+ *  Test Exchange session
+ */
+public class TestExchangeSession {
+
+    private TestExchangeSession() {
+    }
+
+    /**
+     * main method
+     * @param argv command line arg
+     */
+    public static void main(String[] argv) {
+        // register custom SSL Socket factory
+        int currentArg = 0;
+        Settings.setConfigFilePath(argv[currentArg++]);
+        Settings.load();
+
+        DavGatewaySSLProtocolSocketFactory.register();
+
+        ExchangeSession session;
+        // test auth
+        try {
+            ExchangeSessionFactory.checkConfig();
+            session = ExchangeSessionFactory.getInstance(argv[currentArg++], argv[currentArg]);
+
+            ExchangeSession.Folder folder = session.getFolder("INBOX");
+            folder.loadMessages();
+
+            //session.purgeOldestTrashAndSentMessages();
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/src/test/davmail/exchange/TestExchangeSessionCalendar.java b/src/test/davmail/exchange/TestExchangeSessionCalendar.java
new file mode 100644
index 0000000..f0ddf02
--- /dev/null
+++ b/src/test/davmail/exchange/TestExchangeSessionCalendar.java
@@ -0,0 +1,409 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange;
+
+import davmail.Settings;
+import davmail.exchange.ews.EwsExchangeSession;
+import davmail.exchange.ews.FolderQueryTraversal;
+import org.apache.log4j.Level;
+
+import javax.mail.MessagingException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * Test Exchange session calendar features .
+ */
+ at SuppressWarnings({"UseOfSystemOutOrSystemErr"})
+public class TestExchangeSessionCalendar extends AbstractExchangeSessionTestCase {
+
+    public void testGetVtimezone() {
+        VObject timezone = session.getVTimezone();
+        assertNotNull(timezone);
+        assertNotNull(timezone.getPropertyValue("TZID"));
+    }
+
+    public void testDumpVtimezones() throws IOException {
+        Properties properties = new Properties() {
+            @Override
+            public synchronized Enumeration<Object> keys() {
+                Enumeration keysEnumeration = super.keys();
+                TreeSet<String> sortedKeySet = new TreeSet<String>();
+                while (keysEnumeration.hasMoreElements()) {
+                    sortedKeySet.add((String) keysEnumeration.nextElement());
+                }
+                final Iterator<String> sortedKeysIterator = sortedKeySet.iterator();
+                return new Enumeration<Object>() {
+
+                    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));
+            VObject timezone = session.getVTimezone();
+            if (timezone.getProperty("TZID") != null) {
+                properties.put(timezone.getPropertyValue("TZID").replaceAll("\\\\", ""), String.valueOf(i));
+                System.out.println(timezone.getPropertyValue("TZID") + '=' + i);
+            }
+            session.vTimezone = null;
+        }
+        FileOutputStream fileOutputStream = null;
+        try {
+            fileOutputStream = new FileOutputStream("timezoneids.properties");
+            properties.store(fileOutputStream, "Timezone ids");
+        } finally {
+            if (fileOutputStream != null) {
+                try {
+                    fileOutputStream.close();
+                } catch (IOException e) {
+                    // ignore
+                }
+            }
+        }
+    }
+
+    public void testSearchCalendar() throws IOException {
+        List<ExchangeSession.Event> events = null;
+        try {
+            events = session.getAllEvents("/users/" + session.getEmail() + "/calendar");
+            for (ExchangeSession.Event event : events) {
+                System.out.println(event.getBody());
+            }
+        } catch (IOException e) {
+            System.out.println(e.getMessage());
+            throw e;
+        }
+    }
+
+    public void testReportCalendar() throws IOException {
+        List<ExchangeSession.Event> events = null;
+        try {
+            events = session.getAllEvents("/users/" + session.getEmail() + "/calendar");
+            for (ExchangeSession.Event event : events) {
+                System.out.println(event.subject);
+                ExchangeSession.Item item = session.getItem("/users/" + session.getEmail() + "/calendar", event.itemName);
+                System.out.println(item.getBody());
+            }
+        } catch (IOException e) {
+            System.out.println(e.getMessage());
+            throw e;
+        }
+    }
+
+    public void testGetFreeBusyData() throws IOException, MessagingException {
+        Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+        cal.set(Calendar.MONTH, 7);
+        cal.set(Calendar.DAY_OF_MONTH, 1);
+        cal.set(Calendar.HOUR_OF_DAY, 0);
+        cal.set(Calendar.MINUTE, 0);
+        cal.set(Calendar.SECOND, 0);
+        Date startDate = cal.getTime();
+        cal.set(Calendar.HOUR_OF_DAY, 23);
+        cal.set(Calendar.MINUTE, 59);
+        cal.set(Calendar.SECOND, 59);
+        Date endDate = cal.getTime();
+        SimpleDateFormat formatter = ExchangeSession.getExchangeZuluDateFormat();
+        // personal fbdata
+        String fbdata = session.getFreeBusyData(session.getEmail(), formatter.format(startDate),
+                formatter.format(endDate), 60);
+        assertNotNull(fbdata);
+        // other user data
+        fbdata = session.getFreeBusyData(Settings.getProperty("davmail.to"), formatter.format(startDate),
+                formatter.format(endDate), 60);
+        assertNotNull(fbdata);
+        // unknown user data
+        fbdata = session.getFreeBusyData("unknown at company.org", formatter.format(startDate),
+                formatter.format(endDate), 60);
+        assertNull(fbdata);
+    }
+
+    public void testCreateEvent() throws IOException {
+        String itemBody = "BEGIN:VCALENDAR\n" +
+                "PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\n" +
+                "VERSION:2.0\n" +
+                "BEGIN:VTIMEZONE\n" +
+                "TZID:Pacific Time (US & Canada)\\; Tijuana\n" +
+                "BEGIN:STANDARD\n" +
+                "DTSTART:16010101T020000\n" +
+                "TZOFFSETFROM:-0700\n" +
+                "TZOFFSETTO:-0800\n" +
+                "RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=11;BYDAY=1SU\n" +
+                "END:STANDARD\n" +
+                "BEGIN:DAYLIGHT\n" +
+                "DTSTART:16010101T020000\n" +
+                "TZOFFSETFROM:-0800\n" +
+                "TZOFFSETTO:-0700\n" +
+                "RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=2SU\n" +
+                "END:DAYLIGHT\n" +
+                "END:VTIMEZONE\n" +
+                "BEGIN:VEVENT\n" +
+                "CREATED:20100829T204658Z\n" +
+                "LAST-MODIFIED:20100829T204829Z\n" +
+                "DTSTAMP:20100829T204829Z\n" +
+                "UID:701b9d8f-ab64-4a7c-a75d-251cc8687cd9\n" +
+                "SUMMARY:testzz\n" +
+                "DTSTART;TZID=\"Pacific Time (US & Canada); Tijuana\":20100830T230000\n" +
+                "DTEND;TZID=\"Pacific Time (US & Canada); Tijuana\":20100831T000000\n" +
+                "X-MICROSOFT-CDO-ALLDAYEVENT:FALSE\n" +
+                "X-MICROSOFT-CDO-BUSYSTATUS:BUSY\n" +
+                "TRANSP:OPAQUE\n" +
+                "X-MOZ-GENERATION:1\n" +
+                "END:VEVENT\n" +
+                "END:VCALENDAR";
+        String itemName = UUID.randomUUID().toString() + ".EML";
+        session.createOrUpdateItem("calendar", itemName, itemBody, null, null);
+    }
+
+    public void testGetEvent() throws IOException {
+        ExchangeSession.Item item = session.getItem("calendar", "19083675-f8ce-4d81-8ac8-096fa0bd0e13.EML");
+        item.getBody();
+    }
+
+    public void testGetInbox() throws IOException {
+        List<ExchangeSession.Event> items = session.getEventMessages("INBOX");
+        for (ExchangeSession.Item item : items) {
+            System.out.println(item.getBody());
+        }
+    }
+
+    public void testSearchEventCount() throws IOException {
+        Settings.setLoggingLevel("davmail", Level.WARN);
+        System.out.println("Item count: " + session.searchEvents("calendar", null).size());
+        System.out.println("InstanceType null: " + session.searchEvents("calendar", session.isNull("instancetype")).size());
+        System.out.println("InstanceType not null: " + session.searchEvents("calendar", session.not(session.isNull("instancetype"))).size());
+        System.out.println("InstanceType 0: " + session.searchEvents("calendar", session.isEqualTo("instancetype", 0)).size());
+        System.out.println("InstanceType 1: " + session.searchEvents("calendar", session.isEqualTo("instancetype", 1)).size());
+        System.out.println("InstanceType 2: " + session.searchEvents("calendar", session.isEqualTo("instancetype", 2)).size());
+        System.out.println("InstanceType 3: " + session.searchEvents("calendar", session.isEqualTo("instancetype", 3)).size());
+
+        if (session instanceof EwsExchangeSession) {
+            System.out.println("Recurring: " + session.searchEvents("calendar", session.isTrue("isrecurring")).size());
+            System.out.println("Non recurring: " + session.searchEvents("calendar", session.isFalse("isrecurring")).size());
+            System.out.println("Null recurring: " + session.searchEvents("calendar", session.isNull("isrecurring")).size());
+        }
+
+    }
+
+
+    public void testCreateEventTZ() throws IOException {
+        String itemBody = "BEGIN:VCALENDAR\n" +
+                "PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\n" +
+                "VERSION:2.0\n" +
+                "BEGIN:VTIMEZONE\n" +
+                "TZID:America/Bogota\n" +
+                "BEGIN:DAYLIGHT\n" +
+                "TZOFFSETFROM:-0500\n" +
+                "DTSTART:19920503T000000\n" +
+                "TZNAME:COT\n" +
+                "TZOFFSETTO:-0400\n" +
+                "RDATE:19920503T000000\n" +
+                "END:DAYLIGHT\n" +
+                "BEGIN:STANDARD\n" +
+                "TZOFFSETFROM:-0400\n" +
+                "DTSTART:19930404T000000\n" +
+                "TZNAME:COT\n" +
+                "TZOFFSETTO:-0500\n" +
+                "RDATE:19930404T000000\n" +
+                "END:STANDARD\n" +
+                "END:VTIMEZONE\n" +
+                "BEGIN:VEVENT\n" +
+                "CREATED:20110804T203742Z\n" +
+                "UID:1E17151D-92DA-4D2E-9747-60B489DE56F4\n" +
+                "DTEND;TZID=America/Bogota:20110805T090000\n" +
+                "TRANSP:OPAQUE\n" +
+                "SUMMARY:New Event 2\n" +
+                "DTSTART;TZID=America/Bogota:20110805T080000\n" +
+                "DTSTAMP:20110804T203742Z\n" +
+                "SEQUENCE:0\n" +
+                "END:VEVENT\n" +
+                "END:VCALENDAR";
+        String itemName = UUID.randomUUID().toString() + ".EML";
+        session.createOrUpdateItem("calendar", itemName, itemBody, null, null);
+    }
+    
+    public void testCreateEventBrokenTZ() throws IOException {
+        String itemBody = "BEGIN:VCALENDAR\n" +
+                "PRODID:-//K Desktop Environment//NONSGML libkcal 4.3//EN\n" +
+                "VERSION:2.0\n" +
+                "METHOD:PUBLISH\n" +
+                "BEGIN:VTIMEZONE\n" +
+                "TZID:Europe/Amsterdam\n" +
+                "BEGIN:DAYLIGHT\n" +
+                "TZNAME:NST\n" +
+                "TZOFFSETFROM:+001932\n" +
+                "TZOFFSETTO:+011932\n" +
+                "DTSTART:19160501T234028\n" +
+                "RDATE;VALUE=DATE-TIME:19160501T234028\n" +
+                "RDATE;VALUE=DATE-TIME:19170417T014028\n" +
+                "RDATE;VALUE=DATE-TIME:19180402T014028\n" +
+                "RDATE;VALUE=DATE-TIME:19190408T014028\n" +
+                "RDATE;VALUE=DATE-TIME:19200406T014028\n" +
+                "RDATE;VALUE=DATE-TIME:19210405T014028\n" +
+                "RDATE;VALUE=DATE-TIME:19220327T014028\n" +
+                "RDATE;VALUE=DATE-TIME:19230602T014028\n" +
+                "RDATE;VALUE=DATE-TIME:19240331T014028\n" +
+                "RDATE;VALUE=DATE-TIME:19250606T014028\n" +
+                "RDATE;VALUE=DATE-TIME:19260516T014028\n" +
+                "RDATE;VALUE=DATE-TIME:19270516T014028\n" +
+                "RDATE;VALUE=DATE-TIME:19280516T014028\n" +
+                "RDATE;VALUE=DATE-TIME:19290516T014028\n" +
+                "RDATE;VALUE=DATE-TIME:19300516T014028\n" +
+                "RDATE;VALUE=DATE-TIME:19310516T014028\n" +
+                "RDATE;VALUE=DATE-TIME:19320523T014028\n" +
+                "RDATE;VALUE=DATE-TIME:19330516T014028\n" +
+                "RDATE;VALUE=DATE-TIME:19340516T014028\n" +
+                "RDATE;VALUE=DATE-TIME:19350516T014028\n" +
+                "RDATE;VALUE=DATE-TIME:19360516T014028\n" +
+                "RDATE;VALUE=DATE-TIME:19370523T014028\n" +
+                "END:DAYLIGHT\n" +
+                "BEGIN:STANDARD\n" +
+                "TZNAME:AMT\n" +
+                "TZOFFSETFROM:+011932\n" +
+                "TZOFFSETTO:+001932\n" +
+                "DTSTART:19161001T224028\n" +
+                "RDATE;VALUE=DATE-TIME:19161001T224028\n" +
+                "RDATE;VALUE=DATE-TIME:19170918T024028\n" +
+                "RDATE;VALUE=DATE-TIME:19181001T024028\n" +
+                "RDATE;VALUE=DATE-TIME:19190930T024028\n" +
+                "RDATE;VALUE=DATE-TIME:19200928T024028\n" +
+                "RDATE;VALUE=DATE-TIME:19210927T024028\n" +
+                "RDATE;VALUE=DATE-TIME:19221009T024028\n" +
+                "RDATE;VALUE=DATE-TIME:19231008T024028\n" +
+                "RDATE;VALUE=DATE-TIME:19241006T024028\n" +
+                "RDATE;VALUE=DATE-TIME:19251005T024028\n" +
+                "RDATE;VALUE=DATE-TIME:19261004T024028\n" +
+                "RDATE;VALUE=DATE-TIME:19271003T024028\n" +
+                "RDATE;VALUE=DATE-TIME:19281008T024028\n" +
+                "RDATE;VALUE=DATE-TIME:19291007T024028\n" +
+                "RDATE;VALUE=DATE-TIME:19301006T024028\n" +
+                "RDATE;VALUE=DATE-TIME:19311005T024028\n" +
+                "RDATE;VALUE=DATE-TIME:19321003T024028\n" +
+                "RDATE;VALUE=DATE-TIME:19331009T024028\n" +
+                "RDATE;VALUE=DATE-TIME:19341008T024028\n" +
+                "RDATE;VALUE=DATE-TIME:19351007T024028\n" +
+                "RDATE;VALUE=DATE-TIME:19361005T024028\n" +
+                "END:STANDARD\n" +
+                "BEGIN:DAYLIGHT\n" +
+                "TZNAME:NEST\n" +
+                "TZOFFSETFROM:+011932\n" +
+                "TZOFFSETTO:+0120\n" +
+                "DTSTART:19370701T224028\n" +
+                "RDATE;VALUE=DATE-TIME:19370701T224028\n" +
+                "END:DAYLIGHT\n" +
+                "BEGIN:STANDARD\n" +
+                "TZNAME:NET\n" +
+                "TZOFFSETFROM:+0120\n" +
+                "TZOFFSETTO:+0020\n" +
+                "DTSTART:19371004T024028\n" +
+                "RDATE;VALUE=DATE-TIME:19371004T024028\n" +
+                "RDATE;VALUE=DATE-TIME:19381003T024000\n" +
+                "RDATE;VALUE=DATE-TIME:19391009T024000\n" +
+                "END:STANDARD\n" +
+                "BEGIN:DAYLIGHT\n" +
+                "TZNAME:NEST\n" +
+                "TZOFFSETFROM:+0020\n" +
+                "TZOFFSETTO:+0120\n" +
+                "DTSTART:19380516T014000\n" +
+                "RDATE;VALUE=DATE-TIME:19380516T014000\n" +
+                "RDATE;VALUE=DATE-TIME:19390516T014000\n" +
+                "END:DAYLIGHT\n" +
+                "BEGIN:DAYLIGHT\n" +
+                "TZNAME:CEST\n" +
+                "TZOFFSETFROM:+0020\n" +
+                "TZOFFSETTO:+0200\n" +
+                "DTSTART:19400516T234000\n" +
+                "RDATE;VALUE=DATE-TIME:19400516T234000\n" +
+                "END:DAYLIGHT\n" +
+                "BEGIN:STANDARD\n" +
+                "TZNAME:CET\n" +
+                "TZOFFSETFROM:+0200\n" +
+                "TZOFFSETTO:+0100\n" +
+                "DTSTART:19790930T030000\n" +
+                "RRULE:FREQ=YEARLY;COUNT=17;BYDAY=-1SU;BYMONTH=9\n" +
+                "END:STANDARD\n" +
+                "BEGIN:STANDARD\n" +
+                "TZNAME:CET\n" +
+                "TZOFFSETFROM:+0200\n" +
+                "TZOFFSETTO:+0100\n" +
+                "DTSTART:19961027T030000\n" +
+                "RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10\n" +
+                "END:STANDARD\n" +
+                "BEGIN:STANDARD\n" +
+                "TZNAME:CET\n" +
+                "TZOFFSETFROM:+0200\n" +
+                "TZOFFSETTO:+0100\n" +
+                "DTSTART:19421103T024000\n" +
+                "RDATE;VALUE=DATE-TIME:19421103T024000\n" +
+                "RDATE;VALUE=DATE-TIME:19431004T020000\n" +
+                "RDATE;VALUE=DATE-TIME:19441002T020000\n" +
+                "RDATE;VALUE=DATE-TIME:19450916T020000\n" +
+                "RDATE;VALUE=DATE-TIME:19770925T030000\n" +
+                "RDATE;VALUE=DATE-TIME:19781001T030000\n" +
+                "END:STANDARD\n" +
+                "BEGIN:DAYLIGHT\n" +
+                "TZNAME:CEST\n" +
+                "TZOFFSETFROM:+0100\n" +
+                "TZOFFSETTO:+0200\n" +
+                "DTSTART:19810329T020000\n" +
+                "RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3\n" +
+                "END:DAYLIGHT\n" +
+                "BEGIN:DAYLIGHT\n" +
+                "TZNAME:CEST\n" +
+                "TZOFFSETFROM:+0100\n" +
+                "TZOFFSETTO:+0200\n" +
+                "DTSTART:19430329T010000\n" +
+                "RDATE;VALUE=DATE-TIME:19430329T010000\n" +
+                "RDATE;VALUE=DATE-TIME:19440403T010000\n" +
+                "RDATE;VALUE=DATE-TIME:19450402T010000\n" +
+                "RDATE;VALUE=DATE-TIME:19770403T020000\n" +
+                "RDATE;VALUE=DATE-TIME:19780402T020000\n" +
+                "RDATE;VALUE=DATE-TIME:19790401T020000\n" +
+                "RDATE;VALUE=DATE-TIME:19800406T020000\n" +
+                "END:DAYLIGHT\n" +
+                "END:VTIMEZONE\n" +
+                "BEGIN:VEVENT\n" +
+                "DTSTAMP:20111022T175835Z\n" +
+                "CREATED:20111022T175832Z\n" +
+                "UID:libkcal-797112054.882\n" +
+                "LAST-MODIFIED:20111022T175832Z\n" +
+                "SUMMARY:Test Event 000\n" +
+                "DTSTART;TZID=\"Europe/Amsterdam\":20111027T120000\n" +
+                "DTEND;TZID=\"Europe/Amsterdam\":20111027T174500\n" +
+                "TRANSP:OPAQUE\n" +
+                "X-MICROSOFT-CDO-REPLYTIME:20111022T175835Z\n" +
+                "X-MICROSOFT-CDO-ALLDAYEVENT:FALSE\n" +
+                "X-MICROSOFT-CDO-BUSYSTATUS:BUSY\n" +
+                "END:VEVENT\n" +
+                "END:VCALENDAR";
+        String itemName = UUID.randomUUID().toString() + ".EML";
+        session.createOrUpdateItem("calendar", itemName, itemBody, null, null);
+    }
+
+}
+
diff --git a/src/test/davmail/exchange/TestExchangeSessionContact.java b/src/test/davmail/exchange/TestExchangeSessionContact.java
new file mode 100644
index 0000000..ea615d5
--- /dev/null
+++ b/src/test/davmail/exchange/TestExchangeSessionContact.java
@@ -0,0 +1,496 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange;
+
+import davmail.Settings;
+import davmail.util.IOUtil;
+import org.apache.commons.codec.binary.Base64;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Test ExchangeSession contact features.
+ */
+ at SuppressWarnings({"UseOfSystemOutOrSystemErr"})
+public class TestExchangeSessionContact extends AbstractExchangeSessionTestCase {
+    static String itemName;
+
+    protected ExchangeSession.Contact getCurrentContact() throws IOException {
+        if (itemName != null) {
+            return (ExchangeSession.Contact) session.getItem("testcontactfolder", itemName);
+        } else {
+            List<ExchangeSession.Contact> contacts = session.searchContacts("testcontactfolder", ExchangeSession.CONTACT_ATTRIBUTES, null, 0);
+            itemName = contacts.get(0).itemName;
+            return contacts.get(0);
+        }
+    }
+
+    public void testCreateFolder() throws IOException {
+        // recreate empty folder
+        session.deleteFolder("testcontactfolder");
+        session.createContactFolder("testcontactfolder", null);
+    }
+
+    public void testCreateContact() throws IOException {
+        itemName = UUID.randomUUID().toString() + ".vcf";
+        VCardWriter vCardWriter = new VCardWriter();
+        vCardWriter.startCard();
+        vCardWriter.appendProperty("N", "sn", "givenName", "middlename", "personaltitle", "namesuffix");
+        vCardWriter.appendProperty("FN", "common name");
+        vCardWriter.appendProperty("NICKNAME", "nickname");
+
+        vCardWriter.appendProperty("TEL;TYPE=cell", "mobile");
+        vCardWriter.appendProperty("TEL;TYPE=work", "telephoneNumber");
+        vCardWriter.appendProperty("TEL;TYPE=home,voice", "homePhone");
+        vCardWriter.appendProperty("TEL;TYPE=fax", "facsimiletelephonenumber");
+        vCardWriter.appendProperty("TEL;TYPE=pager", "pager");
+
+        vCardWriter.appendProperty("ADR;TYPE=home", "homepostofficebox", null, "homeStreet", "homeCity", "homeState", "homePostalCode", "homeCountry");
+        vCardWriter.appendProperty("ADR;TYPE=work", "postofficebox", "roomnumber", "street", "l", "st", "postalcode", "co");
+        vCardWriter.appendProperty("ADR;TYPE=other", "otherpostofficebox", null, "otherstreet", "othercity", "otherstate", "otherpostalcode", "othercountry");
+
+        vCardWriter.appendProperty("EMAIL;TYPE=work", "email1 at local.net");
+        vCardWriter.appendProperty("EMAIL;TYPE=home", "email2 at local.net");
+        vCardWriter.appendProperty("EMAIL;TYPE=other", "email3 at local.net");
+
+        vCardWriter.appendProperty("ORG", "o", "department");
+
+        vCardWriter.appendProperty("URL;TYPE=work", "http://local.net");
+        vCardWriter.appendProperty("TITLE", "title");
+        vCardWriter.appendProperty("NOTE", "description");
+
+        vCardWriter.appendProperty("CUSTOM1", "extensionattribute1");
+        vCardWriter.appendProperty("CUSTOM2", "extensionattribute2");
+        vCardWriter.appendProperty("CUSTOM3", "extensionattribute3");
+        vCardWriter.appendProperty("CUSTOM4", "extensionattribute4");
+
+        vCardWriter.appendProperty("ROLE", "profession");
+        vCardWriter.appendProperty("X-AIM", "im");
+        vCardWriter.appendProperty("BDAY", "2000-01-02T00:00:00Z");
+        vCardWriter.appendProperty("CATEGORIES", "keyword1,keyword2");
+
+        vCardWriter.appendProperty("FBURL", "http://fburl");
+
+        vCardWriter.appendProperty("X-ASSISTANT", "secretarycn");
+        vCardWriter.appendProperty("X-MANAGER", "manager");
+        vCardWriter.appendProperty("X-SPOUSE", "spousecn");
+
+        vCardWriter.appendProperty("CLASS", "PRIVATE");
+
+        // add photo
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        InputStream fileInputStream = new FileInputStream("src/data/anonymous.jpg");
+        IOUtil.write(fileInputStream, baos);
+        vCardWriter.appendProperty("PHOTO;ENCODING=b;TYPE=JPEG", new String(Base64.encodeBase64(baos.toByteArray())));
+
+        vCardWriter.endCard();
+
+        ExchangeSession.ItemResult result = session.createOrUpdateContact("testcontactfolder", itemName, vCardWriter.toString(), null, null);
+        assertEquals(201, result.status);
+
+    }
+
+    public void testGetContact() throws IOException {
+        ExchangeSession.Contact contact = (ExchangeSession.Contact) session.getItem("testcontactfolder", itemName);
+        assertEquals("common name", contact.get("cn"));
+        assertEquals("sn", contact.get("sn"));
+        assertEquals("givenName", contact.get("givenName"));
+        assertEquals("middlename", contact.get("middlename"));
+        assertEquals("personaltitle", contact.get("personaltitle"));
+        assertEquals("namesuffix", contact.get("namesuffix"));
+        assertNotNull("lastmodified");
+        assertEquals("nickname", contact.get("nickname"));
+
+        assertEquals("mobile", contact.get("mobile"));
+        assertEquals("telephoneNumber", contact.get("telephoneNumber"));
+        assertEquals("homePhone", contact.get("homePhone"));
+        assertEquals("facsimiletelephonenumber", contact.get("facsimiletelephonenumber"));
+        assertEquals("pager", contact.get("pager"));
+
+        assertEquals("homepostofficebox", contact.get("homepostofficebox"));
+        assertEquals("homeStreet", contact.get("homeStreet"));
+        assertEquals("homeCity", contact.get("homeCity"));
+        assertEquals("homeState", contact.get("homeState"));
+        assertEquals("homePostalCode", contact.get("homePostalCode"));
+        assertEquals("homeCountry", contact.get("homeCountry"));
+
+        assertEquals("postofficebox", contact.get("postofficebox"));
+        assertEquals("roomnumber", contact.get("roomnumber"));
+        assertEquals("street", contact.get("street"));
+        assertEquals("l", contact.get("l"));
+        assertEquals("st", contact.get("st"));
+        assertEquals("postalcode", contact.get("postalcode"));
+        assertEquals("co", contact.get("co"));
+
+        assertEquals("email1 at local.net", contact.get("smtpemail1"));
+        assertEquals("email2 at local.net", contact.get("smtpemail2"));
+        assertEquals("email3 at local.net", contact.get("smtpemail3"));
+
+        assertEquals("o", contact.get("o"));
+        assertEquals("department", contact.get("department"));
+
+        assertEquals("http://local.net", contact.get("businesshomepage"));
+        assertEquals("title", contact.get("title"));
+        assertEquals("description", contact.get("description"));
+
+        assertEquals("extensionattribute1", contact.get("extensionattribute1"));
+        assertEquals("extensionattribute2", contact.get("extensionattribute2"));
+        assertEquals("extensionattribute3", contact.get("extensionattribute3"));
+        assertEquals("extensionattribute4", contact.get("extensionattribute4"));
+
+        assertEquals("profession", contact.get("profession"));
+        assertEquals("im", contact.get("im"));
+        assertEquals("20000102T000000Z", contact.get("bday"));
+
+        assertEquals("otherpostofficebox", contact.get("otherpostofficebox"));
+        assertEquals("otherstreet", contact.get("otherstreet"));
+        assertEquals("othercity", contact.get("othercity"));
+        assertEquals("otherstate", contact.get("otherstate"));
+        assertEquals("otherpostalcode", contact.get("otherpostalcode"));
+        assertEquals("othercountry", contact.get("othercountry"));
+
+        assertEquals("secretarycn", contact.get("secretarycn"));
+        assertEquals("manager", contact.get("manager"));
+        assertEquals("spousecn", contact.get("spousecn"));
+        assertEquals("keyword1,keyword2", contact.get("keywords"));
+
+        assertEquals("true", contact.get("private"));
+
+        assertEquals("http://fburl", contact.get("fburl"));
+
+        assertEquals("true", contact.get("haspicture"));
+        if (!Settings.getBooleanProperty("davmail.enableEws") || "Exchange2010".equals(session.getServerVersion())) {
+            assertNotNull(session.getContactPhoto(contact));
+        }
+    }
+
+    public void testUpdateContact() throws IOException {
+        ExchangeSession.Contact contact = getCurrentContact();
+
+        VCardWriter vCardWriter = new VCardWriter();
+        vCardWriter.startCard();
+
+        vCardWriter.endCard();
+
+        ExchangeSession.ItemResult result = session.createOrUpdateContact("testcontactfolder", itemName, vCardWriter.toString(), contact.etag, null);
+        assertEquals(200, result.status);
+
+        contact = getCurrentContact();
+        assertNull(contact.get("cn"));
+        assertNull(contact.get("sn"));
+        assertNull(contact.get("givenName"));
+        assertNull(contact.get("middlename"));
+        assertNull(contact.get("personaltitle"));
+        assertNull(contact.get("namesuffix"));
+        assertNotNull("lastmodified");
+        assertNull(contact.get("nickname"));
+
+        assertNull(contact.get("mobile"));
+        assertNull(contact.get("telephoneNumber"));
+        assertNull(contact.get("homePhone"));
+        assertNull(contact.get("facsimiletelephonenumber"));
+        assertNull(contact.get("pager"));
+
+        assertNull(contact.get("homepostofficebox"));
+        assertNull(contact.get("homeStreet"));
+        assertNull(contact.get("homeCity"));
+        assertNull(contact.get("homeState"));
+        assertNull(contact.get("homePostalCode"));
+        assertNull(contact.get("homeCountry"));
+
+        assertNull(contact.get("postofficebox"));
+        assertNull(contact.get("roomnumber"));
+        assertNull(contact.get("street"));
+        assertNull(contact.get("l"));
+        assertNull(contact.get("st"));
+        assertNull(contact.get("postalcode"));
+        assertNull(contact.get("co"));
+
+        assertNull(contact.get("email1"));
+        assertNull(contact.get("email2"));
+        assertNull(contact.get("email3"));
+
+        assertNull(contact.get("o"));
+        assertNull(contact.get("department"));
+
+        assertNull(contact.get("businesshomepage"));
+        assertNull(contact.get("title"));
+        assertNull(contact.get("description"));
+
+        assertNull(contact.get("extensionattribute1"));
+        assertNull(contact.get("extensionattribute2"));
+        assertNull(contact.get("extensionattribute3"));
+        assertNull(contact.get("extensionattribute4"));
+
+        assertNull(contact.get("profession"));
+        assertNull(contact.get("im"));
+        assertNull(contact.get("bday"));
+
+        assertNull(contact.get("otherpostofficebox"));
+        assertNull(contact.get("otherstreet"));
+        assertNull(contact.get("othercity"));
+        assertNull(contact.get("otherstate"));
+        assertNull(contact.get("otherpostalcode"));
+        assertNull(contact.get("othercountry"));
+
+        assertNull(contact.get("secretarycn"));
+        assertNull(contact.get("manager"));
+        assertNull(contact.get("spousecn"));
+        assertNull(contact.get("keywords"));
+
+        assertNull(contact.get("private"));
+
+        assertTrue(contact.get("haspicture") == null || "false".equals(contact.get("haspicture")));
+
+        assertNull(session.getContactPhoto(contact));
+    }
+
+
+    public void testUpdateEmail() throws IOException {
+        ExchangeSession.Contact contact = getCurrentContact();
+
+        VCardWriter vCardWriter = new VCardWriter();
+        vCardWriter.startCard();
+        vCardWriter.appendProperty("EMAIL;TYPE=work", "email1.test at local.net");
+        vCardWriter.endCard();
+
+        ExchangeSession.ItemResult result = session.createOrUpdateContact("testcontactfolder", itemName, vCardWriter.toString(), contact.etag, null);
+        assertEquals(200, result.status);
+
+        contact = getCurrentContact();
+
+        assertEquals("email1.test at local.net", contact.get("smtpemail1"));
+
+    }
+
+    public void testUpperCaseParamName() throws IOException {
+        ExchangeSession.Contact contact = getCurrentContact();
+
+        VCardWriter vCardWriter = new VCardWriter();
+        vCardWriter.startCard();
+        vCardWriter.appendProperty("TEL;TYPE=CELL", "mobile");
+        vCardWriter.endCard();
+
+        ExchangeSession.ItemResult result = session.createOrUpdateContact("testcontactfolder", itemName, vCardWriter.toString(), contact.etag, null);
+        assertEquals(200, result.status);
+
+        contact = getCurrentContact();
+
+        assertEquals("mobile", contact.get("mobile"));
+
+    }
+
+    public void testMultipleTypesParamName() throws IOException {
+        ExchangeSession.Contact contact = (ExchangeSession.Contact) session.getItem("testcontactfolder", itemName);
+
+        VCardWriter vCardWriter = new VCardWriter();
+        vCardWriter.startCard();
+        vCardWriter.appendProperty("TEL;TYPE=CELL;TYPE=pref", "another mobile");
+        vCardWriter.endCard();
+
+        ExchangeSession.ItemResult result = session.createOrUpdateContact("testcontactfolder", itemName, vCardWriter.toString(), contact.etag, null);
+        assertEquals(200, result.status);
+
+        contact = (ExchangeSession.Contact) session.getItem("testcontactfolder", itemName);
+
+        assertEquals("another mobile", contact.get("mobile"));
+
+    }
+
+    public void testLowerCaseTypesParamName() throws IOException {
+        ExchangeSession.Contact contact = (ExchangeSession.Contact) session.getItem("testcontactfolder", itemName);
+
+        VCardWriter vCardWriter = new VCardWriter();
+        vCardWriter.startCard();
+        vCardWriter.appendProperty("TEL;type=HOME;type=pref", "5 68 99 3");
+        vCardWriter.endCard();
+
+        ExchangeSession.ItemResult result = session.createOrUpdateContact("testcontactfolder", itemName, vCardWriter.toString(), contact.etag, null);
+        assertEquals(200, result.status);
+
+        contact = (ExchangeSession.Contact) session.getItem("testcontactfolder", itemName);
+
+        assertEquals("5 68 99 3", contact.get("homePhone"));
+
+    }
+
+    public void testKeyPrefix() throws IOException {
+        ExchangeSession.Contact contact = (ExchangeSession.Contact) session.getItem("testcontactfolder", itemName);
+
+        VCardWriter vCardWriter = new VCardWriter();
+        vCardWriter.startCard();
+        vCardWriter.appendProperty("ITEM1.TEL;TYPE=CELL;TYPE=pref", "mobile with prefix");
+        vCardWriter.endCard();
+
+        ExchangeSession.ItemResult result = session.createOrUpdateContact("testcontactfolder", itemName, vCardWriter.toString(), contact.etag, null);
+        assertEquals(200, result.status);
+
+        contact = (ExchangeSession.Contact) session.getItem("testcontactfolder", itemName);
+
+        assertEquals("mobile with prefix", contact.get("mobile"));
+
+    }
+
+    public void testIphonePersonalHomePage() throws IOException {
+        ExchangeSession.Contact contact = (ExchangeSession.Contact) session.getItem("testcontactfolder", itemName);
+
+        VCardWriter vCardWriter = new VCardWriter();
+        vCardWriter.startCard();
+        vCardWriter.appendProperty("ITEM1.URL", "http://www.myhomepage.org");
+        vCardWriter.endCard();
+
+        ExchangeSession.ItemResult result = session.createOrUpdateContact("testcontactfolder", itemName, vCardWriter.toString(), contact.etag, null);
+        assertEquals(200, result.status);
+
+        contact = (ExchangeSession.Contact) session.getItem("testcontactfolder", itemName);
+
+        assertEquals("http://www.myhomepage.org", contact.get("personalHomePage"));
+
+    }
+
+
+    public void testIphoneEncodedCategories() throws IOException {
+        ExchangeSession.Contact contact = getCurrentContact();
+
+        VCardWriter vCardWriter = new VCardWriter();
+        vCardWriter.startCard();
+        vCardWriter.appendProperty("CATEGORIES", "rouge,vert");
+        vCardWriter.endCard();
+
+        ExchangeSession.ItemResult result = session.createOrUpdateContact("testcontactfolder", itemName, vCardWriter.toString(), contact.etag, null);
+        assertEquals(200, result.status);
+
+        contact = getCurrentContact();
+
+        assertEquals("rouge,vert", contact.get("keywords"));
+
+    }
+
+    public void testSemiColonInCompoundValue() throws IOException {
+        ExchangeSession.Contact contact = getCurrentContact();
+        String itemBody = "BEGIN:VCARD\n" +
+                "VERSION:3.0\n" +
+                "item1.ADR;type=WORK;type=pref:;;line1\\nline 2 \\; with semicolon;;;;\n" +
+                "END:VCARD";
+
+        ExchangeSession.ItemResult result = session.createOrUpdateContact("testcontactfolder", itemName, itemBody, contact.etag, null);
+        assertEquals(200, result.status);
+    }
+
+    public void testIphoneEncodedComma() throws IOException {
+        ExchangeSession.Contact contact = getCurrentContact();
+
+        VCardWriter vCardWriter = new VCardWriter();
+        vCardWriter.startCard();
+        vCardWriter.appendProperty("ITEM1.TEL;TYPE=CELL;TYPE=pref", "mobile\\, with comma");
+        vCardWriter.endCard();
+
+        ExchangeSession.ItemResult result = session.createOrUpdateContact("testcontactfolder", itemName, vCardWriter.toString(), contact.etag, null);
+        assertEquals(200, result.status);
+
+        contact = getCurrentContact();
+
+        assertEquals("mobile, with comma", contact.get("mobile"));
+
+    }
+
+    public void testAmpersAndValue() throws IOException {
+        ExchangeSession.Contact contact = getCurrentContact();
+
+        VCardWriter vCardWriter = new VCardWriter();
+        vCardWriter.startCard();
+        vCardWriter.appendProperty("FN", "common & name");
+        vCardWriter.endCard();
+
+        ExchangeSession.ItemResult result = session.createOrUpdateContact("testcontactfolder", itemName, vCardWriter.toString(), contact.etag, null);
+        assertEquals(200, result.status);
+
+        contact = getCurrentContact();
+
+        assertEquals("common & name", contact.get("cn"));
+
+    }
+
+    public void testDateValue() throws IOException {
+        ExchangeSession.Contact contact = getCurrentContact();
+
+        VCardWriter vCardWriter = new VCardWriter();
+        vCardWriter.startCard();
+        vCardWriter.appendProperty("BDAY", "2000-01-02");
+        vCardWriter.endCard();
+
+        ExchangeSession.ItemResult result = session.createOrUpdateContact("testcontactfolder", itemName, vCardWriter.toString(), contact.etag, null);
+        assertEquals(200, result.status);
+
+        contact = getCurrentContact();
+
+        assertEquals("20000102T000000Z", contact.get("bday"));
+        System.out.println(contact.getBody());
+    }
+
+    public void testAnniversary() throws IOException {
+        ExchangeSession.Contact contact = getCurrentContact();
+
+        VCardWriter vCardWriter = new VCardWriter();
+        vCardWriter.startCard();
+        vCardWriter.appendProperty("X-ANNIVERSARY", "2000-01-02");
+        vCardWriter.endCard();
+
+        ExchangeSession.ItemResult result = session.createOrUpdateContact("testcontactfolder", itemName, vCardWriter.toString(), contact.etag, null);
+        assertEquals(200, result.status);
+
+        contact = getCurrentContact();
+
+        assertEquals("20000102T000000Z", contact.get("anniversary"));
+        System.out.println(contact.getBody());
+    }
+
+    public void testSpecialUrlCharacters() throws IOException {
+        testCreateFolder();
+
+        VCardWriter vCardWriter = new VCardWriter();
+        vCardWriter.startCard();
+        vCardWriter.appendProperty("N", "sn", "givenName", "middlename", "personaltitle", "namesuffix");
+        vCardWriter.appendProperty("FN", "common name");
+        vCardWriter.endCard();
+
+        itemName = "test {<:&'>} \"accentué.vcf";
+
+        ExchangeSession.ItemResult result = session.createOrUpdateContact("testcontactfolder", itemName, vCardWriter.toString(), null, null);
+        assertEquals(201, result.status);
+
+        ExchangeSession.Contact contact = getCurrentContact();
+
+        assertEquals("common name", contact.get("cn"));
+    }
+
+    public void testPagingSearchContacts() throws IOException {
+        int maxCount = 0;
+        List<ExchangeSession.Contact> contacts = session.searchContacts(ExchangeSession.CONTACTS, ExchangeSession.CONTACT_ATTRIBUTES, null, maxCount);
+        int folderSize = contacts.size();
+        assertEquals(50, session.searchContacts(ExchangeSession.CONTACTS, ExchangeSession.CONTACT_ATTRIBUTES, null, 50).size());
+        assertEquals(folderSize, session.searchContacts(ExchangeSession.CONTACTS, ExchangeSession.CONTACT_ATTRIBUTES, null, folderSize+1).size());
+    }
+
+}
diff --git a/src/test/davmail/exchange/TestExchangeSessionEvent.java b/src/test/davmail/exchange/TestExchangeSessionEvent.java
new file mode 100644
index 0000000..7a38068
--- /dev/null
+++ b/src/test/davmail/exchange/TestExchangeSessionEvent.java
@@ -0,0 +1,388 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange;
+
+import davmail.BundleMessage;
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.io.StringReader;
+
+/**
+ * Test ExchangeSession event conversion.
+ */
+ at SuppressWarnings({"UseOfSystemOutOrSystemErr"})
+public class TestExchangeSessionEvent extends TestCase {
+    static String email = "user at company.com";
+    static VObject vTimeZone;
+
+    static {
+        try {
+            vTimeZone = new VObject(new ICSBufferedReader(new StringReader("BEGIN:VTIMEZONE\n" +
+                    "TZID:(GMT+01.00) Paris/Madrid/Brussels/Copenhagen\n" +
+                    "X-MICROSOFT-CDO-TZID:3\n" +
+                    "BEGIN:STANDARD\n" +
+                    "DTSTART:16010101T030000\n" +
+                    "TZOFFSETFROM:+0200\n" +
+                    "TZOFFSETTO:+0100\n" +
+                    "RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=10;BYDAY=-1SU\n" +
+                    "END:STANDARD\n" +
+                    "BEGIN:DAYLIGHT\n" +
+                    "DTSTART:16010101T020000\n" +
+                    "TZOFFSETFROM:+0100\n" +
+                    "TZOFFSETTO:+0200\n" +
+                    "RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=-1SU\n" +
+                    "END:DAYLIGHT\n" +
+                    "END:VTIMEZONE")));
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+
+    protected String fixICS(String icsBody, boolean fromServer) throws IOException {
+        VCalendar vCalendar = new VCalendar(icsBody, email, vTimeZone);
+        vCalendar.fixVCalendar(fromServer);
+        return vCalendar.toString();
+    }
+
+    public void testNoClass() throws IOException {
+        String itemBody = "BEGIN:VCALENDAR\n" +
+                "BEGIN:VEVENT\n" +
+                "END:VEVENT\n" +
+                "END:VCALENDAR";
+        String toServer = fixICS(itemBody, false);
+        String toClient = fixICS(itemBody, true);
+        System.out.println(toServer);
+        System.out.println(toClient);
+        assertTrue(toServer.indexOf("CLASS") < 0);
+        assertTrue(toClient.indexOf("CLASS") < 0);
+        assertTrue(toClient.indexOf("X-CALENDARSERVER-ACCESS") < 0);
+    }
+
+    public void testPublicClass() throws IOException {
+        String itemBody = "BEGIN:VCALENDAR\n" +
+                "BEGIN:VEVENT\n" +
+                "CLASS:PUBLIC\n" +
+                "END:VEVENT\n" +
+                "END:VCALENDAR";
+        String toServer = fixICS(itemBody, false);
+        String toClient = fixICS(itemBody, true);
+        System.out.println(toServer);
+        System.out.println(toClient);
+        assertTrue(toServer.indexOf("CLASS:PUBLIC") >= 0);
+        assertTrue(toClient.indexOf("CLASS:PUBLIC") >= 0);
+        assertTrue(toClient.indexOf("X-CALENDARSERVER-ACCESS:PUBLIC") >= 0);
+    }
+
+    public void testPrivateClass() throws IOException {
+        String itemBody = "BEGIN:VCALENDAR\n" +
+                "BEGIN:VEVENT\n" +
+                "CLASS:PRIVATE\n" +
+                "END:VEVENT\n" +
+                "END:VCALENDAR";
+        String toServer = fixICS(itemBody, false);
+        String toClient = fixICS(itemBody, true);
+        System.out.println(toServer);
+        System.out.println(toClient);
+        assertTrue(toServer.indexOf("CLASS:PRIVATE") >= 0);
+        assertTrue(toClient.indexOf("CLASS:PRIVATE") >= 0);
+        assertTrue(toClient.indexOf("X-CALENDARSERVER-ACCESS:CONFIDENTIAL") >= 0);
+    }
+
+    public void testConfidentialClass() throws IOException {
+        String itemBody = "BEGIN:VCALENDAR\n" +
+                "BEGIN:VEVENT\n" +
+                "CLASS:CONFIDENTIAL\n" +
+                "END:VEVENT\n" +
+                "END:VCALENDAR";
+        String toServer = fixICS(itemBody, false);
+        String toClient = fixICS(itemBody, true);
+        System.out.println(toServer);
+        System.out.println(toClient);
+        assertTrue(toServer.indexOf("CLASS:CONFIDENTIAL") >= 0);
+        assertTrue(toClient.indexOf("CLASS:CONFIDENTIAL") >= 0);
+        assertTrue(toClient.indexOf("X-CALENDARSERVER-ACCESS:PRIVATE") >= 0);
+    }
+
+    public void testCalendarServerAccessPrivate() throws IOException {
+        String itemBody = "BEGIN:VCALENDAR\n" +
+                "PRODID:-//Apple Inc.//iCal 4.0.3//EN\n" +
+                "BEGIN:VEVENT\n" +
+                "X-CALENDARSERVER-ACCESS:PRIVATE\n" +
+                "END:VEVENT\n" +
+                "END:VCALENDAR";
+        String toServer = fixICS(itemBody, false);
+        System.out.println(toServer);
+        assertTrue(toServer.indexOf("CLASS:CONFIDENTIAL") >= 0);
+    }
+
+    public void testCalendarServerAccessConfidential() throws IOException {
+        String itemBody = "BEGIN:VCALENDAR\n" +
+                "PRODID:-//Apple Inc.//iCal 4.0.3//EN\n" +
+                "BEGIN:VEVENT\n" +
+                "X-CALENDARSERVER-ACCESS:CONFIDENTIAL\n" +
+                "END:VEVENT\n" +
+                "END:VCALENDAR";
+        String toServer = fixICS(itemBody, false);
+        System.out.println(toServer);
+        assertTrue(toServer.indexOf("CLASS:PRIVATE") >= 0);
+    }
+
+    public void testCalendarServerAccessPublic() throws IOException {
+        String itemBody = "BEGIN:VCALENDAR\n" +
+                "PRODID:-//Apple Inc.//iCal 4.0.3//EN\n" +
+                "BEGIN:VEVENT\n" +
+                "X-CALENDARSERVER-ACCESS:PUBLIC\n" +
+                "END:VEVENT\n" +
+                "END:VCALENDAR";
+        String toServer = fixICS(itemBody, false);
+        System.out.println(toServer);
+        assertTrue(toServer.indexOf("CLASS:PUBLIC") >= 0);
+    }
+
+    public void testCalendarServerAccessNone() throws IOException {
+        String itemBody = "BEGIN:VCALENDAR\n" +
+                "PRODID:-//Apple Inc.//iCal 4.0.3//EN\n" +
+                "BEGIN:VEVENT\n" +
+                "END:VEVENT\n" +
+                "END:VCALENDAR";
+        String toServer = fixICS(itemBody, false);
+        System.out.println(toServer);
+        assertFalse(toServer.contains("CLASS"));
+    }
+
+    public void testNoOrganizer() throws IOException {
+        String itemBody = "BEGIN:VCALENDAR\n" +
+                "BEGIN:VEVENT\n" +
+                "END:VEVENT\n" +
+                "END:VCALENDAR";
+        String toServer = fixICS(itemBody, false);
+        System.out.println(toServer);
+        assertTrue(toServer.contains("ORGANIZER"));
+    }
+
+
+    public void testValarm() throws IOException {
+        String itemBody = "BEGIN:VCALENDAR\n" +
+                "BEGIN:VEVENT\n" +
+                "BEGIN:VALARM\n" +
+                "TRIGGER:-PT15M\n" +
+                "ATTACH;VALUE=URI:Basso\n" +
+                "ACTION:AUDIO\n" +
+                "END:VALARM\n" +
+                "END:VEVENT\n" +
+                "END:VCALENDAR";
+        String toServer = fixICS(itemBody, false);
+        System.out.println(toServer);
+        assertTrue(toServer.contains("ACTION:DISPLAY"));
+    }
+
+    public void testReceiveAllDay() throws IOException {
+        String itemBody = "BEGIN:VCALENDAR\n" +
+                vTimeZone +
+                "BEGIN:VEVENT\n" +
+                "DTSTART;TZID=\"(GMT+01.00) Paris/Madrid/Brussels/Copenhagen\":20100615T000000\n" +
+                "DTEND;TZID=\"(GMT+01.00) Paris/Madrid/Brussels/Copenhagen\":20100616T000000\n" +
+                "X-MICROSOFT-CDO-ALLDAYEVENT:TRUE\n" +
+                "END:VEVENT\n" +
+                "END:VCALENDAR";
+        String toClient = fixICS(itemBody, true);
+        System.out.println(toClient);
+        // OWA created allday events have the X-MICROSOFT-CDO-ALLDAYEVENT set to true and always 000000 in event time
+        // just remove the TZID, add VALUE=DATE param and set a date only value 
+        assertTrue(toClient.contains("DTSTART;VALUE=DATE:20100615"));
+        assertTrue(toClient.contains("DTEND;VALUE=DATE:20100616"));
+    }
+
+    public void testSendAllDay() throws IOException {
+        String itemBody = "BEGIN:VCALENDAR\n" +
+                "BEGIN:VEVENT\n" +
+                "DTSTART;VALUE=DATE:20100615\n" +
+                "DTEND;VALUE=DATE:20100616\n" +
+                "END:VEVENT\n" +
+                "END:VCALENDAR";
+        String toServer = fixICS(itemBody, false);
+        System.out.println(toServer);
+        // Client created allday event have no timezone and no time information in date values
+        // first set the X-MICROSOFT-CDO-ALLDAYEVENT flag for OWA
+        assertTrue(toServer.contains("X-MICROSOFT-CDO-ALLDAYEVENT:TRUE"));
+        // then patch TZID for Outlook (need to retrieve OWA TZID
+        assertTrue(toServer.contains("BEGIN:VTIMEZONE"));
+        assertTrue(toServer.contains("TZID:" + vTimeZone.getPropertyValue("TZID")));
+        assertTrue(toServer.contains("DTSTART;TZID=\"" + vTimeZone.getPropertyValue("TZID") + "\":20100615T000000"));
+        assertTrue(toServer.contains("DTEND;TZID=\"" + vTimeZone.getPropertyValue("TZID") + "\":20100616T000000"));
+    }
+
+    public void testRsvp() throws IOException {
+        String itemBody = "BEGIN:VCALENDAR\n" +
+                "BEGIN:VEVENT\n" +
+                "ATTENDEE;PARTSTAT=ACCEPTED;RSVP=TRUE:MAILTO:" + email + "\n" +
+                "END:VEVENT\n" +
+                "END:VCALENDAR";
+        String toClient = fixICS(itemBody, true);
+        System.out.println(toClient);
+        assertTrue(toClient.contains("ATTENDEE;PARTSTAT=ACCEPTED:MAILTO:" + email));
+    }
+
+    public void testExdate() throws IOException {
+        String itemBody = "BEGIN:VCALENDAR\n" +
+                "BEGIN:VEVENT\n" +
+                "EXDATE;TZID=\"Europe/Paris\":20100809T150000,20100823T150000\n" +
+                "END:VEVENT\n" +
+                "END:VCALENDAR";
+        String toClient = fixICS(itemBody, true);
+        System.out.println(toClient);
+        assertTrue(toClient.contains("EXDATE;TZID=\"Europe/Paris\":20100823T150000"));
+
+    }
+
+    public void testEmptyLine() throws IOException {
+        String itemBody = "BEGIN:VCALENDAR\n" +
+                "BEGIN:VEVENT\n" +
+                "\n" +
+                "END:VEVENT\n" +
+                "END:VCALENDAR";
+        String toClient = fixICS(itemBody, true);
+        System.out.println(toClient);
+    }
+
+    public void testAttendeeStatus() throws IOException {
+        String itemBody = "BEGIN:VCALENDAR\n" +
+                "BEGIN:VEVENT\n" +
+                "ATTENDEE;PARTSTAT=ACCEPTED;RSVP=FALSE:MAILTO:" + email + "\n" +
+                "END:VEVENT\n" +
+                "END:VCALENDAR";
+        VCalendar vCalendar = new VCalendar(itemBody, email, vTimeZone);
+        vCalendar.fixVCalendar(false);
+        String status = vCalendar.getAttendeeStatus();
+        assertEquals("ACCEPTED", status);
+        System.out.println("'" + BundleMessage.format(status) + "'");
+    }
+
+    public void testMissingTzid() throws IOException {
+        String itemBody = "BEGIN:VCALENDAR\n" +
+                "BEGIN:VEVENT\n" +
+                "DTSTART:20100101T000000\n" +
+                "DTEND:20100102T000000\n" +
+                "END:VEVENT\n" +
+                "END:VCALENDAR";
+        String toServer = fixICS(itemBody, false);
+        System.out.println(toServer);
+        assertTrue(toServer.contains("DTSTART;TZID="));
+        assertTrue(toServer.contains("DTEND;TZID="));
+    }
+
+    public void testBroken() throws IOException {
+        String itemBody = "BEGIN:VCALENDAR\n" +
+                "PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\n" +
+                "VERSION:2.0\n" +
+                "BEGIN:VEVENT\n" +
+                "CREATED:20100916T115132Z\n" +
+                "LAST-MODIFIED:20100916T115138Z\n" +
+                "DTSTAMP:20100916T115138Z\n" +
+                "UID:d72ff8cc-f3ee-4fbc-b44d-1aaf78d92847\n" +
+                "SUMMARY:New Event\n" +
+                "DTSTART;VALUE=DATE:20100929\n" +
+                "DTEND;VALUE=DATE:20100930\n" +
+                "TRANSP:TRANSPARENT\n" +
+                "END:VEVENT\n" +
+                "END:VCALENDAR";
+        String toServer = fixICS(itemBody, true);
+        System.out.println(toServer);
+    }
+
+    public void testFloatingTimezone() throws IOException {
+        String itemBody = "BEGIN:VCALENDAR\n" +
+                "PRODID:Microsoft CDO for Microsoft Exchange\n" +
+                "VERSION:2.0\n" +
+                "BEGIN:VTIMEZONE\n" +
+                "TZID:Pacific Time (US & Canada)\\; Tijuana\n" +
+                "BEGIN:STANDARD\n" +
+                "DTSTART:16010101T030000\n" +
+                "TZOFFSETFROM:-0700\n" +
+                "TZOFFSETTO:-0800\n" +
+                "RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=11;BYDAY=1SU\n" +
+                "END:STANDARD\n" +
+                "BEGIN:DAYLIGHT\n" +
+                "DTSTART:16010101T010000\n" +
+                "TZOFFSETFROM:-0800\n" +
+                "TZOFFSETTO:-0700\n" +
+                "RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=2SU\n" +
+                "END:DAYLIGHT\n" +
+                "END:VTIMEZONE" +
+                "BEGIN:VEVENT\n" +
+                "END:VEVENT\n" +
+                "END:VCALENDAR";
+        String toServer = fixICS(itemBody, true);
+        System.out.println(toServer);
+    }
+
+    public void testAnotherBroken() throws IOException {
+        String icsBody = "BEGIN:VCALENDAR\n" +
+                "METHOD:PUBLISH\n" +
+                "PRODID:Microsoft Exchange Server 2010\n" +
+                "VERSION:2.0\n" +
+                "BEGIN:VTIMEZONE\n" +
+                "TZID:GMT -0800 (Standard) / GMT -0700 (Daylight)\\n\n" +
+                "BEGIN:STANDARD\n" +
+                "DTSTART:16010101T020000\n" +
+                "TZOFFSETFROM:-0700\n" +
+                "TZOFFSETTO:-0800\n" +
+                "RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=11\n" +
+                "END:STANDARD\n" +
+                "BEGIN:DAYLIGHT\n" +
+                "DTSTART:16010101T020000\n" +
+                "TZOFFSETFROM:-0800\n" +
+                "TZOFFSETTO:-0700\n" +
+                "RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=2SU;BYMONTH=3\n" +
+                "END:DAYLIGHT\n" +
+                "END:VTIMEZONE\n" +
+                "BEGIN:VEVENT\n" +
+                "ORGANIZER;CN=John Doe:MAILTO:aTargetAddress at dummy.com\n" +
+                "DESCRIPTION;LANGUAGE=en-US:Look over broken timezone.\\n\n" +
+                "SUMMARY;LANGUAGE=en-US:meeting\n" +
+                "DTSTART;TZID=GMT -0800 (Standard) / GMT -0700 (Daylight)\n" +
+                ":20060210T130000\n" +
+                "DTEND;TZID=GMT -0800 (Standard) / GMT -0700 (Daylight)\n" +
+                ":20060210T143000\n" +
+                "UID:040000008200E00074C5B7101A82E00800000000D01FF309972CC601000000000000000\n" +
+                " 010000000B389A3C5092D7640A06D2EF5A2125577\n" +
+                "CLASS:PUBLIC\n" +
+                "PRIORITY:5\n" +
+                "DTSTAMP:20060208T180425Z\n" +
+                "TRANSP:OPAQUE\n" +
+                "STATUS:CONFIRMED\n" +
+                "SEQUENCE:0\n" +
+                "LOCATION;LANGUAGE=en-US:not sure\n" +
+                "X-MICROSOFT-CDO-APPT-SEQUENCE:0\n" +
+                "X-MICROSOFT-CDO-OWNERAPPTID:1602758614\n" +
+                "X-MICROSOFT-CDO-BUSYSTATUS:BUSY\n" +
+                "X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY\n" +
+                "X-MICROSOFT-CDO-ALLDAYEVENT:FALSE\n" +
+                "X-MICROSOFT-CDO-IMPORTANCE:1\n" +
+                "X-MICROSOFT-CDO-INSTTYPE:0\n" +
+                "X-MICROSOFT-DISALLOW-COUNTER:FALSE\n" +
+                "END:VEVENT\n" +
+                "END:VCALENDAR";
+        String toClient = fixICS(icsBody, true);
+        System.out.println(toClient);
+
+    }
+}
diff --git a/src/test/davmail/exchange/TestExchangeSessionFolder.java b/src/test/davmail/exchange/TestExchangeSessionFolder.java
new file mode 100644
index 0000000..8668b01
--- /dev/null
+++ b/src/test/davmail/exchange/TestExchangeSessionFolder.java
@@ -0,0 +1,136 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange;
+
+import davmail.Settings;
+
+import javax.mail.MessagingException;
+import java.io.IOException;
+
+/**
+ * Test folder methods.
+ */
+public class TestExchangeSessionFolder extends AbstractExchangeSessionTestCase {
+    public void testCreateFolder() throws IOException {
+        session.createMessageFolder("test");
+    }
+
+    public void testGetFolder() throws IOException {
+        ExchangeSession.Folder folder = session.getFolder("test");
+        assertNotNull(folder);
+        assertEquals("test", folder.folderPath);
+        assertEquals("test", folder.displayName);
+        assertEquals("IPF.Note", folder.folderClass);
+        assertEquals(0, folder.unreadCount);
+        assertFalse(folder.hasChildren);
+        assertFalse(folder.noInferiors);
+        assertNotNull(folder.ctag);
+        assertNotNull(folder.etag);
+    }
+
+    public void testSubFolder() throws IOException {
+        session.createMessageFolder("test/subfolder");
+        ExchangeSession.Folder folder = session.getFolder("test/subfolder");
+        assertNotNull(folder);
+        assertEquals("test/subfolder", folder.folderPath);
+        assertEquals("subfolder", folder.displayName);
+        session.deleteFolder("test/subfolder");
+    }
+
+    public void testUpdateFolder() throws IOException {
+        // TODO: implement
+    }
+
+    public void testMoveFolder() throws IOException {
+        session.deleteFolder("target");
+        session.deleteFolder("tomove");
+        session.createMessageFolder("tomove");
+        session.createMessageFolder("target");
+        session.moveFolder("tomove", "target/moved");
+        session.deleteFolder("target");
+    }
+
+    public void testDeleteFolder() throws IOException {
+        session.deleteFolder("test");
+    }
+
+    public void testCalendarFolder() throws IOException {
+        String folderName = "testcalendar";
+        session.deleteFolder(folderName);
+        session.createCalendarFolder(folderName, null);
+        ExchangeSession.Folder folder = session.getFolder(folderName);
+        assertNotNull(folder);
+        assertEquals("IPF.Appointment", folder.folderClass);
+        session.deleteFolder(folderName);
+    }
+
+    public void testContactFolder() throws IOException {
+        String folderName = "testcontact";
+        session.deleteFolder(folderName);
+        session.createContactFolder(folderName, null);
+        ExchangeSession.Folder folder = session.getFolder(folderName);
+        assertNotNull(folder);
+        assertEquals("IPF.Contact", folder.folderClass);
+        session.deleteFolder(folderName);
+    }
+
+
+    public void testFolderAccent() throws IOException {
+        String folderName = "testé";
+        session.deleteFolder(folderName);
+        session.createMessageFolder(folderName);
+        ExchangeSession.Folder folder = session.getFolder(folderName);
+        assertNotNull(folder);
+        assertEquals(folderName, folder.displayName);
+        assertEquals(folderName, folder.folderPath);
+        session.deleteFolder(folderName);
+    }
+
+    public void testFolderSpace() throws IOException {
+        String folderName = "test space";
+        session.deleteFolder(folderName);
+        session.createMessageFolder(folderName);
+        ExchangeSession.Folder folder = session.getFolder(folderName);
+        assertNotNull(folder);
+        assertEquals(folderName, folder.displayName);
+        assertEquals(folderName, folder.folderPath);
+        session.deleteFolder(folderName);
+    }
+
+    public void testSpecialFolderCharacter() throws IOException {
+        String folderName = "Special & accenté";
+        session.deleteFolder(folderName);
+        session.createMessageFolder(folderName);
+        ExchangeSession.Folder folder = session.getFolder(folderName);
+        assertNotNull(folder);
+        assertEquals(folderName, folder.displayName);
+        assertEquals(folderName, folder.folderPath);
+        session.deleteFolder(folderName);
+    }
+
+    public void testGetSharedFolder() throws IOException, MessagingException {
+        ExchangeSession.Folder folder = session.getFolder("/users/"+ Settings.getProperty("davmail.to")+"/inbox");
+        ExchangeSession.MessageList messages = session.searchMessages("/users/"+ Settings.getProperty("davmail.to")+"/inbox");
+        for (ExchangeSession.Message message:messages) {
+            System.out.println(message.getMimeMessage());
+        }
+        assertNotNull(folder);
+    }
+
+}
diff --git a/src/test/davmail/exchange/TestExchangeSessionMessage.java b/src/test/davmail/exchange/TestExchangeSessionMessage.java
new file mode 100644
index 0000000..02e5c2c
--- /dev/null
+++ b/src/test/davmail/exchange/TestExchangeSessionMessage.java
@@ -0,0 +1,150 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+import javax.mail.util.SharedByteArrayInputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.UUID;
+
+/**
+ * Test message handling features.
+ */
+public class TestExchangeSessionMessage extends AbstractExchangeSessionTestCase {
+    static ExchangeSession.Message message;
+    static String messageName;
+
+    public void testCreateMessage() throws IOException, MessagingException {
+        session.deleteFolder("testfolder");
+        session.createMessageFolder("testfolder");
+        MimeMessage mimeMessage = createMimeMessage();
+        messageName = UUID.randomUUID().toString()+".EML";
+        HashMap<String, String> properties = new HashMap<String, String>();
+        properties.put("draft", "0");
+        session.createMessage("testfolder", messageName, properties, mimeMessage);
+    }
+
+    public void testSearchInbox() throws IOException, MessagingException {
+        ExchangeSession.MessageList messageList = session.searchMessages("INBOX");
+        assertNotNull(messageList);
+    }
+
+    public void testSearchMessage() throws IOException, MessagingException {
+        ExchangeSession.MessageList messageList = session.searchMessages("testfolder");
+        assertNotNull(messageList);
+        assertEquals(1, messageList.size());
+        message = messageList.get(0);
+        assertFalse(message.answered);
+        assertFalse(message.forwarded);
+        assertFalse(message.flagged);
+        assertFalse(message.draft);
+        assertTrue(message.size > 0);
+        assertFalse(message.deleted);
+        assertFalse(message.read);
+        assertNotNull(message.date);
+    }
+
+    public void testFlagMessage() throws IOException, MessagingException {
+        ExchangeSession.Folder testFolder = session.getFolder("testfolder");
+        testFolder.loadMessages();
+        HashMap<String,String> properties = new HashMap<String,String>();
+        properties.put("flagged", "2");
+        session.updateMessage(message, properties);
+
+        // refresh folder
+        testFolder.loadMessages();
+        assertNotNull(testFolder.get(0));
+        assertTrue(testFolder.get(0).flagged);
+        assertEquals(message.getImapUid(), testFolder.get(0).getImapUid());
+    }
+
+    public void testGetContent() throws IOException, MessagingException {
+        byte[] content = session.getContent(message);
+        assertNotNull(content);
+        MimeMessage mimeMessage = new MimeMessage(null, new SharedByteArrayInputStream(content));
+        assertTrue(mimeMessage.getHeader("To")[0].indexOf("test at test.local") >= 0);
+        assertEquals("Test subject", mimeMessage.getSubject());
+        assertEquals("Test message\n", mimeMessage.getContent());
+    }
+
+    public void testProcessMessage() throws IOException, MessagingException {
+        session.processItem("testfolder", messageName);
+    }
+
+    public void testFolderUidNext() throws IOException, MessagingException {
+        ExchangeSession.Folder folder = session.getFolder("testfolder");
+        assertTrue(folder.uidNext > 0);
+    }
+
+    public void testDeleteMessage() throws IOException {
+        session.deleteMessage(message);
+        ExchangeSession.MessageList messageList = session.searchMessages("testfolder");
+        assertNotNull(messageList);
+        assertEquals(0, messageList.size());
+    }
+
+    public void testSpecialMessageCharacter() throws IOException, MessagingException {
+        session.deleteFolder("testfolder");
+        session.createMessageFolder("testfolder");
+        MimeMessage mimeMessage = createMimeMessage();
+        messageName = "Special & accenté.EML";
+        HashMap<String, String> properties = new HashMap<String, String>();
+        properties.put("draft", "0");
+        session.createMessage("testfolder", messageName, properties, mimeMessage);
+        ExchangeSession.MessageList messageList = session.searchMessages("testfolder", session.isEqualTo("urlcompname", messageName));
+        assertNotNull(messageList);
+        assertEquals(1, messageList.size());
+    }
+
+    public void testSlashMessageName() throws IOException, MessagingException {
+        session.deleteFolder("testfolder");
+        session.createMessageFolder("testfolder");
+        MimeMessage mimeMessage = createMimeMessage();
+        messageName = "test _xF8FF_ slash.EML";
+        HashMap<String, String> properties = new HashMap<String, String>();
+        properties.put("draft", "0");
+        session.createMessage("testfolder", messageName, properties, mimeMessage);
+        ExchangeSession.MessageList messageList = session.searchMessages("testfolder", session.isEqualTo("urlcompname", messageName));
+        assertNotNull(messageList);
+        assertEquals(1, messageList.size());
+    }
+
+    public void testPlusMessageName() throws IOException, MessagingException {
+        // fails on Exchange 2003
+        session.deleteFolder("testfolder");
+        session.createMessageFolder("testfolder");
+        MimeMessage mimeMessage = createMimeMessage();
+        messageName = "test + plus.EML";
+        HashMap<String, String> properties = new HashMap<String, String>();
+        properties.put("draft", "0");
+        session.createMessage("testfolder", messageName, properties, mimeMessage);
+        ExchangeSession.MessageList messageList = session.searchMessages("testfolder", session.isEqualTo("urlcompname", messageName));
+        assertNotNull(messageList);
+        assertEquals(1, messageList.size());
+    }
+
+    /**
+     * Cleanup
+     */
+    public void testDeleteFolder() throws IOException {
+        session.deleteFolder("testfolder");
+    }
+}
diff --git a/src/test/davmail/exchange/TestExchangeSessionMessageFlags.java b/src/test/davmail/exchange/TestExchangeSessionMessageFlags.java
new file mode 100644
index 0000000..4987922
--- /dev/null
+++ b/src/test/davmail/exchange/TestExchangeSessionMessageFlags.java
@@ -0,0 +1,115 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange;
+
+import davmail.Settings;
+import org.apache.log4j.Level;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.UUID;
+
+/**
+ * Test message flag update
+ */
+public class TestExchangeSessionMessageFlags extends AbstractExchangeSessionTestCase {
+
+    @Override
+    public void setUp() throws IOException {
+        super.setUp();
+        // recreate empty folder
+        session.deleteFolder("testfolder");
+        session.createMessageFolder("testfolder");
+    }
+
+    public void testCreateDraftMessage() throws MessagingException, IOException {
+        MimeMessage mimeMessage = createMimeMessage();
+        String messageName = UUID.randomUUID().toString()+".EML";
+        HashMap<String, String> properties = new HashMap<String, String>();
+        properties.put("draft", "9");
+        session.createMessage("testfolder", messageName, properties, mimeMessage);
+        ExchangeSession.MessageList messageList = session.searchMessages("testfolder");
+        assertNotNull(messageList);
+        assertEquals(1, messageList.size());
+        assertTrue(messageList.get(0).draft);
+    }
+
+    public void testCreateDraftReadMessage() throws MessagingException, IOException {
+        MimeMessage mimeMessage = createMimeMessage();
+        String messageName = UUID.randomUUID().toString();
+        HashMap<String, String> properties = new HashMap<String, String>();
+        properties.put("draft", "9");
+        session.createMessage("testfolder", messageName, properties, mimeMessage);
+        ExchangeSession.MessageList messageList = session.searchMessages("testfolder");
+        assertNotNull(messageList);
+        assertEquals(1, messageList.size());
+        assertTrue(messageList.get(0).draft);
+        assertTrue(messageList.get(0).read);
+    }
+
+    public void testCreateReadMessage() throws MessagingException, IOException {
+        MimeMessage mimeMessage = createMimeMessage();
+        String messageName = UUID.randomUUID().toString();
+        HashMap<String, String> properties = new HashMap<String, String>();
+        properties.put("draft", "1");
+        session.createMessage("testfolder", messageName, properties, mimeMessage);
+        ExchangeSession.MessageList messageList = session.searchMessages("testfolder");
+        assertNotNull(messageList);
+        assertEquals(1, messageList.size());
+        assertFalse(messageList.get(0).draft);
+        assertTrue(messageList.get(0).read);
+    }
+
+    public void testCreateBccMessage() throws MessagingException, IOException {
+        Settings.setLoggingLevel("httpclient.wire", Level.DEBUG);
+        MimeMessage mimeMessage = createMimeMessage();
+        String messageName = UUID.randomUUID().toString();
+        HashMap<String, String> properties = new HashMap<String, String>();
+        properties.put("draft", "8");
+        properties.put("bcc", "testbcc at test.local");
+        session.createMessage("testfolder", messageName, properties, mimeMessage);
+        ExchangeSession.MessageList messageList = session.searchMessages("testfolder");
+        assertNotNull(messageList);
+        assertEquals(1, messageList.size());
+    }
+
+    public void testCreateDateReceivedMessage() throws MessagingException, IOException {
+        MimeMessage mimeMessage = createMimeMessage();
+        String messageName = UUID.randomUUID().toString();
+        HashMap<String, String> properties = new HashMap<String, String>();
+        SimpleDateFormat dateFormatter = ExchangeSession.getExchangeZuluDateFormat();
+        dateFormatter.setTimeZone(ExchangeSession.GMT_TIMEZONE);
+        Calendar cal = Calendar.getInstance();
+        cal.add(Calendar.MONTH, -1);
+        properties.put("datereceived", dateFormatter.format(cal.getTime()));
+        session.createMessage("testfolder", messageName, properties, mimeMessage);
+        ExchangeSession.MessageList messageList = session.searchMessages("testfolder");
+        assertNotNull(messageList);
+        assertEquals(1, messageList.size());
+        assertNotNull(messageList);
+        // TODO: use same format for date read/write
+        assertEquals(ExchangeSession.getZuluDateFormat().format(cal.getTime()), messageList.get(0).date);
+    }
+
+
+}
diff --git a/src/test/davmail/exchange/TestExchangeSessionSearchContact.java b/src/test/davmail/exchange/TestExchangeSessionSearchContact.java
new file mode 100644
index 0000000..23c1b51
--- /dev/null
+++ b/src/test/davmail/exchange/TestExchangeSessionSearchContact.java
@@ -0,0 +1,104 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange;
+
+import davmail.Settings;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Test contact search.
+ */
+ at SuppressWarnings({"UseOfSystemOutOrSystemErr"})
+public class TestExchangeSessionSearchContact extends AbstractExchangeSessionTestCase {
+    public void testSearchPublicContacts() throws IOException {
+        String folderPath = Settings.getProperty("davmail.publicContactFolder");
+        List<ExchangeSession.Contact> contacts = session.searchContacts(folderPath, ExchangeSession.CONTACT_ATTRIBUTES, null, 0);
+        int count = 0;
+        for (ExchangeSession.Contact contact : contacts) {
+            System.out.println("Contact " + (++count) + '/' + contacts.size() + session.getItem(folderPath, contact.getName()));
+        }
+    }
+
+    public void testSearchPublicContactsRange() throws IOException {
+        String folderPath = Settings.getProperty("davmail.publicContactFolder");
+        List<ExchangeSession.Contact> contacts = session.searchContacts(folderPath, ExchangeSession.CONTACT_ATTRIBUTES, null, 10);
+        assertEquals(10, contacts.size());
+    }
+
+    public void testSearchPublicContactsWithPicture() throws IOException {
+        String folderPath = Settings.getProperty("davmail.publicContactFolder");
+        List<ExchangeSession.Contact> contacts = session.searchContacts(folderPath, ExchangeSession.CONTACT_ATTRIBUTES, session.isTrue("haspicture"), 0);
+        int count = 0;
+        for (ExchangeSession.Contact contact : contacts) {
+            System.out.println("Contact " + (++count) + '/' + contacts.size() + contact.getBody());
+            assertNotNull(session.getContactPhoto(contact));
+        }
+    }
+
+    public void testSearchContacts() throws IOException {
+        List<ExchangeSession.Contact> contacts = session.searchContacts(ExchangeSession.CONTACTS, ExchangeSession.CONTACT_ATTRIBUTES, null, 0);
+        for (ExchangeSession.Contact contact : contacts) {
+            System.out.println(session.getItem(ExchangeSession.CONTACTS, contact.getName()));
+        }
+    }
+
+    public void testSearchContactsUidOnly() throws IOException {
+        Set<String> attributes = new HashSet<String>();
+        attributes.add("uid");
+        List<ExchangeSession.Contact> contacts = session.searchContacts(ExchangeSession.CONTACTS, attributes, null, 0);
+        for (ExchangeSession.Contact contact : contacts) {
+            System.out.println(contact);
+        }
+    }
+
+    public void testSearchContactsByUid() throws IOException {
+        Set<String> attributes = new HashSet<String>();
+        attributes.add("uid");
+        List<ExchangeSession.Contact> contacts = session.searchContacts(ExchangeSession.CONTACTS, attributes, null, 0);
+        for (ExchangeSession.Contact contact : contacts) {
+            System.out.println(session.searchContacts(ExchangeSession.CONTACTS, attributes, session.isEqualTo("uid", contact.get("uid")), 0));
+        }
+    }
+
+    public void testGalFind() throws IOException {
+        // find a set of contacts
+        Map<String, ExchangeSession.Contact> contacts = session.galFind(session.startsWith("cn", "a"), null, 100);
+        for (ExchangeSession.Contact contact : contacts.values()) {
+            System.out.println(contact);
+        }
+        if (!contacts.isEmpty()) {
+            ExchangeSession.Contact testContact = contacts.values().iterator().next();
+            contacts = session.galFind(session.isEqualTo("cn", testContact.get("cn")), null, 100);
+            assertEquals(1, contacts.size());
+            contacts = session.galFind(session.isEqualTo("smtpemail1", testContact.get("smtpemail1")), null, 100);
+            assertEquals(1, contacts.size());
+            contacts = session.galFind(session.startsWith("smtpemail1", testContact.get("smtpemail1")), null, 100);
+            assertEquals(1, contacts.size());
+            contacts = session.galFind(session.and(session.isEqualTo("cn", testContact.get("cn")),
+                    session.startsWith("smtpemail1", testContact.get("smtpemail1"))), null, 100);
+            assertEquals(1, contacts.size());
+        }
+    }
+
+}
diff --git a/src/test/davmail/exchange/TestICSBufferedReader.java b/src/test/davmail/exchange/TestICSBufferedReader.java
new file mode 100644
index 0000000..d223f30
--- /dev/null
+++ b/src/test/davmail/exchange/TestICSBufferedReader.java
@@ -0,0 +1,82 @@
+package davmail.exchange;
+
+import junit.framework.TestCase;
+
+import java.io.StringReader;
+import java.io.IOException;
+
+/**
+ * Test ICSBufferedReader
+ */
+public class TestICSBufferedReader extends TestCase {
+    public void testSimpleRead() throws IOException {
+        String value = "test\nmultiline\nstring";
+        ICSBufferedReader reader = new ICSBufferedReader(new StringReader(value));
+        assertEquals("test", reader.readLine());
+        assertEquals("multiline", reader.readLine());
+        assertEquals("string", reader.readLine());
+        assertNull(reader.readLine());
+    }
+
+    public void testContinuationRead() throws IOException {
+        String value = "test\nmultiline\n string";
+        ICSBufferedReader reader = new ICSBufferedReader(new StringReader(value));
+        assertEquals("test", reader.readLine());
+        assertEquals("multilinestring", reader.readLine());
+        assertNull(reader.readLine());
+    }
+
+    public void testEventWithEmptyLine() throws IOException {
+        String value = "BEGIN:VCALENDAR\n" +
+                "CALSCALE:GREGORIAN\n" +
+                "METHOD:REQUEST\n" +
+                "PRODID:Microsoft CDO for Microsoft Exchange\n" +
+                "VERSION:2.0\n" +
+                "BEGIN:VTIMEZONE\n" +
+                "TZID:Africa/Lagos\n" +
+                "X-MICROSOFT-CDO-TZID:69\n" +
+                "BEGIN:STANDARD\n" +
+                "DTSTART:16010101T000000\n" +
+                "TZOFFSETFROM:+0100\n" +
+                "TZOFFSETTO:+0100\n" +
+                "END:STANDARD\n" +
+                "BEGIN:DAYLIGHT\n" +
+                "DTSTART:16010101T000000\n" +
+                "TZOFFSETFROM:+0100\n" +
+                "TZOFFSETTO:+0100\n" +
+                "END:DAYLIGHT\n" +
+                "END:VTIMEZONE\n" +
+                "BEGIN:VEVENT\n" +
+                "DTSTART;TZID=\"Africa/Lagos\":20070326T070000\n" +
+                "DTEND;TZID=\"Africa/Lagos\":20070326T083000\n" +
+                "DTSTAMP:20070217T231150Z\n" +
+                "SUMMARY:My meeting\n" +
+                "CATEGORIES:Groupcal,iCal:user\n" +
+                "UID:com.apple.syncservices:5C1BCD60-8C8E-4FCE-B2CA-C99DE0BE81EB\n" +
+                "RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,FR\n" +
+                "ORGANIZER:MAILTO:user at domain\n" +
+                "\n" +
+                "X-GROUPCAL-ALARM:PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0\n" +
+                "NUWVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUgQ29tcHV0ZXIvL0RURCBQTElTVCAxLjAvL0VOI\n" +
+                "iAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4wLmR0ZCI+CjxwbGlz\n" +
+                "dCB2ZXJzaW9uPSIxLjAiPgo8YXJyYXkvPgo8L3BsaXN0Pgo=\n" +
+                "CLASS:\n" +
+                "STATUS:TENTATIVE\n" +
+                "TRANSP:OPAQUE\n" +
+                "X-MICROSOFT-CDO-BUSYSTATUS:OOF\n" +
+                "X-MICROSOFT-CDO-INSTTYPE:1\n" +
+                "X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY\n" +
+                "X-MICROSOFT-CDO-ALLDAYEVENT:FALSE\n" +
+                "X-MICROSOFT-CDO-IMPORTANCE:1\n" +
+                "X-MICROSOFT-CDO-OWNERAPPTID:-1\n" +
+                "END:VEVENT\n" +
+                "END:VCALENDAR";
+        ICSBufferedReader reader = new ICSBufferedReader(new StringReader(value));
+        String line;
+        String lastLine = null;
+        while ((line =reader.readLine())!= null) {
+            lastLine = line;
+        }
+        assertEquals("END:VCALENDAR", lastLine);
+    }
+}
diff --git a/src/test/davmail/exchange/TestVProperty.java b/src/test/davmail/exchange/TestVProperty.java
new file mode 100644
index 0000000..3ef262a
--- /dev/null
+++ b/src/test/davmail/exchange/TestVProperty.java
@@ -0,0 +1,40 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2011  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange;
+
+import junit.framework.TestCase;
+
+/**
+ * Test VProperty.
+ */
+public class TestVProperty  extends TestCase {
+    public void testMultivaluedParam() {
+        String line = "TEL;TYPE=home,voice:homePhone";
+        VProperty vProperty = new VProperty(line);
+        assertNotNull(vProperty);
+        assertEquals(line, vProperty.toString());
+    }
+
+    public void testQuoteCn() {
+        String line = "ATTENDEE;CN=\"test\":MAILTO:test at company.com";
+        VProperty vProperty = new VProperty(line);
+        assertNotNull(vProperty);
+        assertEquals(line, vProperty.toString());
+    }
+}
diff --git a/src/test/davmail/exchange/dav/TestDavExchangeSession.java b/src/test/davmail/exchange/dav/TestDavExchangeSession.java
new file mode 100644
index 0000000..d9cf457
--- /dev/null
+++ b/src/test/davmail/exchange/dav/TestDavExchangeSession.java
@@ -0,0 +1,191 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.dav;
+
+import davmail.exchange.AbstractExchangeSessionTestCase;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.httpclient.HttpStatus;
+import org.apache.jackrabbit.webdav.MultiStatusResponse;
+import org.apache.jackrabbit.webdav.property.DavProperty;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Webdav specific unit tests
+ */
+ at SuppressWarnings({"UseOfSystemOutOrSystemErr"})
+public class TestDavExchangeSession extends AbstractExchangeSessionTestCase {
+    DavExchangeSession davSession;
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    public void setUp() throws IOException {
+        super.setUp();
+        davSession = ((DavExchangeSession) session);
+    }
+
+    /**
+     * Test exchange folder path mapping
+     */
+    public void testGetFolderPath() {
+        String mailPath = davSession.getFolderPath("");
+        String rootPath = davSession.getFolderPath("/users/");
+
+        assertEquals(mailPath + davSession.inboxName, davSession.getFolderPath("INBOX"));
+        assertEquals(mailPath + davSession.deleteditemsName, davSession.getFolderPath("Trash"));
+        assertEquals(mailPath + davSession.sentitemsName, davSession.getFolderPath("Sent"));
+        assertEquals(mailPath + davSession.draftsName, davSession.getFolderPath("Drafts"));
+
+        assertEquals(mailPath + davSession.contactsName, davSession.getFolderPath("contacts"));
+        assertEquals(mailPath + davSession.calendarName, davSession.getFolderPath("calendar"));
+
+        assertEquals(mailPath + davSession.inboxName + "/test", davSession.getFolderPath("INBOX/test"));
+        assertEquals(mailPath + davSession.deleteditemsName + "/test", davSession.getFolderPath("Trash/test"));
+        assertEquals(mailPath + davSession.sentitemsName + "/test", davSession.getFolderPath("Sent/test"));
+        assertEquals(mailPath + davSession.draftsName + "/test", davSession.getFolderPath("Drafts/test"));
+
+        // TODO: may be wrong, should return full url, public folders may be located on another server
+        assertEquals("/public", davSession.getFolderPath("/public"));
+        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().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(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.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"));
+
+        // 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"));
+    }
+
+    /**
+     * Get main category list
+     *
+     * @throws IOException on error
+     */
+    public void testGetCategoryList() throws IOException {
+        Set<String> attributes = new HashSet<String>();
+        attributes.add("permanenturl");
+        attributes.add("roamingxmlstream");
+        MultiStatusResponse[] responses = davSession.searchItems("/users/" + davSession.getEmail() + "/calendar", attributes, davSession.and(davSession.isFalse("isfolder"), davSession.isEqualTo("messageclass", "IPM.Configuration.CategoryList")), DavExchangeSession.FolderQueryTraversal.Shallow, 0);
+        String value = (String) responses[0].getProperties(HttpStatus.SC_OK).get(Field.getPropertyName("roamingxmlstream")).getValue();
+        String propertyList = new String(Base64.decodeBase64(value.getBytes()), "UTF-8");
+        System.out.println(propertyList);
+    }
+
+    /**
+     * Find calendar options
+     *
+     * @throws IOException on error
+     */
+    public void testGetCalendarOptions() throws IOException {
+        Set<String> attributes = new HashSet<String>();
+        attributes.add("permanenturl");
+        attributes.add("roamingxmlstream");
+        MultiStatusResponse[] responses = davSession.searchItems("/users/" + davSession.getEmail() + "/calendar", attributes, davSession.and(davSession.isFalse("isfolder"), davSession.isEqualTo("messageclass", "IPM.Configuration.Calendar")), DavExchangeSession.FolderQueryTraversal.Shallow, 0);
+        String value = (String) responses[0].getProperties(HttpStatus.SC_OK).get(Field.getPropertyName("roamingxmlstream")).getValue();
+        String propertyList = new String(Base64.decodeBase64(value.getBytes()), "UTF-8");
+        System.out.println(propertyList);
+    }
+
+    /**
+     * Retrieve all hidden items
+     *
+     * @throws IOException on error
+     */
+    public void testAllHidden() throws IOException {
+        Set<String> attributes = new HashSet<String>();
+        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, 0);
+        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"));
+            }
+
+        }
+    }
+
+    /**
+     * Search in non ipm subtree
+     *
+     * @throws IOException on error
+     */
+    public void testNonIpmSubtree() throws IOException {
+        Set<String> attributes = new HashSet<String>();
+        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, 0);
+        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"));
+            }
+        }
+    }
+
+}
diff --git a/src/test/davmail/exchange/ews/TestEwsExchangeSession.java b/src/test/davmail/exchange/ews/TestEwsExchangeSession.java
new file mode 100644
index 0000000..894998b
--- /dev/null
+++ b/src/test/davmail/exchange/ews/TestEwsExchangeSession.java
@@ -0,0 +1,80 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.exchange.ews;
+
+import davmail.exchange.AbstractExchangeSessionTestCase;
+import davmail.exchange.ExchangeSession;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Webdav specific unit tests
+ */
+ at SuppressWarnings({"UseOfSystemOutOrSystemErr"})
+public class TestEwsExchangeSession extends AbstractExchangeSessionTestCase {
+    EwsExchangeSession ewsSession;
+
+    public void setUp() throws IOException {
+        super.setUp();
+        ewsSession = ((EwsExchangeSession) session);
+    }
+
+    public void testResolveNames() throws IOException {
+        ResolveNamesMethod resolveNamesMethod = new ResolveNamesMethod("smtp:g");
+        ewsSession.executeMethod(resolveNamesMethod);
+        List<EWSMethod.Item> items = resolveNamesMethod.getResponseItems();
+        for (EWSMethod.Item item : items) {
+            System.out.println(item);
+        }
+    }
+
+    public void testGalFind() throws IOException {
+        // find a set of contacts
+        Map<String, ExchangeSession.Contact> contacts = ewsSession.galFind(ewsSession.startsWith("cn", "a"), null, 100);
+        for (ExchangeSession.Contact contact : contacts.values()) {
+            System.out.println(contact);
+        }
+        if (!contacts.isEmpty()) {
+            ExchangeSession.Contact testContact = contacts.values().iterator().next();
+            contacts = ewsSession.galFind(ewsSession.isEqualTo("cn", testContact.get("cn")), null, 100);
+            assertEquals(1, contacts.size());
+            contacts = ewsSession.galFind(ewsSession.isEqualTo("email1", testContact.get("email1")), null, 100);
+            assertEquals(1, contacts.size());
+            contacts = ewsSession.galFind(ewsSession.startsWith("email1", testContact.get("email1")), null, 100);
+            assertEquals(1, contacts.size());
+            contacts = ewsSession.galFind(ewsSession.and(ewsSession.isEqualTo("cn", testContact.get("cn")),
+                    ewsSession.startsWith("email1", testContact.get("email1"))), null, 100);
+            assertEquals(1, contacts.size());
+        }
+    }
+
+    public void testGetUserConfiguration() throws IOException {
+        GetUserConfigurationMethod getUserConfigurationMethod = new GetUserConfigurationMethod();
+        ewsSession.executeMethod(getUserConfigurationMethod);
+        EWSMethod.Item item = getUserConfigurationMethod.getResponseItem();
+        assertNotNull(item);
+    }
+
+    public void testTimezone() {
+        ewsSession.loadVtimezone();
+    }
+
+}
diff --git a/src/test/davmail/imap/AbstractImapTestCase.java b/src/test/davmail/imap/AbstractImapTestCase.java
new file mode 100644
index 0000000..1eef370
--- /dev/null
+++ b/src/test/davmail/imap/AbstractImapTestCase.java
@@ -0,0 +1,82 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2011  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.imap;
+
+import davmail.AbstractDavMailTestCase;
+import davmail.DavGateway;
+import davmail.Settings;
+
+import java.io.*;
+import java.net.Socket;
+
+/**
+ * Abstract IMAP test case.
+ */
+public class AbstractImapTestCase extends AbstractDavMailTestCase {
+    static Socket clientSocket;
+    static BufferedWriter socketWriter;
+    static BufferedReader socketReader;
+
+    static String messageUid;
+
+    protected void write(String line) throws IOException {
+        socketWriter.write(line);
+        socketWriter.flush();
+    }
+
+    protected void writeLine(String line) throws IOException {
+        socketWriter.write(line);
+        socketWriter.newLine();
+        socketWriter.flush();
+    }
+
+    protected String readLine() throws IOException {
+        return socketReader.readLine();
+    }
+
+    protected String readFullAnswer(String prefix) throws IOException {
+        String line = socketReader.readLine();
+        while (!line.startsWith(prefix)) {
+            line = socketReader.readLine();
+        }
+        return line;
+    }
+
+    @Override
+    public void setUp() throws IOException {
+        boolean needStart = !loaded;
+        super.setUp();
+        if (needStart) {
+            // start gateway
+            DavGateway.start();
+        }
+        if (clientSocket == null) {
+            clientSocket = new Socket("localhost", Settings.getIntProperty("davmail.imapPort"));
+            socketWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()));
+            socketReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
+
+            String banner = socketReader.readLine();
+            assertNotNull(banner);
+
+            writeLine(". LOGIN " + Settings.getProperty("davmail.username").replaceAll("\\\\", "\\\\\\\\") + ' ' + Settings.getProperty("davmail.password"));
+            assertEquals(". OK Authenticated", socketReader.readLine());
+        }
+    }
+
+}
diff --git a/src/test/davmail/imap/TestImap.java b/src/test/davmail/imap/TestImap.java
new file mode 100644
index 0000000..ef10471
--- /dev/null
+++ b/src/test/davmail/imap/TestImap.java
@@ -0,0 +1,397 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.imap;
+
+import davmail.Settings;
+
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.internet.MimeMessage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Random;
+
+/**
+ * IMAP tests, an instance of DavMail Gateway must be available
+ */
+ at SuppressWarnings({"JavaDoc", "UseOfSystemOutOrSystemErr"})
+public class TestImap extends AbstractImapTestCase {
+
+    public void testListFolders() throws IOException {
+        writeLine(". LSUB \"\" \"*\"");
+        assertEquals(". OK LSUB completed", readFullAnswer("."));
+    }
+
+    public void testListSubFolders() throws IOException {
+        writeLine(". LIST \"\" \"INBOX*\"");
+        assertEquals(". OK LIST completed", readFullAnswer("."));
+    }
+
+    public void testSelectInbox() throws IOException {
+        writeLine(". SELECT INBOX");
+        assertEquals(". OK [READ-WRITE] SELECT completed", readFullAnswer("."));
+    }
+
+    public void testSelectRoot() throws IOException {
+        writeLine(". SELECT \"\"");
+        assertEquals(". OK [READ-WRITE] SELECT completed", readFullAnswer("."));
+    }
+
+    public void testFetchFlags() throws IOException {
+        writeLine(". UID FETCH 1:* (FLAGS)");
+        assertEquals(". OK UID FETCH completed", readFullAnswer("."));
+    }
+
+    public void testUidSearchUnDeleted() throws IOException {
+        writeLine(". UID SEARCH UNDELETED");
+        assertEquals(". OK SEARCH completed", readFullAnswer("."));
+        writeLine(". UID SEARCH NOT DELETED");
+        assertEquals(". OK SEARCH completed", readFullAnswer("."));
+    }
+
+    public void testUidSearchdeleted() throws IOException {
+        writeLine(". UID SEARCH DELETED");
+        assertEquals(". OK SEARCH completed", readFullAnswer("."));
+    }
+
+    public void testStoreUndelete() throws IOException {
+        writeLine(". UID STORE 10 -FLAGS (\\Deleted)");
+        readFullAnswer(".");
+    }
+
+    public void testCreateFolder() throws IOException {
+        writeLine(". DELETE testfolder");
+        readFullAnswer(".");
+        writeLine(". CREATE testfolder");
+        assertEquals(". OK folder created", readFullAnswer("."));
+        writeLine(". SELECT testfolder");
+        assertEquals(". OK [READ-WRITE] SELECT completed", readFullAnswer("."));
+    }
+
+    public void testFetchBigMessage() throws IOException, MessagingException {
+        testCreateFolder();
+        // create 10MB message
+        MimeMessage mimeMessage = new MimeMessage((Session) null);
+        mimeMessage.addHeader("to", Settings.getProperty("davmail.to"));
+        mimeMessage.addHeader("bcc", Settings.getProperty("davmail.bcc"));
+        Random random = new Random();
+        StringBuilder randomText = new StringBuilder();
+        for (int i = 0; i < 10 * 1024 * 1024; i++) {
+            randomText.append((char) ('a' + random.nextInt(26)));
+        }
+        mimeMessage.setText(randomText.toString());
+        mimeMessage.setSubject("Big subject");
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        mimeMessage.writeTo(baos);
+        byte[] content = baos.toByteArray();
+        long start = System.currentTimeMillis();
+        writeLine(". APPEND testfolder (\\Seen \\Draft) {" + content.length + '}');
+        assertEquals("+ send literal data", readLine());
+        writeLine(new String(content));
+        assertEquals(". OK APPEND completed", readFullAnswer("."));
+        System.out.println("Create time: " + (System.currentTimeMillis() - start) + " ms");
+        writeLine(". NOOP");
+        assertEquals(". OK NOOP completed", readFullAnswer("."));
+        start = System.currentTimeMillis();
+        writeLine(". UID FETCH 1:* (RFC822.SIZE BODY.TEXT)");
+        readFullAnswer(".");
+        System.out.println("Fetch time: " + (System.currentTimeMillis() - start) + " ms");
+
+    }
+
+
+    public void testSelectFolder() throws IOException {
+        writeLine(". SELECT testfolder");
+        assertEquals(". OK [READ-WRITE] SELECT completed", readFullAnswer("."));
+    }
+
+    public void testCreateMessage() throws IOException, MessagingException {
+
+        MimeMessage mimeMessage = new MimeMessage((Session) null);
+        mimeMessage.addHeader("to", Settings.getProperty("davmail.to"));
+        mimeMessage.addHeader("bcc", Settings.getProperty("davmail.bcc"));
+        mimeMessage.setText("Test message");
+        mimeMessage.setSubject("Test subject");
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        mimeMessage.writeTo(baos);
+        byte[] content = baos.toByteArray();
+        writeLine(". APPEND testfolder (\\Seen \\Draft) {" + content.length + '}');
+        assertEquals("+ send literal data", readLine());
+        writeLine(new String(content));
+        assertEquals(". OK APPEND completed", readFullAnswer("."));
+        writeLine(". NOOP");
+        assertEquals(". OK NOOP completed", readFullAnswer("."));
+
+        // fetch message uid
+        writeLine(". UID FETCH 1:* (FLAGS)");
+        String messageLine = readLine();
+        int uidIndex = messageLine.indexOf("UID ") + 4;
+        messageUid = messageLine.substring(uidIndex, messageLine.indexOf(' ', uidIndex));
+        assertEquals(". OK UID FETCH completed", readFullAnswer("."));
+        assertNotNull(messageUid);
+    }
+
+    public void testUidStoreDeletedFlag() throws IOException {
+
+        // test deleted flag
+        writeLine(". UID STORE " + messageUid + " +FLAGS (\\Deleted)");
+        assertEquals(". OK STORE completed", readFullAnswer("."));
+        writeLine(". UID FETCH " + messageUid + " (FLAGS)");
+        assertEquals("* 1 FETCH (UID " + messageUid + " FLAGS (\\Seen \\Deleted \\Draft))", readLine());
+        assertEquals(". OK UID FETCH completed", readFullAnswer("."));
+
+        // remove deleted flag
+        writeLine(". UID STORE " + messageUid + " -FLAGS (\\Deleted)");
+        assertEquals(". OK STORE completed", readFullAnswer("."));
+        writeLine(". UID FETCH " + messageUid + " (FLAGS)");
+        assertEquals("* 1 FETCH (UID " + messageUid + " FLAGS (\\Seen \\Draft))", readLine());
+        assertEquals(". OK UID FETCH completed", readFullAnswer("."));
+
+    }
+
+    public void testUidRemoveSeenFlag() throws IOException {
+        // remove seen flag
+        writeLine(". UID STORE " + messageUid + " FLAGS (\\Draft)");
+        assertEquals(". OK STORE completed", readFullAnswer("."));
+        writeLine(". UID FETCH " + messageUid + " (FLAGS)");
+        assertEquals("* 1 FETCH (UID " + messageUid + " FLAGS (\\Draft))", readLine());
+        assertEquals(". OK UID FETCH completed", readFullAnswer("."));
+    }
+
+    public void testUidStoreForwardedFlag() throws IOException {
+        // add forwarded flag
+        writeLine(". UID STORE " + messageUid + " +FLAGS ($Forwarded)");
+        assertEquals(". OK STORE completed", readFullAnswer("."));
+        writeLine(". UID FETCH " + messageUid + " (FLAGS)");
+        assertEquals("* 1 FETCH (UID " + messageUid + " FLAGS (\\Draft $Forwarded))", readLine());
+        assertEquals(". OK UID FETCH completed", readFullAnswer("."));
+
+        // remove forwarded flag
+        writeLine(". UID STORE " + messageUid + " -FLAGS ($Forwarded)");
+        assertEquals(". OK STORE completed", readFullAnswer("."));
+        writeLine(". UID FETCH " + messageUid + " (FLAGS)");
+        assertEquals("* 1 FETCH (UID " + messageUid + " FLAGS (\\Draft))", readLine());
+        assertEquals(". OK UID FETCH completed", readFullAnswer("."));
+    }
+
+    public void testUidStoreAnsweredFlag() throws IOException {
+        // add answered flag
+        writeLine(". UID STORE " + messageUid + " +FLAGS (\\Answered)");
+        assertEquals(". OK STORE completed", readFullAnswer("."));
+        writeLine(". UID FETCH " + messageUid + " (FLAGS)");
+        assertEquals("* 1 FETCH (UID " + messageUid + " FLAGS (\\Draft \\Answered))", readLine());
+        assertEquals(". OK UID FETCH completed", readFullAnswer("."));
+
+        // remove answered flag
+        writeLine(". UID STORE " + messageUid + " -FLAGS (\\Answered)");
+        assertEquals(". OK STORE completed", readFullAnswer("."));
+        writeLine(". UID FETCH " + messageUid + " (FLAGS)");
+        assertEquals("* 1 FETCH (UID " + messageUid + " FLAGS (\\Draft))", readLine());
+        assertEquals(". OK UID FETCH completed", readFullAnswer("."));
+    }
+
+    public void testUidStoreJunkFlag() throws IOException {
+        // add Junk flag
+        writeLine(". UID STORE " + messageUid + " +FLAGS (Junk)");
+        assertEquals(". OK STORE completed", readFullAnswer("."));
+        writeLine(". UID FETCH " + messageUid + " (FLAGS)");
+        assertEquals("* 1 FETCH (UID " + messageUid + " FLAGS (Junk \\Draft))", readLine());
+        assertEquals(". OK UID FETCH completed", readFullAnswer("."));
+
+        // remove Junk flag
+        writeLine(". UID STORE " + messageUid + " -FLAGS (Junk)");
+        assertEquals(". OK STORE completed", readFullAnswer("."));
+        writeLine(". UID FETCH " + messageUid + " (FLAGS)");
+        assertEquals("* 1 FETCH (UID " + messageUid + " FLAGS (\\Draft))", readLine());
+        assertEquals(". OK UID FETCH completed", readFullAnswer("."));
+    }
+
+    public void testUidStoreSeenFlag() throws IOException {
+        // add Junk flag
+        writeLine(". UID STORE " + messageUid + " +FLAGS (\\Seen)");
+        assertEquals(". OK STORE completed", readFullAnswer("."));
+        writeLine(". UID FETCH " + messageUid + " (FLAGS)");
+        assertEquals("* 1 FETCH (UID " + messageUid + " FLAGS (\\Seen \\Draft))", readLine());
+        assertEquals(". OK UID FETCH completed", readFullAnswer("."));
+
+        // remove Junk flag
+        writeLine(". UID STORE " + messageUid + " -FLAGS (\\Seen)");
+        assertEquals(". OK STORE completed", readFullAnswer("."));
+        writeLine(". UID FETCH " + messageUid + " (FLAGS)");
+        assertEquals("* 1 FETCH (UID " + messageUid + " FLAGS (\\Draft))", readLine());
+        assertEquals(". OK UID FETCH completed", readFullAnswer("."));
+    }
+
+    public void testPartialFetch() throws IOException {
+        writeLine(". UID FETCH " + messageUid + " (BODY.PEEK[1.MIME])");
+        assertEquals(". OK UID FETCH completed", readFullAnswer("."));
+    }
+
+    public void testHeaderFetch() throws IOException {
+        writeLine(". UID FETCH " + messageUid + " (BODY[HEADER.FIELDS (DATE SUBJECT FROM CONTENT-TYPE TO CC BCC MESSAGE-ID IN-REPLY-TO REFERENCES)])");
+        assertEquals(". OK UID FETCH completed", readFullAnswer("."));
+    }
+
+    public void testHeaderBodyFetch() throws IOException {
+        writeLine(". UID FETCH " + messageUid + " (UID BODY.PEEK[HEADER.FIELDS (Content-Type Content-Transfer-Encoding)] BODY.PEEK[TEXT]<0.2048>)");
+        assertEquals(". OK UID FETCH completed", readFullAnswer("."));
+    }
+
+    public void testFetchInternalDate() throws IOException {
+        writeLine(". UID FETCH " + messageUid + " (INTERNALDATE)");
+        assertEquals(". OK UID FETCH completed", readFullAnswer("."));
+    }
+
+
+    public void testDeleteFolder() throws IOException {
+        writeLine(". DELETE testfolder");
+        assertEquals(". OK folder deleted", readFullAnswer("."));
+    }
+
+    public void testLogout() throws IOException {
+        writeLine(". LOGOUT");
+        assertEquals("* BYE Closing connection", socketReader.readLine());
+        assertEquals(". OK LOGOUT completed", socketReader.readLine());
+        clientSocket = null;
+    }
+
+    public void testCopyMessage() throws IOException, InterruptedException, MessagingException {
+        testCreateFolder();
+        testCreateMessage();
+        writeLine(". UID FETCH 1:* (FLAGS)");
+        String messageLine = readLine();
+        int uidIndex = messageLine.indexOf("UID ") + 4;
+        messageUid = messageLine.substring(uidIndex, messageLine.indexOf(' ', uidIndex));
+        assertEquals(". OK UID FETCH completed", readFullAnswer("."));
+
+        writeLine(". UID COPY " + messageUid + " Trash");
+        assertEquals(". OK copy completed", readFullAnswer("."));
+
+        writeLine(". COPY 1 Trash");
+        assertEquals(". OK copy completed", readFullAnswer("."));
+
+        testDeleteFolder();
+    }
+
+    public void testFetchInboxEnvelope() throws IOException {
+        testSelectInbox();
+        writeLine(". UID FETCH 1:* (ENVELOPE)");
+        assertEquals(". OK UID FETCH completed", readFullAnswer("."));
+    }
+
+    public void testFetchInboxBodyStructure() throws IOException {
+        testSelectInbox();
+        writeLine(". UID FETCH 1:* (BODYSTRUCTURE)");
+        assertEquals(". OK UID FETCH completed", readFullAnswer("."));
+    }
+
+    public void testFetchRfc822Header() throws IOException {
+        testSelectInbox();
+        writeLine(". UID FETCH 1:* (UID RFC822.HEADER RFC822.SIZE FLAGS)");
+        assertEquals(". OK UID FETCH completed", readFullAnswer("."));
+    }
+
+    public void testThunderbirdHeaderFetch() throws IOException {
+        testSelectInbox();
+        writeLine(". UID FETCH 1:* (UID RFC822.SIZE FLAGS BODY.PEEK[HEADER.FIELDS (From To Cc Bcc Subject Date Message-ID Priority X-Priority References Newsgroups In-Reply-To Content-Type)])");
+        assertEquals(". OK UID FETCH completed", readFullAnswer("."));
+    }
+
+    public void testSearchHeader() throws IOException {
+        testSelectInbox();
+        writeLine(". UID SEARCH HEADER X-TUID testvalue");
+        assertEquals(". OK SEARCH completed", readFullAnswer("."));
+        writeLine(". UID SEARCH HEADER X-OfflineIMAP \"testvalue\"");
+        assertEquals(". OK SEARCH completed", readFullAnswer("."));
+    }
+
+    public void testSearchUndraft() throws IOException {
+        testSelectInbox();
+        writeLine(". UID SEARCH UNDRAFT");
+        assertEquals(". OK SEARCH completed", readFullAnswer("."));
+        writeLine(". UID SEARCH DRAFT");
+        assertEquals(". OK SEARCH completed", readFullAnswer("."));
+    }
+
+    public void testBrokenPipe() throws IOException, InterruptedException {
+        testSelectInbox();
+        writeLine(". UID FETCH 1:* (RFC822.SIZE BODY.TEXT)");
+        socketReader.readLine();
+        // force close connection
+        clientSocket.close();
+        Thread.sleep(5000);
+    }
+
+    public void testSearchCharset() throws IOException {
+        testSelectInbox();
+        writeLine("UID SEARCH CHARSET UTF-8 (HEADER SUBJECT testé)");
+        assertEquals(". OK SEARCH completed", readFullAnswer("."));
+    }
+
+    public void testWanderLust() throws IOException {
+        testSelectInbox();
+        writeLine(". uid fetch 1:* (body.peek[header.fields (Subject From To Cc Date Message-Id References In-Reply-To Delivered-To)] rfc822.size flags)");
+        assertEquals(". OK UID FETCH completed", readFullAnswer("."));
+    }
+
+    public void testSearchSince() throws IOException {
+        testSelectInbox();
+        writeLine(". UID SEARCH SINCE 1-Jan-2012 UNDELETED");
+        assertEquals(". OK SEARCH completed", readFullAnswer("."));
+    }
+
+    public void testSearchText() throws IOException {
+        testSelectInbox();
+        writeLine(". UID SEARCH TEXT test");
+        assertEquals(". OK SEARCH completed", readFullAnswer("."));
+    }
+
+
+    public void testDraftMessageMessageId() throws IOException, InterruptedException, MessagingException {
+        testCreateFolder();
+        MimeMessage mimeMessage = new MimeMessage((Session) null);
+        mimeMessage.addHeader("to", Settings.getProperty("davmail.to"));
+        mimeMessage.setText("Test message");
+        mimeMessage.setSubject("Test subject");
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        mimeMessage.writeTo(baos);
+        byte[] content = baos.toByteArray();
+        writeLine(". APPEND testfolder (\\Seen \\Draft) {" + content.length + '}');
+        assertEquals("+ send literal data", readLine());
+        writeLine(new String(content));
+        assertEquals(". OK APPEND completed", readFullAnswer("."));
+
+        writeLine(". UID SEARCH UNDELETED (HEADER Message-ID "+mimeMessage.getMessageID().substring(1, mimeMessage.getMessageID().length()-1)+")");
+        assertEquals(". OK SEARCH completed", readFullAnswer("."));
+
+        testDeleteFolder();
+    }
+
+    public void testFetchOSX() throws IOException {
+        testSelectInbox();
+        writeLine(". FETCH 1:* (FLAGS UID BODY.PEEK[HEADER.FIELDS (content-class)])");
+        assertEquals(". OK FETCH completed", readFullAnswer("."));
+    }
+
+    public void testFetchHeadersThunderbird() throws IOException {
+        testSelectInbox();
+        writeLine(". FETCH 1:* (UID RFC822.SIZE FLAGS BODY.PEEK[HEADER.FIELDS (From To Cc Bcc Subject Date Message-ID Priority X-Priority References Newsgroups In-Reply-To Content-Type)])");
+        assertEquals(". OK FETCH completed", readFullAnswer("."));
+    }
+}
diff --git a/src/test/davmail/imap/TestImapFullSync.java b/src/test/davmail/imap/TestImapFullSync.java
new file mode 100644
index 0000000..da5126c
--- /dev/null
+++ b/src/test/davmail/imap/TestImapFullSync.java
@@ -0,0 +1,34 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2011  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.imap;
+
+import java.io.IOException;
+
+/**
+ * Test IMAP full sync.
+ */
+public class TestImapFullSync extends AbstractImapTestCase {
+    public void testSelectTrash() throws IOException {
+        writeLine(". SELECT Trash");
+        assertEquals(". OK [READ-WRITE] SELECT completed", readFullAnswer("."));
+        writeLine(". UID FETCH 1:* (UID RFC822.SIZE FLAGS BODY.PEEK[HEADER.FIELDS (From To Cc Bcc Subject Date Message-ID Priority X-Priority References Newsgroups In-Reply-To Content-Type)])");
+        assertEquals(". OK UID FETCH completed", readFullAnswer("."));
+    }
+
+}
diff --git a/src/test/davmail/ldap/TestLdap.java b/src/test/davmail/ldap/TestLdap.java
new file mode 100644
index 0000000..b894371
--- /dev/null
+++ b/src/test/davmail/ldap/TestLdap.java
@@ -0,0 +1,171 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.ldap;
+
+import davmail.DavGateway;
+import davmail.Settings;
+import davmail.exchange.AbstractExchangeSessionTestCase;
+import davmail.exchange.ExchangeSessionFactory;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import javax.naming.ldap.InitialLdapContext;
+import java.io.IOException;
+import java.util.Hashtable;
+
+/**
+ * Test LDAP.
+ */
+ at SuppressWarnings({"JavaDoc"})
+public class TestLdap extends AbstractExchangeSessionTestCase {
+    InitialLdapContext ldapContext;
+
+    @Override
+    public void setUp() throws IOException {
+        boolean needStart = !loaded;
+        super.setUp();
+        if (needStart) {
+            // start gateway
+            DavGateway.start();
+        }
+        if (ldapContext == null) {
+            Hashtable<String, String> env = new Hashtable<String, String>();
+            env.put("java.naming.security.authentication", "simple");
+            env.put("java.naming.security.principal", Settings.getProperty("davmail.username"));
+            env.put("java.naming.security.credentials", Settings.getProperty("davmail.password"));
+
+            env.put("com.sun.jndi.ldap.connect.pool", "true");
+            env.put("java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory");
+            env.put("java.naming.provider.url", "ldap://localhost:" + Settings.getIntProperty("davmail.ldapPort"));
+            env.put("java.naming.referral", "follow");
+
+            try {
+                ldapContext = new InitialLdapContext(env, null);
+            } catch (NamingException e) {
+                throw new IOException(e);
+            }
+        }
+        if (session == null) {
+            session = ExchangeSessionFactory.getInstance(Settings.getProperty("davmail.username"), Settings.getProperty("davmail.password"));
+        }
+    }
+
+    public void testSearchOneLevel() throws NamingException {
+        SearchControls searchControls = new SearchControls();
+        searchControls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
+        NamingEnumeration<SearchResult> searchResults = ldapContext.search("ou=people", "(objectclass=*)", searchControls);
+    }
+
+    public void testSearchMail() throws NamingException {
+        SearchControls searchControls = new SearchControls();
+        searchControls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
+        searchControls.setReturningAttributes(new String[]{"mail"});
+        NamingEnumeration<SearchResult> searchResults = ldapContext.search("ou=people", "(objectclass=*)", searchControls);
+    }
+
+    public void testMozillaSearchAttributes() throws NamingException {
+        SearchControls searchControls = new SearchControls();
+        searchControls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
+        searchControls.setReturningAttributes(new String[]{"custom1", "mozillausehtmlmail", "postalcode", "custom2", "custom3", "custom4", "street", "surname", "telephonenumber", "mozillahomelocalityname", "orgunit", "mozillaworkstreet2", "xmozillanickname", "mozillahomestreet", "description", "cellphone", "homeurl", "mozillahomepostalcode", "departmentnumber", "postofficebox", "st", "objectclass", "sn", "ou", "fax", "mozillahomeurl", "mozillahomecountryname", "streetaddress", "cn", "company", "mozillaworkurl", "mobile", "region", "birthmonth", "birthday", "labeleduri", "carphone", "department", "xmozillausehtmlmail", "givenname", "nsaimid", "workurl", "facsimiletelephonenumber", "mozillanickname", "title", "nscpaimscreenname", "xmozillasecondemail", "mozillacustom3", "countryname", "mozillacustom4", "mozillacustom1", "mozillacustom2", "homephone", "mozillasecondemail", "pager", "zip", "mail", "c", "mozillahomestate", "o", "l", "birthyear", "modifytimestamp", "locality", "commonname", "notes", "pagerphone", "mozillahomestreet2"});
+        NamingEnumeration<SearchResult> searchResults = ldapContext.search("ou=people", "(objectclass=*)", searchControls);
+    }
+
+    public void testGalfind() throws NamingException {
+        SearchControls searchControls = new SearchControls();
+        searchControls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
+        NamingEnumeration<SearchResult> searchResults = ldapContext.search("ou=people", "(uid="+session.getAlias()+ ')', searchControls);
+        assertTrue(searchResults.hasMore());
+        SearchResult searchResult = searchResults.next();
+        Attributes attributes = searchResult.getAttributes();
+        Attribute attribute = attributes.get("uid");
+        assertEquals(session.getAlias(), attribute.get());
+        // given name not available on Exchange 2007 over Dav (no gallookup)
+        //assertNotNull(attributes.get("givenName"));
+    }
+
+    public void testOSXSearch() throws NamingException {
+        SearchControls searchControls = new SearchControls();
+        searchControls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
+        searchControls.setReturningAttributes(new String[]{"uid", "jpegphoto", "postalcode", "mail", "sn", "apple-emailcontacts", "c", "street", "givenname", "l", "apple-user-picture", "telephonenumber", "cn", "st", "apple-imhandle"});
+        NamingEnumeration<SearchResult> searchResults = ldapContext.search("cn=users, o=od", "(&(objectclass=inetOrgPerson)(|(givenname=Charles*)(|(uid=Ch*)(cn=Ch*))(sn=Ch*))(objectclass=shadowAccount)(objectclass=extensibleObject)(objectclass=posixAccount)(objectclass=apple-user))", searchControls);
+        assertTrue(searchResults.hasMore());
+    }
+
+    public void testOSXICalSearch() throws NamingException {
+        SearchControls searchControls = new SearchControls();
+        searchControls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
+        searchControls.setReturningAttributes(new String[]{"uid", "mail", "sn", "cn", "description", "apple-generateduid", "givenname", "apple-serviceslocator", "uidnumber"});
+        NamingEnumeration<SearchResult> searchResults = ldapContext.search("cn=users, o=od",
+                "(&(objectclass=inetOrgPerson)(objectclass=extensibleObject)(objectclass=apple-user)(|(|(uid=fair*)(cn=fair*))(givenname=fair*)(sn=fair*)(cn=fair*)(mail=fair*))(objectclass=posixAccount)(objectclass=shadowAccount))", searchControls);
+    }
+
+    public void testSearchByGivenNameWithoutReturningAttributes() throws NamingException {
+        SearchControls searchControls = new SearchControls();
+        searchControls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
+        searchControls.setReturningAttributes(new String[]{"uid"});
+        NamingEnumeration<SearchResult> searchResults = ldapContext.search("ou=people", "(givenName=mic*)", searchControls);
+    }
+
+    public void testSearchByGalfindUnsupportedAttribute() throws NamingException {
+        SearchControls searchControls = new SearchControls();
+        searchControls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
+        NamingEnumeration<SearchResult> searchResults = ldapContext.search("ou=people", "(postalcode=N18 1ZF)", searchControls);
+    }
+
+    public void testSearchByCnReturnSn() throws NamingException {
+        SearchControls searchControls = new SearchControls();
+        searchControls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
+        searchControls.setReturningAttributes(new String[]{"sn"});
+        NamingEnumeration<SearchResult> searchResults = ldapContext.search("ou=people", "(cn=*)", searchControls);
+    }
+
+    public void testSearchByCnReturnGivenName() throws NamingException {
+        SearchControls searchControls = new SearchControls();
+        searchControls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
+        searchControls.setReturningAttributes(new String[]{"givenName"});
+        NamingEnumeration<SearchResult> searchResults = ldapContext.search("ou=people", "(cn=*a*)", searchControls);
+    }
+
+    public void testSearchIPad() throws NamingException {
+        SearchControls searchControls = new SearchControls();
+        searchControls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
+        searchControls.setReturningAttributes(new String[]{"postalcode", "labeleduri", "street", "givenname", "telephonenumber", "facsimiletelephonenumber", "title", "imhandle", "homepostaladdress", "st", "homephone", "applefloor", "jpegphoto", "pager", "mail", "sn", "buildingname", "ou", "destinationindicator", "c", "o", "l", "co", "postaladdress", "cn", "mobile"});
+        NamingEnumeration<SearchResult> searchResults = ldapContext.search("ou=people", "(|(mail=Test*)(cn=Test*)(givenname=Test*)(sn=Test*))", searchControls);
+    }
+
+    public void testThunderbird() throws NamingException {
+        String filter = "(|(sn=*stocker*)(givenname=*stocker*)(mail=*stocker*)(cn=*stocker*))";
+        String[] returningAttributes = new String[]{"custom1", "mozillausehtmlmail", "postalcode", "custom2", "custom3", "custom4", "street", "surname", "telephonenumber", "mozillahomelocalityname", "orgunit", "mozillaworkstreet2", "xmozillanickname", "mozillahomestreet", "description", "cellphone", "homeurl", "mozillahomepostalcode", "departmentnumber", "postofficebox", "st", "objectclass", "sn", "ou", "fax", "mozillahomeurl", "mozillahomecountryname", "streetaddress", "cn", "company", "mozillaworkurl", "mobile", "region", "birthmonth", "birthday", "labeleduri", "carphone", "department", "xmozillausehtmlmail", "givenname", "nsaimid", "workurl", "facsimiletelephonenumber", "mozillanickname", "title", "nscpaimscreenname", "xmozillasecondemail", "mozillacustom3", "countryname", "mozillacustom4", "mozillacustom1", "mozillacustom2", "homephone", "mozillasecondemail", "pager", "zip", "mail", "c", "mozillahomestate", "o", "l", "birthyear", "modifytimestamp", "locality", "commonname", "notes", "pagerphone", "mozillahomestreet2"};
+        SearchControls searchControls = new SearchControls();
+        searchControls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
+        searchControls.setReturningAttributes(returningAttributes);
+        NamingEnumeration<SearchResult> searchResults = ldapContext.search("ou=people", filter, searchControls);
+    }
+
+     public void testSearchNotFilter() throws NamingException {
+        SearchControls searchControls = new SearchControls();
+        searchControls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
+        searchControls.setReturningAttributes(new String[]{"mail"});
+        NamingEnumeration<SearchResult> searchResults = ldapContext.search("ou=people", "(!(objectclass=test))", searchControls);
+    }
+}
diff --git a/src/test/davmail/smtp/TestSmtp.java b/src/test/davmail/smtp/TestSmtp.java
new file mode 100644
index 0000000..88d0ca9
--- /dev/null
+++ b/src/test/davmail/smtp/TestSmtp.java
@@ -0,0 +1,254 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.smtp;
+
+import davmail.AbstractDavMailTestCase;
+import davmail.DavGateway;
+import davmail.Settings;
+import davmail.exchange.DoubleDotOutputStream;
+import davmail.exchange.ExchangeSession;
+import davmail.exchange.ExchangeSessionFactory;
+import org.apache.commons.codec.binary.Base64;
+
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.internet.MimeMessage;
+import java.io.*;
+import java.net.Socket;
+
+/**
+ * Test smtp send message.
+ */
+public class TestSmtp extends AbstractDavMailTestCase {
+    static Socket clientSocket;
+    static BufferedOutputStream socketOutputStream;
+    static BufferedInputStream socketInputStream;
+
+    enum State {
+        CHAR, CR, CRLF
+    }
+
+    protected void write(String line) throws IOException {
+        socketOutputStream.write(line.getBytes("ASCII"));
+        socketOutputStream.flush();
+    }
+
+    protected void writeLine(String line) throws IOException {
+        write(line);
+        socketOutputStream.write(13);
+        socketOutputStream.write(10);
+        socketOutputStream.flush();
+    }
+
+    protected String readLine() throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        State state = State.CHAR;
+        while (!(state == State.CRLF)) {
+            int character = socketInputStream.read();
+            if (state == State.CHAR) {
+                if (character == 13) {
+                    state = State.CR;
+                }
+            } else {
+                if (character == 10) {
+                    state = State.CRLF;
+                } else {
+                    state = State.CHAR;
+                }
+            }
+            if (state == State.CHAR) {
+                baos.write(character);
+            }
+        }
+        return new String(baos.toByteArray(), "ASCII");
+    }
+
+    @Override
+    public void setUp() throws IOException {
+        super.setUp();
+        if (clientSocket == null) {
+            // start gateway
+            DavGateway.start();
+            clientSocket = new Socket("localhost", Settings.getIntProperty("davmail.smtpPort"));
+            socketOutputStream = new BufferedOutputStream(clientSocket.getOutputStream());
+            socketInputStream = new BufferedInputStream(clientSocket.getInputStream());
+
+            String banner = readLine();
+            assertNotNull(banner);
+            //String credentials = (char) 0 + Settings.getProperty("davmail.username") + (char) 0 + Settings.getProperty("davmail.password");
+            String credentials = Settings.getProperty("davmail.username")  + (char) 0 + Settings.getProperty("davmail.username") + (char) 0 + Settings.getProperty("davmail.password");
+            writeLine("AUTH PLAIN " + new String(new Base64().encode(credentials.getBytes())));
+            assertEquals("235 OK Authenticated", readLine());
+        }
+        if (session == null) {
+            session = ExchangeSessionFactory.getInstance(Settings.getProperty("davmail.username"), Settings.getProperty("davmail.password"));
+        }
+    }
+
+    public void sendAndCheckMessage(MimeMessage mimeMessage) throws IOException, MessagingException, InterruptedException {
+        sendAndCheckMessage(mimeMessage, null);
+    }
+
+    public void sendAndCheckMessage(MimeMessage mimeMessage, String bcc) throws IOException, MessagingException, InterruptedException {
+        sendAndCheckMessage(mimeMessage, null, bcc);
+    }
+
+    public void sendAndCheckMessage(MimeMessage mimeMessage, String from, String bcc) throws IOException, MessagingException, InterruptedException {
+        // generate message id
+        mimeMessage.saveChanges();
+        // mimeMessage.writeTo(System.out);
+
+        // copy Message-id to references header
+        mimeMessage.addHeader("references", mimeMessage.getHeader("message-id")[0]);
+        if (from != null) {
+            writeLine("MAIL FROM:" + from);
+        } else {
+            writeLine("MAIL FROM:" + session.getEmail());
+        }
+        readLine();
+        if (bcc != null) {
+            writeLine("RCPT TO:" + bcc);
+            readLine();
+        }
+        writeLine("RCPT TO:" + Settings.getProperty("davmail.to"));
+        readLine();
+        writeLine("DATA");
+        assertEquals("354 Start mail input; end with <CRLF>.<CRLF>", readLine());
+        mimeMessage.writeTo(new DoubleDotOutputStream(socketOutputStream));
+        writeLine("");
+        writeLine(".");
+        assertEquals("250 Queued mail for delivery", readLine());
+        // wait for asynchronous message send
+        ExchangeSession.MessageList messages = null;
+        for (int i = 0; i < 5; i++) {
+            messages = session.searchMessages("Sent", session.headerIsEqualTo("references", mimeMessage.getMessageID()));
+            if (messages.size() > 0) {
+                break;
+            }
+            Thread.sleep(1000);
+        }
+        assertEquals(1, messages.size());
+        ExchangeSession.Message sentMessage = messages.get(0);
+        sentMessage.getMimeMessage().writeTo(System.out);
+        assertEquals(mimeMessage.getDataHandler().getContent(), sentMessage.getMimeMessage().getDataHandler().getContent());
+    }
+
+    public void testSendSimpleMessage() throws IOException, MessagingException, InterruptedException {
+        String body = "Test message";
+        MimeMessage mimeMessage = new MimeMessage((Session) null);
+        mimeMessage.addHeader("To", Settings.getProperty("davmail.to"));
+        mimeMessage.setSubject("Test subject");
+        mimeMessage.setText(body);
+        sendAndCheckMessage(mimeMessage);
+    }
+
+    public void testInvalidFrom() throws IOException, MessagingException, InterruptedException {
+        String body = "Test message";
+        MimeMessage mimeMessage = new MimeMessage((Session) null);
+        mimeMessage.addHeader("From", "guessant at loca.net");
+        mimeMessage.addHeader("To", Settings.getProperty("davmail.to"));
+        mimeMessage.setSubject("Test subject");
+        mimeMessage.setText(body);
+        sendAndCheckMessage(mimeMessage, "guessant at loca.net", null);
+    }
+
+    public void testSendMessage() throws IOException, MessagingException, InterruptedException {
+        String body = "Test message\r\n" +
+                "Special characters: éèçà\r\n" +
+                "Chinese: " + ((char) 0x604F) + ((char) 0x7D59);
+        MimeMessage mimeMessage = new MimeMessage((Session) null);
+        mimeMessage.addHeader("To", Settings.getProperty("davmail.to"));
+        mimeMessage.setSubject("Test subject");
+        mimeMessage.setText(body, "UTF-8");
+        sendAndCheckMessage(mimeMessage);
+    }
+
+    public void testBccMessage() throws IOException, MessagingException, InterruptedException {
+        MimeMessage mimeMessage = new MimeMessage((Session) null);
+        mimeMessage.addHeader("to", Settings.getProperty("davmail.to"));
+        mimeMessage.setSubject("Test subject dav");
+        mimeMessage.setText("Test message");
+        sendAndCheckMessage(mimeMessage, Settings.getProperty("davmail.bcc"));
+    }
+
+    public void testDotMessage() throws IOException, MessagingException, InterruptedException {
+        String body = "First line\r\n.\r\nSecond line";
+        MimeMessage mimeMessage = new MimeMessage((Session) null);
+        mimeMessage.addHeader("to", Settings.getProperty("davmail.to"));
+        mimeMessage.setSubject("Test subject");
+        mimeMessage.setText(body);
+        sendAndCheckMessage(mimeMessage);
+    }
+
+    public void testSendMessageTwice() throws IOException, MessagingException, InterruptedException {
+        Settings.setProperty("davmail.smtpCheckDuplicates", "true");
+        String body = "First line\r\n.\r\nSecond line";
+        MimeMessage mimeMessage = new MimeMessage((Session) null);
+        mimeMessage.addHeader("to", Settings.getProperty("davmail.to"));
+        mimeMessage.setSubject("Test subject");
+        mimeMessage.setText(body);
+        sendAndCheckMessage(mimeMessage);
+        sendAndCheckMessage(mimeMessage);
+    }
+
+    public void testComplexToMessage() throws IOException, MessagingException, InterruptedException {
+        String body = "Test message";
+        MimeMessage mimeMessage = new MimeMessage((Session) null);
+        mimeMessage.addHeader("To", "nickname <" + Settings.getProperty("davmail.to") + '>');
+        mimeMessage.setSubject("Test subject");
+        mimeMessage.setText(body);
+        sendAndCheckMessage(mimeMessage);
+    }
+
+    public void testSendPlainTextMessage() throws IOException, MessagingException, InterruptedException {
+        String body = "Test plain text message";
+        MimeMessage mimeMessage = new MimeMessage((Session) null);
+        mimeMessage.addHeader("To", Settings.getProperty("davmail.to"));
+        mimeMessage.setSubject("Test text/plain message");
+        mimeMessage.setText(body);
+        sendAndCheckMessage(mimeMessage);
+    }
+
+    public void testSendHtmlMessage() throws IOException, MessagingException, InterruptedException {
+        String body = "Test html message <font color=\"#ff0000\">red</font>";
+        MimeMessage mimeMessage = new MimeMessage((Session) null);
+        mimeMessage.addHeader("To", Settings.getProperty("davmail.to"));
+        mimeMessage.setSubject("Test html message");
+        mimeMessage.setContent(body, "text/html");
+        sendAndCheckMessage(mimeMessage);
+    }
+
+    public void testQuit() throws IOException {
+        writeLine("QUIT");
+        assertEquals("221 Closing connection", readLine());
+    }
+
+
+    public void testBrokenMessage() throws MessagingException, IOException, InterruptedException {
+        MimeMessage mimeMessage = new MimeMessage(null, new FileInputStream("test.eml"));
+        sendAndCheckMessage(mimeMessage);
+    }
+
+    public void testBrokenMessage2() throws MessagingException, IOException, InterruptedException {
+        MimeMessage mimeMessage = new MimeMessage(null, new org.apache.commons.codec.binary.Base64InputStream(new FileInputStream("broken64.txt")));
+        mimeMessage.addHeader("To", Settings.getProperty("davmail.to"));
+        sendAndCheckMessage(mimeMessage);
+    }
+
+}
diff --git a/src/test/davmail/ui/TestNotificationDialog.java b/src/test/davmail/ui/TestNotificationDialog.java
new file mode 100644
index 0000000..1d19c3d
--- /dev/null
+++ b/src/test/davmail/ui/TestNotificationDialog.java
@@ -0,0 +1,36 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2010  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.ui;
+
+import junit.framework.TestCase;
+
+import javax.swing.*;
+import java.io.IOException;
+
+/**
+ * Test Notification Frame
+ */
+public class TestNotificationDialog extends TestCase {
+    public void testCreateNotificationFrame() throws IOException, ClassNotFoundException, UnsupportedLookAndFeelException, IllegalAccessException, InstantiationException {
+        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+        NotificationDialog notificationDialog = new NotificationDialog("to", "cc", "subject", "description");
+        notificationDialog.setVisible(true);
+        System.out.println(notificationDialog.getSendNotification());
+    }
+}
diff --git a/src/test/davmail/util/StringUtilTest.java b/src/test/davmail/util/StringUtilTest.java
new file mode 100644
index 0000000..7a7612a
--- /dev/null
+++ b/src/test/davmail/util/StringUtilTest.java
@@ -0,0 +1,122 @@
+/*
+ * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+ * Copyright (C) 2009  Mickael Guessant
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package davmail.util;
+
+import junit.framework.TestCase;
+import org.apache.commons.httpclient.URIException;
+import org.apache.commons.httpclient.util.URIUtil;
+
+/**
+ * Test StringUtil.
+ */
+public class StringUtilTest extends TestCase {
+    /**
+     * Test get token
+     */
+    public void testGetToken() {
+        assertNull(StringUtil.getToken(null, null, null));
+        assertNull(StringUtil.getToken(null, "\'", "\'"));
+        assertNull(StringUtil.getToken("'test", "\'", "\'"));
+        assertEquals("test", StringUtil.getToken("'test'", "'", "'"));
+        assertEquals("test", StringUtil.getToken("value=\"test\"", "value=\"", "\""));
+    }
+
+    /**
+     * Test replace token
+     */
+    public void testReplaceToken() {
+        assertNull(StringUtil.replaceToken(null, null, null, null));
+        assertNull(StringUtil.replaceToken(null, null, null, "new"));
+        assertEquals("'new'", StringUtil.replaceToken("'old'", "'", "'", "new"));
+        assertEquals("value=\"new\"", StringUtil.replaceToken("value=\"old\"", "value=\"", "\"", "new"));
+    }
+
+    /**
+     * Test Xml Encode
+     */
+    public void testXmlEncode() {
+        assertEquals("&", StringUtil.xmlEncode("&"));
+        assertEquals("<", StringUtil.xmlEncode("<"));
+        assertEquals(">", StringUtil.xmlEncode(">"));
+        assertEquals("&", StringUtil.xmlDecode("&"));
+        assertEquals("<", StringUtil.xmlDecode("<"));
+        assertEquals(">", StringUtil.xmlDecode(">"));
+        assertEquals("<test>", StringUtil.xmlEncode("<test>"));
+    }
+
+    public void testUrlEncodeAmpersand() {
+        assertEquals("%26", StringUtil.urlEncodeAmpersand("&"));
+        assertEquals("&", StringUtil.urlDecodeAmpersand("%26"));
+    }
+
+    public void testPerf() {
+        String value = "dqsdqs+dsqds+dsqdqs";
+        for (int j = 0; j < 5; j++) {
+            long startTime = System.currentTimeMillis();
+            for (int i = 0; i < 1000000; i++) {
+                //String result = StringUtil.encodePlusSign(value);
+                //String result = StringUtil.replaceAll(value, '+', "%2B");
+                                                 
+                /*int length = value.length();
+                StringBuilder buffer = new StringBuilder(length);
+                int startIndex = 0;
+                int endIndex = value.indexOf('+');
+                while (endIndex >= 0) {
+                    buffer.append(value.substring(startIndex, endIndex));
+                    buffer.append("%2B");
+                    startIndex = endIndex + 1;
+                    endIndex = value.indexOf('+', startIndex);
+                }
+                buffer.append(value.substring(startIndex)); */
+                /*
+                for (int k = 0; k < length; k++) {
+                    char c = value.charAt(k);
+                    if (c == '+') {
+                        buffer.append("%2B");
+                    } else {
+                        buffer.append(c);
+                    }
+                }*/
+                //String result = buffer.toString();
+                //String result = value.replaceAll("\\+", "%2B");
+            }
+            System.out.println("Elapsed: " + (System.currentTimeMillis() - startTime) + " ms");
+        }
+    }
+
+    public void testRemoveQuotes() {
+        assertEquals("test", StringUtil.removeQuotes("test"));
+        assertEquals("test", StringUtil.removeQuotes("\"test\""));
+    }
+
+    public void testEncodePipe() {
+        assertEquals("test %7C", StringUtil.encodeUrlcompname("test |"));
+        assertEquals("test |", StringUtil.decodeUrlcompname("test %7C"));
+    }
+
+    public void testEncodeQuestion() {
+        try {
+            URIUtil.encodeWithinQuery("test ?");
+        } catch (URIException e) {
+            e.printStackTrace();
+        }
+        assertEquals("test %3F", StringUtil.encodeUrlcompname("test ?"));
+        assertEquals("test ?", StringUtil.decodeUrlcompname("test %3F"));
+    }
+}
diff --git a/src/web/WEB-INF/classes/davmail.properties b/src/web/WEB-INF/classes/davmail.properties
new file mode 100644
index 0000000..ad0e495
--- /dev/null
+++ b/src/web/WEB-INF/classes/davmail.properties
@@ -0,0 +1,23 @@
+davmail.url=http://exchangeServer/exchange/
+davmail.popPort=1110
+davmail.smtpPort=1025
+davmail.keepDelay=30
+davmail.sentKeepDelay=90
+davmail.caldavPastDelay=90
+
+davmail.useSystemProxies=false
+davmail.enableProxy=false
+davmail.proxyHost=
+davmail.proxyPort=
+davmail.proxyUser=
+davmail.proxyPassword=
+
+davmail.allowRemote=true
+davmail.bindAddress=
+davmail.server=true
+davmail.disableUpdateCheck=false
+
+log4j.logger.davmail=DEBUG
+log4j.rootLogger=WARN
+log4j.logger.httpclient.wire=WARN
+log4j.logger.org.apache.commons.httpclient=WARN
diff --git a/src/web/WEB-INF/web.xml b/src/web/WEB-INF/web.xml
new file mode 100644
index 0000000..ccb57ad
--- /dev/null
+++ b/src/web/WEB-INF/web.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app xmlns="http://java.sun.com/xml/ns/javaee"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
+		  http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+         version="2.5">
+    <description>
+        Ever wanted to get rid of Outlook ? DavMail is a POP/IMAP/SMTP/Caldav/Carddav/LDAP exchange
+        gateway allowing users to use any mail/calendar client (e.g. Thunderbird with Lightning or
+        Apple iCal) with an Exchange server, even from the internet or behind a firewall through
+        Outlook Web Access.
+        DavMail now includes an LDAP gateway to Exchange global address book and user personal
+        contacts to allow recipient address completion in mail compose window and full calendar
+        support with attendees free/busy display.
+        The main goal of DavMail is to provide standard compliant protocols in front of proprietary
+        Exchange. This means LDAP for global address book, SMTP to send messages, IMAP to browse
+        messages on the server in any folder, POP to retrieve inbox messages only, Caldav for
+        calendar support and Carddav for personal contacts sync.
+        Thus any standard compliant client can be used with Microsoft Exchange.
+        DavMail gateway is implemented in java and should run on any platform. Releases are tested
+        on Windows, Linux (Ubuntu) and Mac OSX. Tested successfully with the Iphone
+        (gateway running on a server).
+
+        http://davmail.sourceforge.net
+    </description>
+    <listener>
+        <listener-class>davmail.web.DavGatewayServletContextListener</listener-class>
+    </listener>
+</web-app>
diff --git a/src/winrun4j/davmailservice.exe b/src/winrun4j/davmailservice.exe
new file mode 100644
index 0000000..e8428aa
Binary files /dev/null and b/src/winrun4j/davmailservice.exe differ
diff --git a/src/winrun4j/davmailservice.ini b/src/winrun4j/davmailservice.ini
new file mode 100644
index 0000000..bf5b70a
--- /dev/null
+++ b/src/winrun4j/davmailservice.ini
@@ -0,0 +1,15 @@
+service.startup=auto
+service.class=davmail.service.DavService
+service.id=DavMail
+service.name=DavMail Gateway
+service.description=DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway
+
+classpath.1=davmail.jar
+classpath.2=lib/*
+
+vm.version.min=1.6
+vmarg.1=-Dsun.net.inetaddr.ttl=60
+vmarg.2=-Xrs
+
+
+arg.1=davmail.properties
diff --git a/src/winrun4j/rcedit.exe b/src/winrun4j/rcedit.exe
new file mode 100644
index 0000000..642e955
Binary files /dev/null and b/src/winrun4j/rcedit.exe differ
diff --git a/user.properties b/user.properties
new file mode 100644
index 0000000..1d12c7a
--- /dev/null
+++ b/user.properties
@@ -0,0 +1,3 @@
+username=mguessan
+keyfile=C:/mguessan/.ssh/id_dsa
+passphrase=

-- 
davmail packaging



More information about the pkg-java-commits mailing list