diff --git a/src/java/davmail/exchange/ExchangeSession.java b/src/java/davmail/exchange/ExchangeSession.java index c4be9d68..28026dc6 100644 --- a/src/java/davmail/exchange/ExchangeSession.java +++ b/src/java/davmail/exchange/ExchangeSession.java @@ -14,6 +14,7 @@ import org.apache.webdav.lib.ResponseEntity; import org.apache.webdav.lib.WebdavResource; import org.apache.webdav.lib.methods.PropPatchMethod; import org.apache.webdav.lib.methods.SearchMethod; +import org.apache.webdav.lib.methods.MoveMethod; import org.htmlcleaner.CommentToken; import org.htmlcleaner.HtmlCleaner; import org.htmlcleaner.TagNode; @@ -537,23 +538,10 @@ public class ExchangeSession { message.size = Integer.parseInt(prop.getPropertyAsString()); } else if ("uid".equals(localName)) { message.uid = prop.getPropertyAsString(); - } else if ("date".equals(prop.getLocalName())) { - message.date = prop.getPropertyAsString(); + } else if ("read".equals(localName)) { + message.read = "1".equals(prop.getPropertyAsString()); } else if ("message-id".equals(prop.getLocalName())) { message.messageId = prop.getPropertyAsString(); - } else if ("from".equals(prop.getLocalName())) { - message.from = prop.getPropertyAsString(); - } else if ("to".equals(prop.getLocalName())) { - message.to = prop.getPropertyAsString(); - } else if ("cc".equals(prop.getLocalName())) { - message.cc = prop.getPropertyAsString(); - } else if ("subject".equals(prop.getLocalName())) { - message.subject = prop.getPropertyAsString(); - } else if ("priority".equals(prop.getLocalName())) { - String priorityLabel = PRIORITIES.get(prop.getPropertyAsString()); - if (priorityLabel != null) { - message.priority = priorityLabel; - } } } @@ -575,11 +563,29 @@ public class ExchangeSession { } + public void updateMessage(Message message, Map properties) throws IOException { + PropPatchMethod patchMethod = new PropPatchMethod(URIUtil.encodePathQuery(message.messageUrl)); + try { + for (Map.Entry entry:properties.entrySet()) { + if ("read".equals(entry.getKey())) { + patchMethod.addPropertyToSet("read", entry.getValue(), "e", "urn:schemas:httpmail:"); + } + } + patchMethod.setDebug(4); + int statusCode = wdr.retrieveSessionInstance().executeMethod(patchMethod); + if (statusCode != HttpStatus.SC_MULTI_STATUS) { + throw new IOException("Unable to update message properties"); + } + + } finally { + patchMethod.releaseConnection(); + } + } public List getAllMessages(String folderName) throws IOException { String folderUrl = getFolderPath(folderName); List messages = new ArrayList(); String searchRequest = "Select \"DAV:uid\", \"http://schemas.microsoft.com/mapi/proptag/x0e080003\"" + - " ,\"urn:schemas:mailheader:from\",\"urn:schemas:mailheader:to\",\"urn:schemas:mailheader:cc\",\"urn:schemas:httpmail:subject\",\"urn:schemas:mailheader:date\",\"urn:schemas:mailheader:message-id\",\"urn:schemas:httpmail:priority\"" + + " ,\"urn:schemas:mailheader:message-id\", \"urn:schemas:httpmail:read\""+ " FROM Scope('SHALLOW TRAVERSAL OF \"" + folderUrl + "\"')\n" + " WHERE \"DAV:ishidden\" = False AND \"DAV:isfolder\" = False\n" + " ORDER BY \"urn:schemas:httpmail:date\" ASC"; @@ -600,7 +606,8 @@ public class ExchangeSession { String searchRequest = "Select \"DAV:nosubs\", \"DAV:hassubs\"," + " \"DAV:hassubs\",\"urn:schemas:httpmail:unreadcount\"" + " FROM Scope('" + mode + " TRAVERSAL OF \"" + getFolderPath(folderName) + "\"')\n" + - " WHERE \"DAV:ishidden\" = False AND \"DAV:isfolder\" = True \n"; + " WHERE \"DAV:ishidden\" = False AND \"DAV:isfolder\" = True \n"+ + " AND (\"DAV:contentclass\"='urn:content-classes:mailfolder' OR \"DAV:contentclass\"='urn:content-classes:folder')"; Enumeration folderEnum = DavGatewayHttpClientFacade.executeSearchMethod(wdr.retrieveSessionInstance(), mailPath, searchRequest); while (folderEnum.hasMoreElements()) { @@ -822,6 +829,7 @@ public class ExchangeSession { } }; method.addPropertyToSet("outlookfolderclass", "IPF.Note", "ex", "http://schemas.microsoft.com/exchange/"); + try { wdr.retrieveSessionInstance().executeMethod(method); // ok or alredy exists if (method.getStatusCode() != HttpStatus.SC_MULTI_STATUS && method.getStatusCode() != HttpStatus.SC_METHOD_NOT_ALLOWED) { @@ -830,6 +838,31 @@ public class ExchangeSession { ex.setReason(method.getStatusText()); throw ex; } + } finally { + method.releaseConnection(); + } + } + + public void moveFolder(String folderName, String targetName) throws IOException { + String folderPath = getFolderPath(folderName); + String targetPath = getFolderPath(targetName); + MoveMethod method = new MoveMethod(URIUtil.encodePath(folderPath), + URIUtil.encodePath(targetPath)); + method.setOverwrite(false); + //method.addRequestHeader("Allow-Rename", "t"); + try { + int statusCode = wdr.retrieveSessionInstance().executeMethod(method); + if (statusCode == HttpStatus.SC_PRECONDITION_FAILED) { + throw new HttpException("Unable to move folder, target already exists"); + } else if (statusCode != HttpStatus.SC_CREATED) { + HttpException ex = new HttpException(); + ex.setReasonCode(method.getStatusCode()); + ex.setReason(method.getStatusText()); + throw ex; + } + } finally { + method.releaseConnection(); + } } public static class Folder { @@ -856,13 +889,8 @@ public class ExchangeSession { public String messageUrl; public String uid; public int size; - public String from; - public String date; public String messageId; - public String subject; - public String priority; - public String cc; - public String to; + public boolean read; public void write(OutputStream os) throws IOException { HttpMethod method = null; diff --git a/src/java/davmail/imap/ImapConnection.java b/src/java/davmail/imap/ImapConnection.java index ddd1cad6..19bc5c88 100644 --- a/src/java/davmail/imap/ImapConnection.java +++ b/src/java/davmail/imap/ImapConnection.java @@ -5,8 +5,11 @@ import java.net.SocketTimeoutException; import java.net.SocketException; import java.util.StringTokenizer; import java.util.List; +import java.util.HashMap; import java.io.IOException; import java.io.ByteArrayOutputStream; +import java.io.FilterOutputStream; +import java.io.OutputStream; import davmail.AbstractConnection; import davmail.tray.DavGatewayTray; @@ -14,6 +17,7 @@ import davmail.exchange.ExchangeSession; import davmail.exchange.ExchangeSessionFactory; import com.sun.mail.imap.protocol.BASE64MailboxEncoder; import com.sun.mail.imap.protocol.BASE64MailboxDecoder; +import org.apache.commons.httpclient.HttpException; /** * Dav Gateway smtp connection implementation. @@ -55,7 +59,7 @@ public class ImapConnection extends AbstractConnection { && nextToken.charAt(nextToken.length() - 1) != ')') { nextToken.append(' ').append(super.nextToken()); } - return nextToken.toString(); + return removeQuotes(nextToken.toString()); } }; if (tokens.hasMoreTokens()) { @@ -157,7 +161,7 @@ public class ImapConnection extends AbstractConnection { } else if ("close".equalsIgnoreCase(command)) { currentFolder = null; messages = null; - sendClient(commandId + " OK CLOSE unrecognized"); + sendClient(commandId + " OK CLOSE completed"); } else if ("create".equalsIgnoreCase(command)) { if (tokens.hasMoreTokens()) { String folderName = BASE64MailboxDecoder.decode(removeQuotes(tokens.nextToken())); @@ -166,6 +170,15 @@ public class ImapConnection extends AbstractConnection { } else { sendClient(commandId + " BAD missing create argument"); } + } else if ("rename".equalsIgnoreCase(command)) { + String folderName = BASE64MailboxDecoder.decode(removeQuotes(tokens.nextToken())); + String targetName = BASE64MailboxDecoder.decode(removeQuotes(tokens.nextToken())); + try { + session.moveFolder(folderName, targetName); + sendClient(commandId + " OK rename completed"); + } catch (HttpException e) { + sendClient(commandId + " NO " + e.getReason()); + } } else if ("uid".equalsIgnoreCase(command)) { if (tokens.hasMoreTokens()) { String subcommand = tokens.nextToken(); @@ -192,7 +205,7 @@ public class ImapConnection extends AbstractConnection { int count = 0; for (ExchangeSession.Message message : messages) { count++; - sendClient("* " + count + " FETCH (UID " + count + " FLAGS (\\Seen))"); + sendClient("* " + count + " FETCH (UID " + count + " FLAGS ("+(message.read?"\\Seen":"")+"))"); } sendClient(commandId + " OK UID FETCH completed"); } else { @@ -208,18 +221,22 @@ public class ImapConnection extends AbstractConnection { message.write(baos); baos.close(); + DavGatewayTray.debug("Messagee size: "+message.size+" actual size:"+baos.size()+" message+headers: "+(message.size+baos.size())); sendClient("* " + messageIndex + " FETCH (UID " + messageIndex + " RFC822.SIZE " + baos.size() + " BODY[]<0>" + " {" + baos.size() + "}"); message.write(os); sendClient(")"); } else { + // write headers to byte array ByteArrayOutputStream baos = new ByteArrayOutputStream(); - message.write(baos); + HeaderOutputStream headerOutputStream = new HeaderOutputStream(baos); + message.write(headerOutputStream); baos.close(); - sendClient("* " + messageIndex + " FETCH (UID " + messageIndex + " RFC822.SIZE " + baos.size() + " BODY[HEADER.FIELDS (FROM TO CC SUBJECT DATE MESSAGE-ID PRIORITY X-PRIORITY REFERENCES NEWSGROUPS IN-REPLY-TO CONTENT-TYPE)" + + sendClient("* " + messageIndex + " FETCH (UID " + messageIndex + " RFC822.SIZE " + headerOutputStream.size() + " BODY[HEADER.FIELDS ()" + "] {" + baos.size() + "}"); - message.write(os); - sendClient(" FLAGS (\\Seen))"); + os.write(baos.toByteArray()); + os.flush(); + sendClient(" FLAGS ("+(message.read?"\\Seen":"")+"))"); } } sendClient(commandId + " OK FETCH completed"); @@ -249,7 +266,29 @@ public class ImapConnection extends AbstractConnection { sendClient(commandId + " OK SEARCH completed"); } else if ("store".equalsIgnoreCase(subcommand)) { + int uid = Integer.parseInt(tokens.nextToken()); + String action = tokens.nextToken(); + String flags = tokens.nextToken(); + HashMap properties = new HashMap(); + if ("-Flags".equalsIgnoreCase(action)) { + StringTokenizer flagtokenizer = new StringTokenizer(flags); + while (flagtokenizer.hasMoreTokens()) { + String flag = flagtokenizer.nextToken(); + if ("\\Seen".equals(flag)) { + properties.put("read", "0"); + } + } + } else if ("+Flags".equalsIgnoreCase(action)) { + StringTokenizer flagtokenizer = new StringTokenizer(flags); + while (flagtokenizer.hasMoreTokens()) { + String flag = flagtokenizer.nextToken(); + if ("\\Seen".equals(flag)) { + properties.put("read", "1"); + } + } + } // TODO + session.updateMessage(messages.get(uid-1), properties); sendClient(commandId + " OK STORE completed"); } } else { @@ -293,11 +332,11 @@ public class ImapConnection extends AbstractConnection { if (subjectStartIndex >= 0) { int subjectEndIndex = messageBody.indexOf("\r", subjectStartIndex); if (subjectEndIndex >= 0) { - subject = messageBody.substring(subjectStartIndex+"Subject: ".length(), subjectEndIndex); + subject = messageBody.substring(subjectStartIndex + "Subject: ".length(), subjectEndIndex); } } if (subject == null) { - subject = "mail"+System.currentTimeMillis(); + subject = "mail" + System.currentTimeMillis(); } session.createMessage(session.getFolderPath(folderName), subject, null, new String(buffer), true); sendClient(commandId + " OK APPEND completed"); @@ -365,14 +404,67 @@ public class ImapConnection extends AbstractConnection { protected String removeQuotes(String value) { String result = value; - if (result.startsWith("\"") || result.startsWith("{")) { + if (result.startsWith("\"") || result.startsWith("{") || result.startsWith("(")) { result = result.substring(1); } - if (result.endsWith("\"") || result.endsWith("}")) { + if (result.endsWith("\"") || result.endsWith("}") || result.endsWith(")")) { result = result.substring(0, result.length() - 1); } return result; } + /** + * Filter to limit output lines to max body lines after header + */ + private static class HeaderOutputStream extends FilterOutputStream { + protected static final int START = 0; + protected static final int CR = 1; + protected static final int CRLF = 2; + protected static final int CRLFCR = 3; + protected static final int BODY = 4; + + protected int state = START; + protected int size = 0; + + public HeaderOutputStream(OutputStream os) { + super(os); + } + + public int size() { + return size; + } + + @Override + public void write(int b) throws IOException { + size++; + if (state != BODY) { + super.write(b); + } + 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; + } + } + } + } + }