diff --git a/src/java/davmail/exchange/ExchangeSession.java b/src/java/davmail/exchange/ExchangeSession.java index 28026dc6..24790f6f 100644 --- a/src/java/davmail/exchange/ExchangeSession.java +++ b/src/java/davmail/exchange/ExchangeSession.java @@ -7,14 +7,15 @@ import org.apache.commons.httpclient.auth.AuthenticationException; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.PutMethod; +import org.apache.commons.httpclient.util.Base64; import org.apache.commons.httpclient.util.URIUtil; import org.apache.log4j.Logger; import org.apache.webdav.lib.Property; import org.apache.webdav.lib.ResponseEntity; import org.apache.webdav.lib.WebdavResource; +import org.apache.webdav.lib.methods.MoveMethod; 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; @@ -452,6 +453,7 @@ public class ExchangeSession { /** * Replace invalid url chars + * * @param subject * @return */ @@ -542,6 +544,9 @@ public class ExchangeSession { message.read = "1".equals(prop.getPropertyAsString()); } else if ("message-id".equals(prop.getLocalName())) { message.messageId = prop.getPropertyAsString(); + if (message.messageId.startsWith("<") && message.messageId.endsWith(">")) { + message.messageId = message.messageId.substring(1, message.messageId.length()-1); + } } } @@ -565,27 +570,27 @@ 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:"); - } + 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(); } + 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 { + + public MessageList getAllMessages(String folderName) throws IOException { String folderUrl = getFolderPath(folderName); - List messages = new ArrayList(); + MessageList messages = new MessageList(); String searchRequest = "Select \"DAV:uid\", \"http://schemas.microsoft.com/mapi/proptag/x0e080003\"" + - " ,\"urn:schemas:mailheader:message-id\", \"urn:schemas:httpmail:read\""+ + " ,\"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"; @@ -597,6 +602,7 @@ public class ExchangeSession { Message message = buildMessage(entity); messages.add(message); } + Collections.sort(messages); return messages; } @@ -606,7 +612,7 @@ 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); @@ -830,14 +836,14 @@ 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) { - HttpException ex = new HttpException(); - ex.setReasonCode(method.getStatusCode()); - ex.setReason(method.getStatusText()); - throw ex; - } + wdr.retrieveSessionInstance().executeMethod(method); + // ok or alredy exists + if (method.getStatusCode() != HttpStatus.SC_MULTI_STATUS && method.getStatusCode() != HttpStatus.SC_METHOD_NOT_ALLOWED) { + HttpException ex = new HttpException(); + ex.setReasonCode(method.getStatusCode()); + ex.setReason(method.getStatusText()); + throw ex; + } } finally { method.releaseConnection(); } @@ -847,19 +853,19 @@ public class ExchangeSession { String folderPath = getFolderPath(folderName); String targetPath = getFolderPath(targetName); MoveMethod method = new MoveMethod(URIUtil.encodePath(folderPath), - URIUtil.encodePath(targetPath)); + 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; - } + 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(); } @@ -885,13 +891,25 @@ public class ExchangeSession { } } - public class Message { + public class Message implements Comparable { public String messageUrl; public String uid; public int size; public String messageId; public boolean read; + public long getUidAsLong() { + byte[] decodedValue = Base64.decode(uid.getBytes()); + + long result = 0; + for (int i = 2; i < 9; i++) { + result = result << 8; + result |= decodedValue[i] & 0xff; + } + + return result; + } + public void write(OutputStream os) throws IOException { HttpMethod method = null; BufferedReader reader = null; @@ -970,6 +988,23 @@ public class ExchangeSession { LOGGER.debug("Deleted to :" + destination + " " + wdr.getStatusCode() + " " + wdr.getStatusMessage()); } + public int compareTo(Object message) { + return (int)(getUidAsLong()-((Message)message).getUidAsLong()); + } + } + + public class MessageList extends ArrayList { + HashMap uidMessageMap = new HashMap(); + + @Override + public boolean add(Message message) { + uidMessageMap.put(message.getUidAsLong(), message); + return super.add(message); + } + + public Message getByUid(long uid) { + return uidMessageMap.get(uid); + } } public WebdavResource getWebDavResource() { diff --git a/src/java/davmail/http/DavGatewayHttpClientFacade.java b/src/java/davmail/http/DavGatewayHttpClientFacade.java index abf8de7f..a2d15c07 100644 --- a/src/java/davmail/http/DavGatewayHttpClientFacade.java +++ b/src/java/davmail/http/DavGatewayHttpClientFacade.java @@ -170,6 +170,7 @@ public final class DavGatewayHttpClientFacade { " " + searchRequest + "\n" + ""; SearchMethod searchMethod = new SearchMethod(URIUtil.encodePath(folderUrl), searchBody); + //searchMethod.setDebug(4); try { int status = httpClient.executeMethod(searchMethod); // Also accept OK sent by buggy servers. diff --git a/src/java/davmail/imap/ImapConnection.java b/src/java/davmail/imap/ImapConnection.java index 90687350..bbf5cf81 100644 --- a/src/java/davmail/imap/ImapConnection.java +++ b/src/java/davmail/imap/ImapConnection.java @@ -27,7 +27,7 @@ public class ImapConnection extends AbstractConnection { protected static final int AUTHENTICATED = 2; ExchangeSession.Folder currentFolder; - List messages; + ExchangeSession.MessageList messages; // Initialize the streams and start the thread public ImapConnection(Socket clientSocket) { @@ -148,8 +148,13 @@ public class ImapConnection extends AbstractConnection { messages = session.getAllMessages(currentFolder.folderUrl); sendClient("* " + currentFolder.objectCount + " EXISTS"); sendClient("* " + currentFolder.objectCount + " RECENT"); - sendClient("* OK [UIDVALIDITY " + currentFolder.lastModified + "]"); - sendClient("* OK [UIDNEXT " + (currentFolder.objectCount + 1) + "]"); + sendClient("* OK [UIDVALIDITY 1]"); + if (messages.size() == 0) { + sendClient("* OK [UIDNEXT " + 1 + "]"); + } else { + sendClient("* OK [UIDNEXT " + (messages.get(messages.size()-1).getUidAsLong()+1) + "]"); + } + //sendClient("* OK [UIDNEXT " + (currentFolder.objectCount + 1) + "]"); sendClient("* FLAGS (\\Answered \\Deleted \\Draft \\Flagged \\Seen $Forwarded $MDNSent Forwarded $Junk $NotJunk Junk JunkRecorded NonJunk NotJunk)"); sendClient("* OK [PERMANENTFLAGS (\\Answered \\Deleted \\Draft \\Flagged \\Seen $Forwarded $MDNSent Forwarded \\*)] junk-related flags are not permanent"); //sendClient("* [UNSEEN 1] first unseen message in inbox"); @@ -187,55 +192,72 @@ public class ImapConnection extends AbstractConnection { if (currentFolder == null) { sendClient(commandId + " NO no folder selected"); } - int startIndex; - int endIndex; - int colonIndex = messageParameter.indexOf(":"); + long startIndex; + long endIndex; + int colonIndex = messageParameter.lastIndexOf(":"); if (colonIndex < 0) { - startIndex = endIndex = Integer.parseInt(messageParameter); + startIndex = endIndex = Long.parseLong(messageParameter); } else { - startIndex = Integer.parseInt(messageParameter.substring(0, colonIndex)); - if (messageParameter.endsWith("*")) { - endIndex = messages.size(); + int commaIndex = messageParameter.indexOf(","); + if (commaIndex > 0) { + // workaround for multiple scopes : start at first and end at last + startIndex = Long.parseLong(messageParameter.substring(0, Math.min(commaIndex, messageParameter.indexOf(":")))); } else { - endIndex = Integer.parseInt(messageParameter.substring(colonIndex + 1)); + startIndex = Long.parseLong(messageParameter.substring(0, colonIndex)); + } + if (messageParameter.endsWith("*")) { + if (messages.size() > 0) { + endIndex = messages.get(messages.size() - 1).getUidAsLong(); + // fix according to spec + if (startIndex > endIndex) { + startIndex = endIndex; + } + } else { + endIndex = 1; + } + } else { + endIndex = Long.parseLong(messageParameter.substring(colonIndex + 1)); } } if ("1:*".equals(messageParameter)) { int count = 0; for (ExchangeSession.Message message : messages) { count++; - sendClient("* " + count + " FETCH (UID " + count + " FLAGS ("+(message.read?"\\Seen":"")+"))"); + sendClient("* " + count + " FETCH (UID " + message.getUidAsLong() + " FLAGS (" + (message.read ? "\\Seen" : "") + "))"); } sendClient(commandId + " OK UID FETCH completed"); } else { if (tokens.hasMoreTokens()) { String parameters = tokens.nextToken(); - for (int messageIndex = startIndex; messageIndex <= endIndex; messageIndex++) { + for (int messageIndex = 1; messageIndex <= messages.size(); messageIndex++) { ExchangeSession.Message message = messages.get(messageIndex - 1); + long uid = message.getUidAsLong(); + if (uid >= startIndex && uid <= endIndex) { - if ("BODYSTRUCTURE".equals(parameters)) { - sendClient("* " + messageIndex + " FETCH (BODYSTRUCTURE (\"TEXT\" \"PLAIN\" (\"CHARSET\" \"windows-1252\") NIL NIL \"QUOTED-PRINTABLE\" " + message.size + " 50 NIL NIL NIL NIL))"); - } else if (parameters.indexOf("BODY[]") >= 0) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - message.write(baos); - baos.close(); + if ("BODYSTRUCTURE".equals(parameters)) { + sendClient("* " + messageIndex + " FETCH (BODYSTRUCTURE (\"TEXT\" \"PLAIN\" (\"CHARSET\" \"windows-1252\") NIL NIL \"QUOTED-PRINTABLE\" " + message.size + " 50 NIL NIL NIL NIL))"); + } else if (parameters.indexOf("BODY[]") >= 0) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + 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(); - HeaderOutputStream headerOutputStream = new HeaderOutputStream(baos); - message.write(headerOutputStream); - baos.close(); - sendClient("* " + messageIndex + " FETCH (UID " + messageIndex + " RFC822.SIZE " + headerOutputStream.size() + " BODY[HEADER.FIELDS ()" + - "] {" + baos.size() + "}"); - os.write(baos.toByteArray()); - os.flush(); - sendClient(" FLAGS ("+(message.read?"\\Seen":"")+"))"); + DavGatewayTray.debug("Messagee size: " + message.size + " actual size:" + baos.size() + " message+headers: " + (message.size + baos.size())); + sendClient("* " + messageIndex + " FETCH (UID " + message.getUidAsLong() + " RFC822.SIZE " + baos.size() + " BODY[]<0>" + + " {" + baos.size() + "}"); + message.write(os); + sendClient(")"); + } else { + // write headers to byte array + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + HeaderOutputStream headerOutputStream = new HeaderOutputStream(baos); + message.write(headerOutputStream); + baos.close(); + sendClient("* " + messageIndex + " FETCH (UID " + message.getUidAsLong() + " RFC822.SIZE " + headerOutputStream.size() + " BODY[HEADER.FIELDS ()" + + "] {" + baos.size() + "}"); + os.write(baos.toByteArray()); + os.flush(); + sendClient(" FLAGS (" + (message.read ? "\\Seen" : "") + "))"); + } } } sendClient(commandId + " OK FETCH completed"); @@ -248,31 +270,31 @@ public class ImapConnection extends AbstractConnection { } else if ("search".equalsIgnoreCase(subcommand)) { // only create check search String messageId = null; - int messageIndex = 0; + long messageUid = 0; while (tokens.hasMoreTokens()) { messageId = tokens.nextToken(); } // reload messages messages = session.getAllMessages(currentFolder.folderName); - for (int i = 0; i < messages.size(); i++) { - if (messageId.equals(messages.get(i).messageId)) { - messageIndex = i + 1; + for (ExchangeSession.Message message : messages) { + if (messageId.equals(message.messageId)) { + messageUid = message.getUidAsLong(); } } - if (messageIndex > 0) { - sendClient("* SEARCH " + messageIndex); + if (messageUid > 0) { + sendClient("* SEARCH " + messageUid); } sendClient(commandId + " OK SEARCH completed"); } else if ("store".equalsIgnoreCase(subcommand)) { - int uid = Integer.parseInt(tokens.nextToken()); + long uid = Long.parseLong(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(); + String flag = flagtokenizer.nextToken(); if ("\\Seen".equals(flag)) { properties.put("read", "0"); } @@ -280,14 +302,14 @@ public class ImapConnection extends AbstractConnection { } else if ("+Flags".equalsIgnoreCase(action)) { StringTokenizer flagtokenizer = new StringTokenizer(flags); while (flagtokenizer.hasMoreTokens()) { - String flag = flagtokenizer.nextToken(); + String flag = flagtokenizer.nextToken(); if ("\\Seen".equals(flag)) { properties.put("read", "1"); } } } // TODO - session.updateMessage(messages.get(uid-1), properties); + session.updateMessage(messages.getByUid(uid), properties); sendClient(commandId + " OK STORE completed"); } } else { @@ -340,13 +362,14 @@ public class ImapConnection extends AbstractConnection { } session.createMessage(session.getFolderPath(folderName), subject, null, new String(buffer), true); sendClient(commandId + " OK APPEND completed"); - } else if ("noop".equalsIgnoreCase(command)) { + } else if ("noop".equalsIgnoreCase(command) || "check".equalsIgnoreCase(command)) { if (currentFolder != null) { currentFolder = session.getFolder(currentFolder.folderName); + messages = session.getAllMessages(currentFolder.folderUrl); sendClient("* " + currentFolder.objectCount + " EXISTS"); sendClient("* " + currentFolder.objectCount + " RECENT"); } - sendClient(commandId + " OK NOOP completed"); + sendClient(commandId + " OK " + command + " completed"); } else { sendClient(commandId + " BAD command unrecognized"); }