diff --git a/src/java/davmail/exchange/ExchangeSession.java b/src/java/davmail/exchange/ExchangeSession.java index 26100572..bcc8da96 100644 --- a/src/java/davmail/exchange/ExchangeSession.java +++ b/src/java/davmail/exchange/ExchangeSession.java @@ -3,7 +3,6 @@ package davmail.exchange; import davmail.Settings; import org.apache.commons.httpclient.*; import org.apache.commons.httpclient.methods.GetMethod; -import org.apache.commons.httpclient.methods.HeadMethod; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.PutMethod; import org.apache.commons.httpclient.util.URIUtil; @@ -11,15 +10,7 @@ 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.jdom.Attribute; -import org.jdom.input.DOMBuilder; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NamedNodeMap; -import org.w3c.dom.Node; -import org.w3c.tidy.Tidy; -import javax.mail.MessagingException; import javax.mail.internet.MimeUtility; import java.io.*; import java.net.HttpURLConnection; @@ -42,69 +33,18 @@ public class ExchangeSession { static { MESSAGE_REQUEST_PROPERTIES.add("DAV:uid"); - MESSAGE_REQUEST_PROPERTIES.add("urn:schemas:httpmail:subject"); - MESSAGE_REQUEST_PROPERTIES.add("urn:schemas:mailheader:mime-version"); MESSAGE_REQUEST_PROPERTIES.add("urn:schemas:mailheader:content-class"); - MESSAGE_REQUEST_PROPERTIES.add("urn:schemas:httpmail:hasattachment"); - MESSAGE_REQUEST_PROPERTIES.add("urn:schemas:httpmail:read"); - // needed only when full headers not found - MESSAGE_REQUEST_PROPERTIES.add("urn:schemas:mailheader:received"); - MESSAGE_REQUEST_PROPERTIES.add("urn:schemas:mailheader:date"); - MESSAGE_REQUEST_PROPERTIES.add("urn:schemas:mailheader:message-id"); - MESSAGE_REQUEST_PROPERTIES.add("urn:schemas:mailheader:thread-topic"); - MESSAGE_REQUEST_PROPERTIES.add("urn:schemas:mailheader:thread-index"); - MESSAGE_REQUEST_PROPERTIES.add("urn:schemas:mailheader:from"); - MESSAGE_REQUEST_PROPERTIES.add("urn:schemas:mailheader:to"); - MESSAGE_REQUEST_PROPERTIES.add("urn:schemas:httpmail:priority"); - - // full headers - MESSAGE_REQUEST_PROPERTIES.add("http://schemas.microsoft.com/mapi/proptag/x0007D001E"); - // mail body - MESSAGE_REQUEST_PROPERTIES.add("http://schemas.microsoft.com/mapi/proptag/x01000001E"); - // html body - MESSAGE_REQUEST_PROPERTIES.add("urn:schemas:httpmail:htmldescription"); - // same as htmldescription, remove - // MESSAGE_REQUEST_PROPERTIES.add("http://schemas.microsoft.com/mapi/proptag/x01013001E"); // size MESSAGE_REQUEST_PROPERTIES.add("http://schemas.microsoft.com/mapi/proptag/x0e080003"); - // only for calendar events - MESSAGE_REQUEST_PROPERTIES.add("urn:schemas:calendar:location"); - MESSAGE_REQUEST_PROPERTIES.add("urn:schemas:calendar:dtstart"); - MESSAGE_REQUEST_PROPERTIES.add("urn:schemas:calendar:dtend"); - MESSAGE_REQUEST_PROPERTIES.add("urn:schemas:calendar:instancetype"); - MESSAGE_REQUEST_PROPERTIES.add("urn:schemas:calendar:busystatus"); - MESSAGE_REQUEST_PROPERTIES.add("urn:schemas:calendar:meetingstatus"); - MESSAGE_REQUEST_PROPERTIES.add("urn:schemas:calendar:alldayevent"); - MESSAGE_REQUEST_PROPERTIES.add("urn:schemas:calendar:responserequested"); - MESSAGE_REQUEST_PROPERTIES.add("urn:schemas:mailheader:cc"); - } - public static final HashMap PRIORITIES = new HashMap(); - - static { - PRIORITIES.put("-2", "5 (Lowest)"); - PRIORITIES.put("-1", "4 (Low)"); - PRIORITIES.put("1", "2 (High)"); - PRIORITIES.put("2", "1 (Highest)"); - } - - public static final String CONTENT_TYPE_HEADER = "content-type: "; - public static final String CONTENT_TRANSFER_ENCODING_HEADER = "content-transfer-encoding: "; - public static final String CONTENT_DISPOSITION_HEADER = "content-disposition: "; - public static final String CONTENT_ID_HEADER = "content-id: "; - private static final int DEFAULT_KEEP_DELAY = 30; /** * Date parser from Exchange format */ private final SimpleDateFormat dateParser; - /** - * Date formatter to MIME format - */ - private final SimpleDateFormat dateFormatter; /** * Various standard mail boxes Urls @@ -130,7 +70,6 @@ public class ExchangeSession { // each session dateParser = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); dateParser.setTimeZone(new SimpleTimeZone(0, "GMT")); - dateFormatter = new java.text.SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.ENGLISH); } /** @@ -459,48 +398,15 @@ public class ExchangeSession { while (propertiesEnum.hasMoreElements()) { Property prop = (Property) propertiesEnum.nextElement(); String localName = prop.getLocalName(); - if ("x0007D001E".equals(localName)) { - message.fullHeaders = prop.getPropertyAsString(); - } else if ("x01000001E".equals(localName)) { - message.body = prop.getPropertyAsString(); - } else if ("x0e080003".equals(localName)) { + + if ("x0e080003".equals(localName)) { message.size = Integer.parseInt(prop.getPropertyAsString()); - } else if ("htmldescription".equals(localName)) { - message.htmlBody = prop.getPropertyAsString(); } else if ("uid".equals(localName)) { message.uid = prop.getPropertyAsString(); } else if ("content-class".equals(prop.getLocalName())) { message.contentClass = prop.getPropertyAsString(); - } else if (("hasattachment").equals(prop.getLocalName())) { - message.hasAttachment = "1".equals(prop.getPropertyAsString()); - } else if ("received".equals(prop.getLocalName())) { - message.received = prop.getPropertyAsString(); - } else if ("read".equals(prop.getLocalName())) { - message.read = "1".equals(prop.getPropertyAsString()); - } else if ("date".equals(prop.getLocalName())) { - message.date = prop.getPropertyAsString(); - } else if ("message-id".equals(prop.getLocalName())) { - message.messageId = prop.getPropertyAsString(); - } else if ("thread-topic".equals(prop.getLocalName())) { - message.threadTopic = prop.getPropertyAsString(); - } else if ("thread-index".equals(prop.getLocalName())) { - message.threadIndex = 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; - } } } - message.preProcessHeaders(); return message; } @@ -527,11 +433,11 @@ public class ExchangeSession { public List getAllMessages() throws IOException { List messages = new ArrayList(); - //wdr.setDebug(4); - //wdr.propfindMethod(currentFolderUrl, 1); + wdr.setDebug(4); + wdr.propfindMethod(currentFolderUrl, 1); // one level search Enumeration folderEnum = wdr.propfindMethod(currentFolderUrl, 1, MESSAGE_REQUEST_PROPERTIES); - //wdr.setDebug(0); + wdr.setDebug(0); while (folderEnum.hasMoreElements()) { ResponseEntity entity = (ResponseEntity) folderEnum.nextElement(); @@ -539,6 +445,7 @@ public class ExchangeSession { if ("urn:content-classes:message".equals(message.contentClass) || "urn:content-classes:calendarmessage".equals(message.contentClass) || "urn:content-classes:recallmessage".equals(message.contentClass) || + "urn:content-classes:appointment".equals(message.contentClass) || "urn:content-classes:dsn".equals(message.contentClass)) { messages.add(message); } @@ -690,462 +597,57 @@ public class ExchangeSession { } public class Message { - public static final String CONTENT_TYPE_HEADER = "Content-Type: "; - public static final String CONTENT_TRANSFER_ENCODING_HEADER = "Content-Transfer-Encoding: "; public String messageUrl; public String uid; public int size; - public String fullHeaders; - public String body; - public String htmlBody; public String contentClass; - public boolean hasAttachment; - public boolean hasInlineAttachment; - public String to; - public String cc; - public String date; - public String messageId; - public String received; - public String threadIndex; - public String threadTopic; - public String from; - public String subject; - public String priority; - public boolean read; - - protected List attachments; - - // attachment index used during write - protected int attachmentIndex; - - protected String getReceived() { - StringTokenizer st = new StringTokenizer(received, "\r\n"); - StringBuffer result = new StringBuffer(); - while (st.hasMoreTokens()) { - result.append("Received: ").append(st.nextToken()).append("\r\n"); - } - return result.toString(); - } - - protected void preProcessHeaders() { - // only handle exchange messages - if (!"urn:content-classes:message".equals(contentClass) && - !"urn:content-classes:calendarmessage".equals(contentClass) && - !"urn:content-classes:recallmessage".equals(contentClass) - ) { - return; - } - - // fullHeaders seem empty sometimes, rebuild it - if (fullHeaders == null || fullHeaders.length() == 0) { - try { - if (date.length() > 0) { - Date parsedDate = dateParser.parse(date); - date = dateFormatter.format(parsedDate); - } - fullHeaders = "Skipped header\n" + - getReceived() + - "MIME-Version: 1.0\n" + - "Content-Type: application/ms-tnef;\n" + - "\tname=\"winmail.dat\"\n" + - "Content-Transfer-Encoding: binary\n" + - "Content-class: " + contentClass + "\n" + - "Subject: " + MimeUtility.encodeText(subject) + "\n" + - "Date: " + date + "\n" + - "Message-ID: " + messageId + "\n" + - "Thread-Topic: " + MimeUtility.encodeText(threadTopic) + "\n" + - "Thread-Index: " + threadIndex + "\n" + - "From: " + from + "\n" + - "To: " + to + "\n"; - if (priority != null) { - fullHeaders += "X-Priority: " + priority + "\n"; - } - if (cc != null && cc.length() > 0) { - fullHeaders += "CC: " + cc + "\n"; - } - } catch (ParseException e) { - throw new RuntimeException("Unable to rebuild header " + e + " " + e.getMessage()); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("Unable to rebuild header " + e + " " + e.getMessage()); - } - } - StringBuffer result = new StringBuffer(); - boolean mstnefDetected = false; - String boundary = null; - BufferedReader reader = null; - try { - reader = new BufferedReader(new StringReader(fullHeaders)); - String line = reader.readLine(); - while (line != null && line.length() > 0) { - // patch exchange Content type - if (line.equals(CONTENT_TYPE_HEADER + "application/ms-tnef;")) { - if (hasAttachment) { - // trigger attachments load - loadAttachments(); - boundary = "----_=_NextPart_001_" + uid; - String contentType = "multipart/mixed"; - // use multipart/related with inline images - if (hasInlineAttachment) { - contentType = "multipart/related"; - } - line = CONTENT_TYPE_HEADER + contentType + ";\n\tboundary=\"" + boundary + "\""; - } else { - line = CONTENT_TYPE_HEADER + "text/html; charset=UTF-8"; - } - // skip winmail.dat - reader.readLine(); - mstnefDetected = true; - - } else if (line.startsWith(CONTENT_TYPE_HEADER)) { - if (line.endsWith(";")) { - result.append(line); - result.append("\n"); - - // boundary is on next line - line = reader.readLine(); - } - if (line != null && line.indexOf("boundary=\"") >= 0) { - boundary = line.substring(line.indexOf("boundary=\"") + 10, line.length() - 1); - } - - } else if (line.startsWith(CONTENT_TRANSFER_ENCODING_HEADER)) { - if (hasAttachment && mstnefDetected) { - line = null; - } - } - - if (line != null) { - result.append(line); - result.append("\n"); - } - line = reader.readLine(); - } - - // look for ms-tnef inside mime part headers - while (line != null) { - if (line.equals(CONTENT_TYPE_HEADER + "application/ms-tnef;")) { - LOGGER.debug("application/ms-tnef detected inside part headers"); - mstnefDetected = true; - } - line = reader.readLine(); - } - - // exchange message : create mime part headers - if (boundary != null && mstnefDetected) { - loadAttachments(); - // TODO : test actual header values - result.append("\n--").append(boundary) - .append("\nContent-Type: text/html; charset=UTF-8") - .append("\nContent-Transfer-Encoding: 7bit") - .append("\n\n"); - - for (Attachment attachment : attachments) { - String attachmentContentType = getAttachmentContentType(attachment.href); - String attachmentContentEncoding = "base64"; - if (attachmentContentType.startsWith("text/")) { - attachmentContentEncoding = "quoted-printable"; - } else if (attachmentContentType.startsWith("message/rfc822")) { - attachmentContentEncoding = "7bit"; - } - - result.append("\n--").append(boundary) - .append("\nContent-Type: ") - .append(attachmentContentType) - .append(";") - .append("\n\tname=\"").append(attachment.name).append("\""); - if (attachment.contentid != null) { - result.append("\nContent-ID: <") - .append(attachment.contentid) - .append(">"); - } - - result.append("\nContent-Transfer-Encoding: ").append(attachmentContentEncoding) - .append("\n\n"); - } - - // end parts - result.append("--").append(boundary).append("--\n"); - } - if (mstnefDetected) { - fullHeaders = result.toString(); - // also fix invalid body characters - htmlBody = htmlBody.replaceAll("’", "'"); - } - - } catch (IOException e) { - throw new RuntimeException("Unable to preprocess headers " + e + " " + e.getMessage()); - } finally { - if (reader != null) { - try { - reader.close(); - } catch (IOException e) { - LOGGER.warn("Error closing header reader", e); - } - } - } - } - public void write(OutputStream os) throws IOException { - // TODO : filter submessage headers in fullHeaders + HttpMethod method = null; BufferedReader reader = null; try { - reader = new BufferedReader(new StringReader(fullHeaders)); - // skip first line - reader.readLine(); - MimeHeader mimeHeader = new MimeHeader(); - mimeHeader.processHeaders(reader, os, null); - // non MIME message without attachments, append body - if (mimeHeader.boundary == null) { - os.write('\r'); - os.write('\n'); - writeBody(os, mimeHeader); + method = new GetMethod(URIUtil.encodePath(messageUrl)); + method.setRequestHeader("Content-Type", "text/xml; charset=utf-8"); + method.setRequestHeader("Translate", "f"); + wdr.retrieveSessionInstance().executeMethod(method); - if (hasAttachment) { - os.write("**warning : missing attachments**".getBytes()); - } - } else { - attachmentIndex = 0; + boolean inHTML = false; - loadAttachments(); - writeMimeMessage(reader, os, mimeHeader); - if (attachmentIndex - 1 != attachments.size()) { - LOGGER.error("Found " + (attachmentIndex - 1) + " attachments, expected " + attachments.size()); + reader = new BufferedReader(new InputStreamReader(method.getResponseBodyAsStream())); + OutputStreamWriter isoWriter = new OutputStreamWriter(os); + String line; + while ((line = reader.readLine()) != null) { + if (".".equals(line)) { + line = ".."; + // detect html body to patch Exchange html body + } else if (line.startsWith("".equals(line)) { + inHTML = false; } + if (inHTML) { + line = line.replaceAll("’", "'"); + } + isoWriter.write(line); + isoWriter.write((char) 13); + isoWriter.write((char) 10); } - os.flush(); + isoWriter.flush(); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { - LOGGER.warn("Error closing header reader", e); + LOGGER.warn("Error closing message input stream", e); } } - } - } - - public void writeMimeMessage(BufferedReader reader, OutputStream os, MimeHeader mimeHeader) throws IOException { - String line; - // with alternative, there are two body forms (plain+html) - // with multipart/report server sends plain and delivery-status - if ("multipart/alternative".equalsIgnoreCase(mimeHeader.contentType) || - "multipart/report".equalsIgnoreCase(mimeHeader.contentType)) { - attachmentIndex--; - } - - while (((line = reader.readLine()) != null) && !line.equals(mimeHeader.boundary + "--")) { - os.write(line.getBytes()); - os.write('\r'); - os.write('\n'); - - // detect part boundary start - if (line.equals(mimeHeader.boundary)) { - Attachment currentAttachment; - String currentAttachmentName = null; - if (attachmentIndex > 0 && attachmentIndex <= attachments.size()) { - currentAttachment = attachments.get(attachmentIndex - 1); - currentAttachmentName = currentAttachment.name; - } - - // process current part header - MimeHeader partHeader = new MimeHeader(); - partHeader.processHeaders(reader, os, currentAttachmentName); - - // detect inner mime message - if (partHeader.contentType != null - && partHeader.contentType.startsWith("multipart") - && partHeader.boundary != null) { - writeMimeMessage(reader, os, partHeader); - } else if (attachmentIndex <= 0) { - // body part - attachmentIndex++; - writeBody(os, partHeader); - } else { - Attachment attachment = getAttachment(partHeader, attachmentIndex - 1); - - attachmentIndex++; - if (attachment == null) { - // only warn, could happen depending on IIS config - //throw new IOException("Attachment " + partHeader.name + " not found in " + messageUrl); - LOGGER.warn("Attachment " + partHeader.name + " not found in " + messageUrl); - } else { - writeAttachment(os, partHeader, attachment); - } - } + if (method != null) { + method.releaseConnection(); } } - - // write mime end marker - if (line != null) { - os.write(line.getBytes()); - os.write('\r'); - os.write('\n'); - } - // failover for invalid mime headers : at least write body - if (attachmentIndex == 0) { - MimeHeader bodyHeader = new MimeHeader(); - bodyHeader.contentType = "text/html"; - bodyHeader.charset = "utf-8"; - StringBuffer headerBuffer = new StringBuffer(); - headerBuffer.append(mimeHeader.boundary).append("\r\n"); - headerBuffer.append("Content-Type: text/html; charset=UTF-8\r\n"); - headerBuffer.append("Content-Transfer-Encoding: 7bit\r\n"); - headerBuffer.append("\r\n"); - os.write(headerBuffer.toString().getBytes()); - writeBody(os, bodyHeader); - os.write((mimeHeader.boundary + "--\r\n").getBytes()); - } - } - - protected void writeAttachment(OutputStream os, MimeHeader mimeHeader, Attachment attachment) throws IOException { - try { - // quotedOs must not be closed : this would close the underlying outputstream - OutputStream quotedOs; - try { - // try another base64Encoder implementation - if ("base64".equalsIgnoreCase(mimeHeader.contentTransferEncoding)) { - //noinspection IOResourceOpenedButNotSafelyClosed - quotedOs = new BASE64EncoderStream(os); - } else { - quotedOs = (MimeUtility.encode(os, mimeHeader.contentTransferEncoding)); - } - } catch (MessagingException e) { - throw new IOException(e + " " + e.getMessage()); - } - - if ("message/rfc822".equalsIgnoreCase(mimeHeader.contentType)) { - String messageAttachmentPath = null; - - // Get real attachment path from owa page content - GetMethod method = new GetMethod(URIUtil.encodePath(attachment.href)); - wdr.retrieveSessionInstance().executeMethod(method); - String body = method.getResponseBodyAsString(); - final String URL_DECLARATION = "var g_szURL\t= \""; - int messageUrlBeginIndex = body.indexOf(URL_DECLARATION); - if (messageUrlBeginIndex >= 0) { - messageUrlBeginIndex = messageUrlBeginIndex + URL_DECLARATION.length(); - int messageUrlEndIndex = body.indexOf("/\"", messageUrlBeginIndex); - if (messageUrlBeginIndex >= 0) { - messageAttachmentPath = URIUtil.decode(body.substring(messageUrlBeginIndex, messageUrlEndIndex)); - } - } - - // failover, exchange mail attachment - if (messageAttachmentPath == null) { - messageAttachmentPath = messageUrl + "/" + attachment.name; - } - - Message attachedMessage = getMessage(messageAttachmentPath); - attachedMessage.write(quotedOs); - } else { - - GetMethod method = new GetMethod(URIUtil.encodePath(attachment.href)); - wdr.retrieveSessionInstance().executeMethod(method); - - // encode attachment - BufferedInputStream bis = null; - try { - bis = new BufferedInputStream(method.getResponseBodyAsStream()); - byte[] buffer = new byte[4096]; - int count; - while ((count = bis.read(buffer)) >= 0) { - quotedOs.write(buffer, 0, count); - } - } finally { - if (bis != null) { - try { - bis.close(); - } catch (IOException e) { - LOGGER.warn("Error closing message input stream", e); - } - } - } - quotedOs.flush(); - os.write('\r'); - os.write('\n'); - } - - } catch (HttpException e) { - throw new IOException(e + " " + e.getMessage()); - } - } - - protected void writeBody(OutputStream os, MimeHeader mimeHeader) throws IOException { - OutputStream quotedOs; - try { - // double dot filter : avoid end of message in body - quotedOs = new FilterOutputStream(os) { - byte state = 0; - - public void write(int achar) throws IOException { - if (achar == 13 && state != 3) { - state = 1; - } else if (achar == 10 && state == 1) { - state = 2; - } else if (achar == '.' && state == 2) { - state = 3; - } else if (achar == 13) { - state = 0; - super.write('.'); - } else { - state = 0; - } - super.write(achar); - } - }; - quotedOs = (MimeUtility.encode(quotedOs, mimeHeader.contentTransferEncoding)); - } catch (MessagingException e) { - throw new IOException(e + " " + e.getMessage()); - } - String currentBody = null; - if ("text/html".equalsIgnoreCase(mimeHeader.contentType)) { - currentBody = htmlBody; - // patch charset if null and html body encoded - if (mimeHeader.charset == null) { - String delimiter = " 0) { - mimeHeader.charset = htmlBody.substring(startIndex, endIndex); - } - } - } - - } else if (mimeHeader.contentType == null || - "text/plain".equalsIgnoreCase(mimeHeader.contentType)) { - currentBody = body; - } - if (currentBody != null) { - if (mimeHeader.charset != null) { - try { - quotedOs.write(currentBody.getBytes(MimeUtility.javaCharset(mimeHeader.charset))); - } catch (UnsupportedEncodingException uee) { - // TODO : try to decode other encodings - quotedOs.write(currentBody.getBytes()); - } - } else { - quotedOs.write(currentBody.getBytes()); - } - quotedOs.flush(); - os.write('\r'); - os.write('\n'); - } } public void delete() throws IOException { - // TODO : refactor String destination = deleteditemsUrl + messageUrl.substring(messageUrl.lastIndexOf("/")); LOGGER.debug("Deleting : " + messageUrl + " to " + destination); @@ -1161,494 +663,6 @@ public class ExchangeSession { LOGGER.debug("Deleted to :" + destination + " " + wdr.getStatusCode() + " " + wdr.getStatusMessage()); } - public void printHeaders(OutputStream os) throws IOException { - String line; - BufferedReader reader = null; - try { - reader = new BufferedReader(new StringReader(fullHeaders)); - // skip first line - reader.readLine(); - line = reader.readLine(); - while (line != null && line.length() > 0) { - os.write(line.getBytes()); - os.write('\r'); - os.write('\n'); - line = reader.readLine(); - } - } finally { - if (reader != null) { - try { - reader.close(); - } catch (IOException e) { - LOGGER.warn("Error closing header reader", e); - } - } - } - } - - /** - * Custom head method to force connection close (needed when attachment filtered by IIS) - */ - protected class ConnectionCloseHeadMethod extends HeadMethod { - public ConnectionCloseHeadMethod(String url) { - super(url); - } - - public boolean isConnectionCloseForced() { - // force connection if attachment not found - return getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND; - } - - } - - protected String getAttachmentContentType(String attachmentUrl) { - String result; - try { - String decodedPath = URIUtil.decode(attachmentUrl); - LOGGER.debug("Head " + decodedPath); - - ConnectionCloseHeadMethod method = new ConnectionCloseHeadMethod(URIUtil.encodePathQuery(decodedPath)); - wdr.retrieveSessionInstance().executeMethod(method); - if (method.getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) { - method.releaseConnection(); - System.err.println("Unable to retrieve attachment"); - } - result = method.getResponseHeader("Content-Type").getValue(); - method.releaseConnection(); - - } catch (Exception e) { - throw new RuntimeException("Exception retrieving " + attachmentUrl + " : " + e + " " + e.getCause()); - } - // fix content type for known extension - if ("application/octet-stream".equalsIgnoreCase(result)) { - if (attachmentUrl.endsWith(".pdf") || attachmentUrl.endsWith(".PDF")) { - result = "application/pdf"; - } else if (attachmentUrl.endsWith(".zip") || attachmentUrl.endsWith(".ZIP")) { - result = "application/zip"; - } - } - return result; - - } - - protected XmlDocument tidyDocument(InputStream inputStream) { - Tidy tidy = new Tidy(); - tidy.setXmlTags(false); //treat input not XML - tidy.setQuiet(true); - tidy.setShowWarnings(false); - tidy.setDocType("omit"); - - DOMBuilder builder = new DOMBuilder(); - XmlDocument xmlDocument = new XmlDocument(); - try { - Document w3cDocument = tidy.parseDOM(inputStream, null); - Element documentElement = w3cDocument.getDocumentElement(); - - if (documentElement != null) { - // Fix broken Office xml document with empty namespace - NamedNodeMap namedNodeMap = w3cDocument.getDocumentElement().getAttributes(); - for (int i = 0; i < namedNodeMap.getLength(); i++) { - Node node = namedNodeMap.item(i); - String nodeName = node.getNodeName(); - String nodeValue = node.getNodeValue(); - if (nodeName != null && nodeName.startsWith("xmlns") - && (nodeValue == null || nodeValue.length() == 0)) { - w3cDocument.getDocumentElement().removeAttribute(nodeName); - } - } - } - xmlDocument.load(builder.build(w3cDocument)); - } catch (Exception ex1) { - LOGGER.error("Exception parsing document", ex1); - } - return xmlDocument; - } - - public void loadAttachments() throws IOException { - // do not load attachments twice - if (attachments == null) { - - GetMethod getMethod = new GetMethod(URIUtil.encodePathQuery(messageUrl + "?Cmd=Open&unfiltered=1")); - wdr.retrieveSessionInstance().executeMethod(getMethod); - if (getMethod.getStatusCode() != HttpURLConnection.HTTP_OK) { - throw new IOException("Unable to get attachments: " + getMethod.getStatusCode() - + " " + getMethod.getStatusLine()); - } - - XmlDocument xmlDocument = tidyDocument(getMethod.getResponseBodyAsStream()); - // Release the connection. - getMethod.releaseConnection(); - - attachments = new ArrayList(); - int attachmentIndex = 1; - // fix empty body in dsn report - if (body == null || body.length() == 0) { - body = xmlDocument.getValue("//textarea"); - } - // list file attachments identified explicitly - List list = xmlDocument.getNodes("//table[@id='idAttachmentWell']//a/@href"); - for (Attribute element : list) { - String attachmentHref = element.getValue(); - // exclude empty links (owa buttons) - if (!"#".equals(attachmentHref)) { - final String ATTACH_QUERY = "?attach=1"; - if (attachmentHref.endsWith(ATTACH_QUERY)) { - attachmentHref = attachmentHref.substring(0, attachmentHref.length() - ATTACH_QUERY.length()); - } - // url is encoded - attachmentHref = URIUtil.decode(attachmentHref); - // exclude external URLs - if (attachmentHref.startsWith(messageUrl)) { - String attachmentName = attachmentHref.substring(messageUrl.length() + 1); - // yet another case, attached messages ? - final String ANOTHER_MULTIPART_STRING = "1_multipart/2_"; - int anotherMultipartIndex = attachmentName.indexOf(ANOTHER_MULTIPART_STRING); - if (anotherMultipartIndex >= 0) { - attachmentName = attachmentName.substring(anotherMultipartIndex + ANOTHER_MULTIPART_STRING.length()); - } - - int slashIndex = attachmentName.indexOf('/'); - if (slashIndex >= 0) { - attachmentName = attachmentName.substring(0, slashIndex); - } - // attachmentName is now right for Exchange messages, need to handle external MIME messages - final String MULTIPART_STRING = "1_multipart_xF8FF_"; - if (attachmentName.startsWith(MULTIPART_STRING)) { - attachmentName = attachmentName.substring(MULTIPART_STRING.length()); - int underscoreIndex = attachmentName.indexOf('_'); - if (underscoreIndex >= 0) { - attachmentName = attachmentName.substring(underscoreIndex + 1); - } - } - // decode slashes in attachment name - attachmentName = attachmentName.replaceAll("_xF8FF_", "/"); - // decode tilde - attachmentName = attachmentName.replaceAll("_x007E_", "~"); - // trim attachment name - attachmentName = attachmentName.trim(); - - Attachment attachment = new Attachment(); - attachment.name = attachmentName; - attachment.href = attachmentHref; - - attachments.add(attachment); - LOGGER.debug("Attachment " + (attachmentIndex++) + " : " + attachmentName); - } else { - LOGGER.warn("Message URL : " + messageUrl + " is not a substring of attachment URL : " + attachmentHref); - } - } - } - - // HTML body may be empty - if (htmlBody != null && htmlBody.length() > 0) { - // get inline images from htmlBody (without OWA transformation) - ByteArrayInputStream bais = new ByteArrayInputStream(htmlBody - // quick fix remove default office namespace - .replaceFirst("xmlns=\".*\"", "") - // quick fix remove inline processing instructions - .replaceAll("<\\?xml:namespace", "") - .replaceAll("<\\?XML:NAMESPACE", "") - // quick fix invalid comments - .replaceAll("