/**
*
*/
package fr.cedrik.inotes;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpCookie;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.stream.XMLStreamException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.LineIterator;
import org.apache.commons.lang3.CharEncoding;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.text.translate.EntityArrays;
import org.apache.commons.lang3.text.translate.LookupTranslator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.mail.MailParseException;
import fr.cedrik.email.FoldersList;
import fr.cedrik.email.MessagesMetaData;
import fr.cedrik.email.spi.Message;
import fr.cedrik.util.IteratorChain;
/**
* @author Cédrik LIME
*/
public class Session implements fr.cedrik.email.spi.Session {
private static final int META_DATA_LOAD_BATCH_SIZE = 500;
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
protected final HttpContext context;
protected final Set<String> mailsToDelete = new HashSet<String>();
protected final Set<String> mailsToMarkUnread = new HashSet<String>();
protected final Set<String> mailsToMarkRead = new HashSet<String>();
protected final Set<String> mailsToMarkUnreadAll = new HashSet<String>();
protected final Set<String> mailsToMarkReadAll = new HashSet<String>();
protected final Set<String> noticesToDelete = new HashSet<String>();
protected MessagesMetaData<MessageMetaData> allMessagesCache = null;
protected MessagesMetaData<MeetingNoticeMetaData> allNoticesCache = null;
protected boolean isLoggedIn = false;
protected FoldersList folders = new FoldersList();
static {
// System.setProperty("java.util.logging.config.file", "logging.properties");//XXX DEBUG
}
public Session(INotesProperties iNotes) {
context = new HttpContext(iNotes);
}
protected void trace(ClientHttpRequest httpRequest, ClientHttpResponse httpResponse) throws IOException {
if (logger.isDebugEnabled()) {
logger.debug(httpRequest.getMethod().toString() + ' ' + httpResponse.getRawStatusCode() + ' ' + httpResponse.getStatusText() + ' ' + httpRequest.getURI());
}
}
private void traceBody(ClientHttpResponse httpResponse) throws IOException {
if (logger.isTraceEnabled()) {
String responseBody = IOUtils.toString(httpResponse.getBody(), context.getCharset(httpResponse));
logger.trace(responseBody);
}
}
@Override
public String getServerAddress() {
return context.iNotes.getServerAddress();
}
@Override
public void setServerAddress(URL url) {
if (isLoggedIn) {
throw new IllegalStateException();
}
context.iNotes.setServerAddress(url);
}
@Override
public FoldersList getFolders() {
if (! isLoggedIn) {
throw new IllegalStateException();
}
return folders;
}
@Override
public void setCurrentFolder(fr.cedrik.email.spi.Folder folder) throws IOException {
if (isLoggedIn) {
cleanup();
}
context.setCurrentFolderId(folder.getId());
allMessagesCache = null;
allNoticesCache = null;
}
@Override
public boolean login(String userName, String password) throws IOException {
context.setUserName(userName);
context.setUserPassword(password);
Map<String, Object> params = new HashMap<String, Object>();
ClientHttpRequest httpRequest;
ClientHttpResponse httpResponse;
String responseBody;
// Step 1a: login (auth)
{
params.clear();
params.put("%%ModDate", "0000000100000000");
params.put("RedirectTo", "/dwaredirect.nsf");
params.put("password", context.getUserPassword());
params.put("username", context.getUserName());
httpRequest = context.createRequest(new URL(context.getServerAddress() + "/names.nsf?Login"), HttpMethod.POST, params);
httpResponse = httpRequest.execute();
trace(httpRequest, httpResponse);
context.rememberCookies(httpRequest, httpResponse);
if (logger.isTraceEnabled()) {
traceBody(httpResponse);
}
try {
if (httpResponse.getStatusCode().series().equals(HttpStatus.Series.REDIRECTION)) {
logger.debug("Initial authentication successful for user \"" + context.getUserName() + '"');
logger.debug("Redirect: {}", httpResponse.getHeaders().getLocation());
} else if (httpResponse.getStatusCode().series().equals(HttpStatus.Series.SUCCESSFUL)) {
// body will contain "Invalid username or password was specified."
logger.warn("ERROR while authenticating user \""+context.getUserName()+"\". Please check your parameters in " + INotesProperties.FILE);
return false;
} else {
logger.error("Unknown server response while authenticating user \""+context.getUserName()+"\": " + httpResponse.getRawStatusCode() + ' ' + httpResponse.getStatusText());
return false;
}
} finally {
httpResponse.close();
}
}
// Step 1b: login (iNotesSRV cookie + base url)
{
params.clear();
httpRequest = context.createRequest(new URL(context.getServerAddress() + "/dwaredirect.nsf"), HttpMethod.GET, params);
httpResponse = httpRequest.execute();
trace(httpRequest, httpResponse);
context.rememberCookies(httpRequest, httpResponse);
responseBody = IOUtils.toString(httpResponse.getBody(), context.getCharset(httpResponse));
try {
if (! httpResponse.getStatusCode().series().equals(HttpStatus.Series.SUCCESSFUL)) {
logger.error("Unknown server response while authenticating user \""+context.getUserName()+"\": " + httpResponse.getRawStatusCode() + ' ' + httpResponse.getStatusText());
return false;
}
} finally {
httpResponse.close();
}
if (logger.isTraceEnabled()) {
logger.trace(responseBody);
}
}
// search for additional cookie
{
Pattern jsCookie = Pattern.compile("<script language=javascript>document\\.cookie='([^']+)';</script>", Pattern.CASE_INSENSITIVE);
Matcher jsCookieMatcher = jsCookie.matcher(responseBody);
assert jsCookieMatcher.groupCount() == 1 ; jsCookieMatcher.groupCount();
while (jsCookieMatcher.find()) {
String cookieStr = jsCookieMatcher.group(1);
logger.trace("Found additional cookie: {}", cookieStr);
List<HttpCookie> cookies = HttpCookie.parse(cookieStr);
for (HttpCookie cookie : cookies) {
cookie.setSecure("https".equalsIgnoreCase(httpRequest.getURI().getScheme()));
cookie.setPath("/");
logger.trace("Adding cookie: {}", cookie);
context.getCookieStore().add(httpRequest.getURI(), cookie);
// context.getHttpHeaders().add("Cookie", cookie.toString());// hack, since the previous line does not work correctly when using the JVM default CookieHandler
}
}
}
// search for redirect
final String redirectURL;
{
Pattern htmlRedirect = Pattern.compile("<META HTTP-EQUIV=\"refresh\" content=\"\\d;URL=([^\"]+)\">", Pattern.CASE_INSENSITIVE);
Matcher htmlRedirectMatcher = htmlRedirect.matcher(responseBody);
assert htmlRedirectMatcher.groupCount() == 1 ; htmlRedirectMatcher.groupCount();
if (htmlRedirectMatcher.find()) {
redirectURL = htmlRedirectMatcher.group(1);
logger.trace("Found redirect URL: {}", redirectURL);
} else {
logger.error("Can not find the redirect URL; aborting. Response body:\n" + responseBody);
return false;
}
if (htmlRedirectMatcher.find()) {
logger.error("Found more than 1 redirect URL; aborting. Response body:\n" + responseBody);
return false;
}
}
responseBody = null;
// Step 1c: base URL
{
String baseURL = redirectURL.substring(0, redirectURL.indexOf(".nsf")+".nsf".length()) + '/';
context.setProxyBaseURL(baseURL + "iNotes/Proxy/?OpenDocument");
context.setFolderBaseURL(baseURL);
context.setMailEditBaseURL(baseURL + "iNotes/Mail/?EditDocument");
logger.trace("Proxy base URL for user \"{}\": {}", context.getUserName(), context.getProxyBaseURL());
logger.trace("Folder base URL for user \"{}\": {}", context.getUserName(), context.getFolderBaseURL());
logger.trace("Mail edit base URL for user \"{}\": {}", context.getUserName(), context.getMailEditBaseURL());
}
// Step 1d: login (ShimmerS cookie)
{
params.clear();
// need to emulate a real browser, or else we get an "unknown browser" response with no possibility to continue
context.getHttpHeaders().set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:24.0) Gecko/20100101 Firefox/24.0");
// context.getHttpHeaders().set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/536.30.1 (KHTML, like Gecko) Version/6.0.5 Safari/536.30.1");
httpRequest = context.createRequest(new URL(redirectURL), HttpMethod.GET, params);
httpResponse = httpRequest.execute();
trace(httpRequest, httpResponse);
context.rememberCookies(httpRequest, httpResponse);
responseBody = IOUtils.toString(httpResponse.getBody(), context.getCharset(httpResponse));
// Apparently we don't need to parse the embeded JS to set the "Shimmer" cookie.
try {
if (! httpResponse.getStatusCode().series().equals(HttpStatus.Series.SUCCESSFUL)) {
logger.error("Unknown server response while authenticating user \""+context.getUserName()+"\": " + httpResponse.getRawStatusCode() + ' ' + httpResponse.getStatusText());
return false;
}
} finally {
httpResponse.close();
}
if (logger.isTraceEnabled()) {
logger.trace(responseBody);
}
}
// Step 1e: get available folders
{
// parse the response to get the SessionFrame URL
String sessionFrameURL;
{
Pattern sessionFrame = Pattern.compile("<frame name=\"s_SessionFrame\" src=\"([^\"]+Form=l_SessionFrame[^\"]+)\".*>", Pattern.CASE_INSENSITIVE);
Matcher sessionFrameMatcher = sessionFrame.matcher(responseBody);
assert sessionFrameMatcher.groupCount() == 1 ; sessionFrameMatcher.groupCount();
if (sessionFrameMatcher.find()) {
sessionFrameURL = sessionFrameMatcher.group(1);
if (! sessionFrameURL.toLowerCase().startsWith("http")) {
URI uri = httpRequest.getURI();
String uriString = uri.toString();
sessionFrameURL = uriString.substring(0, uriString.indexOf(uri.getRawPath())) + sessionFrameURL;
}
logger.trace("Found l_SessionFrame URL: {}", sessionFrameURL);
} else {
logger.error("Can not find the l_SessionFrame URL; aborting. Response body:\n" + responseBody);
return false;
}
if (sessionFrameMatcher.find()) {
logger.error("Found more than 1 l_SessionFrame URL; aborting. Response body:\n" + responseBody);
return false;
}
}
// play the SessionFrame URL to get the SessionInfo URL
{
params.clear();
httpRequest = context.createRequest(new URL(sessionFrameURL), HttpMethod.GET, params);
httpResponse = httpRequest.execute();
trace(httpRequest, httpResponse);
context.rememberCookies(httpRequest, httpResponse);
responseBody = IOUtils.toString(httpResponse.getBody(), context.getCharset(httpResponse));
try {
if (! httpResponse.getStatusCode().series().equals(HttpStatus.Series.SUCCESSFUL)) {
logger.error("Unknown server response while authenticating user \""+context.getUserName()+"\": " + httpResponse.getRawStatusCode() + ' ' + httpResponse.getStatusText());
return false;
}
} finally {
httpResponse.close();
}
if (logger.isTraceEnabled()) {
logger.trace(responseBody);
}
}
String sessionInfoURL;
{
Pattern sessionInfo = Pattern.compile("<script src=\"([^\"]+Form=f_SessionInfo[^\"]+)\".*>", Pattern.CASE_INSENSITIVE);
Matcher sessionInfoMatcher = sessionInfo.matcher(responseBody);
assert sessionInfoMatcher.groupCount() == 1 ; sessionInfoMatcher.groupCount();
if (sessionInfoMatcher.find()) {
sessionInfoURL = sessionInfoMatcher.group(1);
if (! sessionInfoURL.toLowerCase().startsWith("http")) {
URI uri = httpRequest.getURI();
String uriString = uri.toString();
sessionInfoURL = uriString.substring(0, uriString.indexOf(uri.getRawPath())) + sessionInfoURL;
}
logger.trace("Found s_SessionInfo URL: {}", sessionInfoURL);
} else {
logger.error("Can not find the s_SessionInfo URL; aborting. Response body:\n" + responseBody);
return false;
}
if (sessionInfoMatcher.find()) {
logger.error("Found more than 1 s_SessionInfo URL; aborting. Response body:\n" + responseBody);
return false;
}
}
// play the SessionInfo URL to parse the folders (this also contains the Domino server name)
{
params.clear();
httpRequest = context.createRequest(new URL(sessionInfoURL), HttpMethod.GET, params);
httpResponse = httpRequest.execute();
trace(httpRequest, httpResponse);
context.rememberCookies(httpRequest, httpResponse);
responseBody = IOUtils.toString(httpResponse.getBody(), context.getCharset(httpResponse));
try {
if (! httpResponse.getStatusCode().series().equals(HttpStatus.Series.SUCCESSFUL)) {
logger.error("Unknown server response while authenticating user \""+context.getUserName()+"\": " + httpResponse.getRawStatusCode() + ' ' + httpResponse.getStatusText());
return false;
}
} finally {
httpResponse.close();
}
if (logger.isTraceEnabled()) {
logger.trace(responseBody);
}
}
{
Pattern jsArray = Pattern.compile("new Array\\(\"([.0-9]*?)\",(\\d+),'(.*?)','.*?','(.*?)',\".*?\"\\)", Pattern.CASE_INSENSITIVE);
Matcher jsArrayMatcher = jsArray.matcher(responseBody);
assert jsArrayMatcher.groupCount() == 4 ; jsArrayMatcher.groupCount();
List<String> excludedFoldersIds = context.getExcludedFoldersIds();
while (jsArrayMatcher.find()) {
String levelTree = jsArrayMatcher.group(1);
int levelNumber = Integer.parseInt(jsArrayMatcher.group(2));
String name = StringEscapeUtils.unescapeEcmaScript(jsArrayMatcher.group(3));
String url = jsArrayMatcher.group(4);
if (StringUtils.isNotEmpty(url) && url.contains(".nsf/")) {
int startIndex = url.indexOf(".nsf/") + ".nsf/".length();
int endIndex = url.indexOf('/', startIndex+1);
String id = url.substring(startIndex, endIndex);
// filter folders to exclude non-user things like "Forums", "Rules", "Stationery" etc.
if (! (levelNumber <= 1 && excludedFoldersIds.contains(id))) {
Folder folder = new Folder();
folder.levelTree = levelTree;
folder.levelNumber = levelNumber;
folder.name = name;
folder.id = id;
folders.add(folder);
logger.debug("Found iNotes folder {}", folder);
}
}
}
}
}
responseBody = null;
// Step 1f: X-IBM-INOTES-NONCE header
{
List<HttpCookie> cookies = context.getCookieStore().getCookies();
for (HttpCookie cookie : cookies) {
if ("ShimmerS".equals(cookie.getName())) {
if (context.getHttpHeaders().containsKey("X-IBM-INOTES-NONCE")) {
logger.error("Multiple cookies \"ShimmerS\" in store; aborting.");
return false;
}
String xIbmINotesNonce;
Pattern shimmerS = Pattern.compile("&N:(\\p{XDigit}+)");
Matcher shimmerSMatcher = shimmerS.matcher(cookie.getValue());
assert shimmerSMatcher.groupCount() == 1 ; shimmerSMatcher.groupCount();
if (shimmerSMatcher.find()) {
xIbmINotesNonce = shimmerSMatcher.group(1);
logger.trace("Found X-IBM-INOTES-NONCE: {}", xIbmINotesNonce);
} else {
logger.error("Can not find X-IBM-INOTES-NONCE; aborting. ShimmerS cookie: " + cookie);
return false;
}
if (shimmerSMatcher.find()) {
logger.error("Found more than 1 X-IBM-INOTES-NONCE; aborting. ShimmerS cookie: " + cookie);
return false;
}
context.getHttpHeaders().set("X-IBM-INOTES-NONCE", xIbmINotesNonce);
}
}
}
logger.info("Authentication successful for user \"" + context.getUserName() + '"');
isLoggedIn = true;
return true;
}
protected void checkLoggedIn() {
if (! isLoggedIn) {
throw new IllegalStateException();
}
}
@Override
public MessagesMetaData<MessageMetaData> getMessagesMetaData() throws IOException {
if (allMessagesCache != null) {
return allMessagesCache;
}
allMessagesCache = getMessagesMetaData(null, null, Integer.MAX_VALUE);
return allMessagesCache;
}
@Override
public MessagesMetaData<MessageMetaData> getMessagesMetaData(int count) throws IOException {
return getMessagesMetaData(null, null, count);
}
@Override
public MessagesMetaData<MessageMetaData> getMessagesMetaData(Date oldestMessageToFetch) throws IOException {
return getMessagesMetaData(oldestMessageToFetch, null, Integer.MAX_VALUE);
}
@Override
public MessagesMetaData<MessageMetaData> getMessagesMetaData(Date oldestMessageToFetch, Date newestMessageToFetch) throws IOException {
return getMessagesMetaData(oldestMessageToFetch, newestMessageToFetch, Integer.MAX_VALUE);
}
protected MessagesMetaData<MessageMetaData> getMessagesMetaData(Date oldestMessageToFetch, Date newestMessageToFetch, int count) throws IOException {
checkLoggedIn();
if (oldestMessageToFetch == null) {
oldestMessageToFetch = new Date(0);
}
if (newestMessageToFetch == null) {
newestMessageToFetch = new Date(Long.MAX_VALUE);
}
// iNotes limits the number of results to 1000. Need to paginate.
int start = 1, currentCount = 0;
MessagesMetaData<MessageMetaData> messages = null, partialMessages;
boolean stopLoading = false;
do {
partialMessages = getMessagesMetaDataNoSort(start, Math.min(count - currentCount, META_DATA_LOAD_BATCH_SIZE));
if (messages == null) {
messages = partialMessages;
// filter on date
Iterator<MessageMetaData> iterator = messages.entries.iterator();
while (iterator.hasNext()) {
BaseINotesMessage message = iterator.next();
if (message.getDate().before(oldestMessageToFetch)) {
iterator.remove();
} else if (message.getDate().after(newestMessageToFetch)) {
iterator.remove();
}
}
} else {
for (MessageMetaData message : partialMessages.entries) {
if (message.getDate().before(oldestMessageToFetch)) {
stopLoading = true;
break;
} else if (message.getDate().after(newestMessageToFetch)) {
stopLoading = false;
continue;
} else {
messages.entries.add(message);
}
}
}
start += META_DATA_LOAD_BATCH_SIZE;
currentCount = messages.entries.size();
} while (! stopLoading && partialMessages.entries.size() >= Math.min(count - currentCount, META_DATA_LOAD_BATCH_SIZE) && currentCount < count);
Collections.reverse(messages.entries);
logger.trace("Loaded {} messages metadata", Integer.valueOf(messages.entries.size()));
return messages;
}
/**
* @return set of messages meta-data, in iNotes order (most recent first)
*/
protected MessagesMetaData<MessageMetaData> getMessagesMetaDataNoSort(int start, int count) throws IOException {
checkLoggedIn();
Map<String, Object> params = new HashMap<String, Object>();
params.put("charset", CharEncoding.UTF_8);
params.put("Form", "s_ReadViewEntries");
// params.put("PresetFields", "DBQuotaInfo;1,FolderName;"+context.getNotesFolderName()+",UnreadCountInfo;1,s_UsingHttps;1,hc;$98,noPI;1");
params.put("TZType", "UTC");
params.put("Start", Integer.toString(start));
params.put("Count", Integer.toString(count));
params.put("resortdescending", "5");
ClientHttpRequest httpRequest = context.createRequest(new URL(context.getProxyBaseURL()+"&PresetFields=DBQuotaInfo;1,FolderName;"+context.getCurrentFolderId()+",UnreadCountInfo;1,s_UsingHttps;1,hc;$98,noPI;1"), HttpMethod.GET, params);
ClientHttpResponse httpResponse = httpRequest.execute();
trace(httpRequest, httpResponse);
// traceBody(httpResponse);// DEBUG
if (! httpResponse.getStatusCode().series().equals(HttpStatus.Series.SUCCESSFUL)) {
logger.error("Unknown server response while fetching messages meta-data for user \""+context.getUserName()+"\": " + httpResponse.getRawStatusCode() + ' ' + httpResponse.getStatusText());
httpResponse.close();
return null;
}
MessagesMetaData<MessageMetaData> messages;
try {
messages = new MessagesXMLConverter().convertXML(httpResponse.getBody(), context.getCharset(httpResponse));
} catch (XMLStreamException e) {
throw new IOException(e);
} finally {
httpResponse.close();
}
return messages;
}
/**
* Don't forget to call {@link LineIterator#close()} when done with the response!
*
* @param message
* @return
* @throws IOException
* @throws MailParseException if the content of the email is invalid (i.e. iNotes http session has expired)
*/
public IteratorChain<String> getMessageMIMEHeaders(MessageMetaData message) throws IOException, MailParseException {
checkLoggedIn();
Map<String, Object> params = new HashMap<String, Object>();
params.put("charset", CharEncoding.UTF_8);
params.put("Form", "l_MailMessageHeader");
ClientHttpRequest httpRequest = context.createRequest(new URL(context.getFolderBaseURL()+message.getId()+"/?OpenDocument"), HttpMethod.GET, params);
ClientHttpResponse httpResponse = httpRequest.execute();
trace(httpRequest, httpResponse);
if (! httpResponse.getStatusCode().series().equals(HttpStatus.Series.SUCCESSFUL)) {
logger.error("Unknown server response while fetching message MIME headers for user \""+context.getUserName()+"\": " + httpResponse.getRawStatusCode() + ' ' + httpResponse.getStatusText());
httpResponse.close();
return null;
}
LineIterator responseLines = new HttpCleaningLineIterator(httpResponse);
//httpResponse.close();// done in HttpLineIterator#close()
if (message.unread) {
// exporting (read MIME) marks mail as read. Need to get the read/unread information and set it back!
mailsToMarkUnread.add(message.getId());
}
return new IteratorChain<String>(getINotesData(message).iterator(), responseLines);
}
/**
* Don't forget to call {@link LineIterator#close()} when done with the response!
*
* @param message
* @return
* @throws IOException
* @throws MailParseException if the content of the email is invalid (i.e. iNotes http session has expired)
*/
public IteratorChain<String> getMessageMIME(MessageMetaData message) throws IOException, MailParseException {
checkLoggedIn();
Map<String, Object> params = new HashMap<String, Object>();
params.put("charset", CharEncoding.UTF_8);
params.put("Form", "l_MailMessageHeader");
// params.put("PresetFields", "FullMessage;1");
ClientHttpRequest httpRequest = context.createRequest(new URL(context.getFolderBaseURL()+message.getId()+"/?OpenDocument&PresetFields=FullMessage;1"), HttpMethod.GET, params);
ClientHttpResponse httpResponse = httpRequest.execute();
trace(httpRequest, httpResponse);
if (! httpResponse.getStatusCode().series().equals(HttpStatus.Series.SUCCESSFUL)) {
logger.error("Unknown server response while fetching message MIME for user \""+context.getUserName()+"\": " + httpResponse.getRawStatusCode() + ' ' + httpResponse.getStatusText());
httpResponse.close();
return null;
}
LineIterator responseLines = new HttpCleaningLineIterator(httpResponse);
//httpResponse.close();// done in HttpLineIterator#close()
if (message.unread) {
// exporting (read MIME) marks mail as read. Need to get the read/unread information and set it back!
mailsToMarkUnread.add(message.getId());
}
return new IteratorChain<String>(getINotesData(message).iterator(), responseLines);
}
protected List<String> getINotesData(MessageMetaData message) throws IOException {
List<String> iNotes = new ArrayList<String>(5);
iNotes.add("X-iNotes-unid: " + message.unid);
iNotes.add("X-iNotes-noteid: " + message.noteid);
iNotes.add("X-iNotes-unread: " + message.unread);
iNotes.add("X-iNotes-date: " + fr.cedrik.util.DateUtils.RFC2822_DATE_TIME_FORMAT.format(message.date));
iNotes.add("X-iNotes-size: " + message.size);
return Collections.unmodifiableList(iNotes);
}
protected List<String> getINotesData(MeetingNoticeMetaData message) throws IOException {
List<String> iNotes = new ArrayList<String>(5);
iNotes.add("X-iNotes-unid: " + message.unid);
iNotes.add("X-iNotes-noteid: " + message.noteid);
iNotes.add("X-iNotes-date: " + fr.cedrik.util.DateUtils.RFC2822_DATE_TIME_FORMAT.format(message.date));
return Collections.unmodifiableList(iNotes);
}
/**
* will be done server-side on logout
* @param messages
* @throws IOException
*/
public void deleteMessage(MessageMetaData... messages) throws IOException {
checkLoggedIn();
for (MessageMetaData message : messages) {
mailsToDelete.add(message.getId());
mailsToMarkRead.remove(message.getId());
mailsToMarkUnread.remove(message.getId());
}
}
/**
* will be done server-side on logout
* @param messages
* @throws IOException
*/
public void deleteMessage(MeetingNoticeMetaData... messages) throws IOException {
checkLoggedIn();
for (MeetingNoticeMetaData message : messages) {
noticesToDelete.add(message.getId());
}
}
/**
* will be done server-side on logout
* @param messages
* @throws IOException
*/
@Override
public void deleteMessage(Collection<? extends Message> messages) throws IOException {
deleteMessage(messages.toArray(new Message[messages.size()]));
}
/**
* will be done server-side on logout
* @param messages
* @throws IOException
*/
@Override
public void deleteMessage(Message... messages) throws IOException {
checkLoggedIn();
for (Message message : messages) {
if (message instanceof MessageMetaData) {
deleteMessage((MessageMetaData) message);
} else if (message instanceof MeetingNoticeMetaData) {
deleteMessage((MeetingNoticeMetaData) message);
} else {
throw new IllegalArgumentException(message.getClass().toString());
}
}
}
@Override
public void undeleteAllMessages() {
checkLoggedIn();
mailsToDelete.clear();
mailsToMarkRead.clear();
mailsToMarkRead.addAll(mailsToMarkReadAll);
mailsToMarkUnread.clear();
mailsToMarkUnread.addAll(mailsToMarkUnreadAll);
noticesToDelete.clear();
}
protected void doDeleteMessages() throws IOException {
if (mailsToDelete.isEmpty()) {
return;
}
Map<String, Object> params = new HashMap<String, Object>();
params.put("Form", "l_HaikuErrorStatusJSON");
params.put("ui", "dwa_form");
params.put("h_EditAction", "h_Next");
params.put("h_SetReturnURL", "[[./&Form=s_CallBlankScript]]");
params.put("h_AllDocs", "");
params.put("h_FolderStorage", "");
params.put("s_ViewName", context.getCurrentFolderId());
params.put("h_SetCommand", "h_DeletePages");
params.put("h_SetEditNextScene", "l_HaikuErrorStatusJSON");
params.put("h_SetDeleteList", StringUtils.join(mailsToDelete, ';'));
params.put("h_SetDeleteListCS", "");
ClientHttpRequest httpRequest = context.createRequest(new URL(context.getMailEditBaseURL()), HttpMethod.POST, params);
ClientHttpResponse httpResponse = httpRequest.execute();
trace(httpRequest, httpResponse);
if (logger.isTraceEnabled()) {
traceBody(httpResponse);
}
try {
if (! httpResponse.getStatusCode().series().equals(HttpStatus.Series.SUCCESSFUL)) {
logger.error("Unknown server response while deleting messages for user \""+context.getUserName()+"\": " + httpResponse.getRawStatusCode() + ' ' + httpResponse.getStatusText());
return;
}
} finally {
httpResponse.close();
}
logger.info("Deleted (moved to Trash) {} messsage(s): {}", mailsToDelete.size(), StringUtils.join(mailsToDelete, ';'));
mailsToMarkReadAll.removeAll(mailsToDelete);
mailsToMarkUnreadAll.removeAll(mailsToDelete);
mailsToDelete.clear();
}
protected void doDeleteNotices() throws IOException {
if (noticesToDelete.isEmpty()) {
return;
}
Map<String, Object> params = new HashMap<String, Object>();
params.put("Form", "l_HaikuErrorStatusJSON");
params.put("ui", "dwa_form");
params.put("h_EditAction", "h_Next");
params.put("h_SetReturnURL", "[[./&Form=s_CallBlankScript]]");
params.put("h_AllDocs", "");
params.put("h_FolderStorage", "");
params.put("s_ViewName", Folder.MEETING_NOTICES);
params.put("h_SetCommand", "h_ShimmerCSRemoveFromFolder");
params.put("h_SetEditNextScene", "l_HaikuErrorStatusJSON");
params.put("h_SetDeleteList", "");
params.put("h_SetDeleteListCS", StringUtils.join(noticesToDelete, ';'));
ClientHttpRequest httpRequest = context.createRequest(new URL(context.getMailEditBaseURL()), HttpMethod.POST, params);
ClientHttpResponse httpResponse = httpRequest.execute();
trace(httpRequest, httpResponse);
if (logger.isTraceEnabled()) {
traceBody(httpResponse);
}
try {
if (! httpResponse.getStatusCode().series().equals(HttpStatus.Series.SUCCESSFUL)) {
logger.error("Unknown server response while removing notices from view for user \""+context.getUserName()+"\": " + httpResponse.getRawStatusCode() + ' ' + httpResponse.getStatusText());
return;
}
} finally {
httpResponse.close();
}
logger.info("Deleted (removed from view) {} messsage(s): {}", noticesToDelete.size(), StringUtils.join(noticesToDelete, ';'));
noticesToDelete.clear();
}
/**
* will be done server-side on logout
* @param messages
* @throws IOException
*/
public void markMessagesRead(MessageMetaData... messages) throws IOException {
checkLoggedIn();
for (MessageMetaData message : messages) {
String id = message.getId();
mailsToMarkUnreadAll.remove(id);
mailsToMarkReadAll.add(id);
if (! mailsToDelete.contains(id)) {
mailsToMarkUnread.remove(id);
mailsToMarkRead.add(id);
}
}
}
protected void doMarkMessagesRead() throws IOException {
if (mailsToMarkRead.isEmpty()) {
return;
}
Map<String, Object> params = new HashMap<String, Object>();
params.put("Form", "l_HaikuErrorStatusJSON");
params.put("ui", "dwa_form");
params.put("s_ViewName", context.getCurrentFolderId());
params.put("h_AllDocs", "");
params.put("h_SetCommand", "h_ShimmerMarkRead");
params.put("h_SetReturnURL", "[[./&Form=s_CallBlankScript]]");
params.put("h_EditAction", "h_Next");
params.put("h_SetEditNextScene", "l_HaikuErrorStatusJSON");
params.put("h_SetDeleteList", StringUtils.join(mailsToMarkRead, ';'));
params.put("h_SetDeleteListCS", "");
ClientHttpRequest httpRequest = context.createRequest(new URL(context.getMailEditBaseURL()), HttpMethod.POST, params);
ClientHttpResponse httpResponse = httpRequest.execute();
trace(httpRequest, httpResponse);
if (logger.isTraceEnabled()) {
traceBody(httpResponse);
}
try {
if (! httpResponse.getStatusCode().series().equals(HttpStatus.Series.SUCCESSFUL)) {
logger.error("Unknown server response while marking messages read for user \""+context.getUserName()+"\": " + httpResponse.getRawStatusCode() + ' ' + httpResponse.getStatusText());
return;
}
} finally {
httpResponse.close();
}
logger.info("Marked {} messsage(s) as read in folder {}: {}", mailsToMarkRead.size(), context.getCurrentFolderId(), StringUtils.join(mailsToMarkRead, ';'));
mailsToMarkReadAll.removeAll(mailsToMarkRead);
mailsToMarkRead.clear();
}
/**
* will be done server-side on logout
* @param messages
* @throws IOException
*/
public void markMessagesUnread(MessageMetaData... messages) throws IOException {
checkLoggedIn();
for (MessageMetaData message : messages) {
String id = message.getId();
mailsToMarkReadAll.remove(id);
mailsToMarkUnreadAll.add(id);
if (! mailsToDelete.contains(id)) {
mailsToMarkRead.remove(id);
mailsToMarkUnread.add(id);
}
}
}
protected void doMarkMessagesUnread() throws IOException {
if (mailsToMarkUnread.isEmpty()) {
return;
}
Map<String, Object> params = new HashMap<String, Object>();
params.put("Form", "l_HaikuErrorStatusJSON");
params.put("ui", "dwa_form");
// params.put("PresetFields", "s_NoMarkRead;1");
params.put("s_ViewName", context.getCurrentFolderId());
params.put("h_AllDocs", "");
params.put("h_SetCommand", "h_ShimmerMarkUnread");
params.put("h_SetReturnURL", "[[./&Form=s_CallBlankScript]]");
params.put("h_EditAction", "h_Next");
params.put("h_SetEditNextScene", "l_HaikuErrorStatusJSON");
params.put("h_SetDeleteList", StringUtils.join(mailsToMarkUnread, ';'));
params.put("h_SetDeleteListCS", "");
ClientHttpRequest httpRequest = context.createRequest(new URL(context.getMailEditBaseURL()+"&PresetFields=s_NoMarkRead;1"), HttpMethod.POST, params);
ClientHttpResponse httpResponse = httpRequest.execute();
trace(httpRequest, httpResponse);
if (logger.isTraceEnabled()) {
traceBody(httpResponse);
}
try {
if (! httpResponse.getStatusCode().series().equals(HttpStatus.Series.SUCCESSFUL)) {
logger.error("Unknown server response while marking messages unread for user \""+context.getUserName()+"\": " + httpResponse.getRawStatusCode() + ' ' + httpResponse.getStatusText());
return;
}
} finally {
httpResponse.close();
}
logger.info("Marked {} messsage(s) as unread in folder {}: {}", mailsToMarkUnread.size(), context.getCurrentFolderId(), StringUtils.join(mailsToMarkUnread, ';'));
mailsToMarkUnreadAll.removeAll(mailsToMarkUnread);
mailsToMarkUnread.clear();
}
public MessagesMetaData<MeetingNoticeMetaData> getMeetingNoticesMetaData() throws IOException {
if (allNoticesCache != null) {
return allNoticesCache;
}
allNoticesCache = getMeetingNoticesMetaData(null, null, Integer.MAX_VALUE);
return allNoticesCache;
}
public MessagesMetaData<MeetingNoticeMetaData> getMeetingNoticesMetaData(int count) throws IOException {
return getMeetingNoticesMetaData(null, null, count);
}
public MessagesMetaData<MeetingNoticeMetaData> getMeetingNoticesMetaData(Date oldestMessageToFetch) throws IOException {
return getMeetingNoticesMetaData(oldestMessageToFetch, null, Integer.MAX_VALUE);
}
public MessagesMetaData<MeetingNoticeMetaData> getMeetingNoticesMetaData(Date oldestMessageToFetch, Date newestMessageToFetch) throws IOException {
return getMeetingNoticesMetaData(oldestMessageToFetch, newestMessageToFetch, Integer.MAX_VALUE);
}
public MessagesMetaData<MeetingNoticeMetaData> getMeetingNoticesMetaData(Date oldestMessageToFetch, Date newestMessageToFetch, int count) throws IOException {
checkLoggedIn();
cleanup();
if (oldestMessageToFetch == null) {
oldestMessageToFetch = new Date(0);
}
if (newestMessageToFetch == null) {
newestMessageToFetch = new Date(Long.MAX_VALUE);
}
String notesFolderIdBackup = context.getCurrentFolderId();
context.setCurrentFolderId(Folder.MEETING_NOTICES);
// iNotes limits the number of results to 1000. Need to paginate.
int start = 1, currentCount = 0;
MessagesMetaData<MeetingNoticeMetaData> notices = null, partialNotices;
boolean stopLoading = false;
do {
partialNotices = getMeetingNoticesMetaDataNoSort(start, Math.min(count - currentCount, META_DATA_LOAD_BATCH_SIZE));
if (notices == null) {
notices = partialNotices;
// filter on date
Iterator<MeetingNoticeMetaData> iterator = notices.entries.iterator();
while (iterator.hasNext()) {
BaseINotesMessage notice = iterator.next();
if (notice.getDate().before(oldestMessageToFetch)) {
iterator.remove();
} else if (notice.getDate().after(newestMessageToFetch)) {
iterator.remove();
}
}
} else {
for (MeetingNoticeMetaData notice : partialNotices.entries) {
if (notice.getDate().before(oldestMessageToFetch)) {
stopLoading = true;
break;
} else if (notice.getDate().after(newestMessageToFetch)) {
stopLoading = false;
continue;
} else {
notices.entries.add(notice);
}
}
}
start += META_DATA_LOAD_BATCH_SIZE;
currentCount = notices.entries.size();
} while (! stopLoading && partialNotices.entries.size() >= Math.min(count - currentCount, META_DATA_LOAD_BATCH_SIZE) && currentCount < count);
context.setCurrentFolderId(notesFolderIdBackup);
Collections.reverse(notices.entries);
logger.trace("Loaded {} meeting notices metadata", Integer.valueOf(notices.entries.size()));
return notices;
}
/**
* @return set of meeting notices meta-data, in iNotes order
*/
protected MessagesMetaData<MeetingNoticeMetaData> getMeetingNoticesMetaDataNoSort(int start, int count) throws IOException {
checkLoggedIn();
String notesFolderIdBackup = context.getCurrentFolderId();
context.setCurrentFolderId(Folder.MEETING_NOTICES);
Map<String, Object> params = new HashMap<String, Object>();
params.put("charset", CharEncoding.UTF_8);
params.put("Form", "s_ReadViewEntries");
// params.put("PresetFields", "DBQuotaInfo;1,FolderName;"+context.getNotesFolderName()+",UnreadCountInfo;1,s_UsingHttps;1,hc;$98,noPI;1");
params.put("TZType", "UTC");
params.put("Start", Integer.toString(start));
params.put("Count", Integer.toString(count));
ClientHttpRequest httpRequest = context.createRequest(new URL(context.getProxyBaseURL()+"&PresetFields=DBQuotaInfo;1,FolderName;"+Folder.MEETING_NOTICES+",s_UsingHttps;1,hc;AltChair,noPI;1"), HttpMethod.GET, params);
ClientHttpResponse httpResponse = httpRequest.execute();
trace(httpRequest, httpResponse);
// traceBody(httpResponse);// DEBUG
context.setCurrentFolderId(notesFolderIdBackup);
if (! httpResponse.getStatusCode().series().equals(HttpStatus.Series.SUCCESSFUL)) {
logger.error("Unknown server response while fetching metting notices meta-data for user \""+context.getUserName()+"\": " + httpResponse.getRawStatusCode() + ' ' + httpResponse.getStatusText());
httpResponse.close();
return null;
}
MessagesMetaData<MeetingNoticeMetaData> notices;
try {
notices = new MeetingNoticesXMLConverter().convertXML(httpResponse.getBody(), context.getCharset(httpResponse));
} catch (XMLStreamException e) {
throw new IOException(e);
} finally {
httpResponse.close();
}
return notices;
}
// public Calendar getMeetingNoticeICS(MeetingNoticeMetaData meetingNotice) throws IOException {
// checkLoggedIn();
// String notesFolderIdBackup = context.getNotesFolderId();
// context.setNotesFolderId(Folder.MEETING_NOTICES);
// Map<String, Object> params = new HashMap<String, Object>();
// params.put("charset", CharEncoding.UTF_8);
// params.put("Form", "l_JSVars");
// ClientHttpRequest httpRequest = context.createRequest(new URL(context.getFolderBaseURL()+meetingNotice.getId()+"/?OpenDocument"+"&PresetFields=s_HandleAttachmentNames;1,s_HandleMime;1,s_OpenUI;1,s_HideRemoteImage;1"), HttpMethod.GET, params);
// ClientHttpResponse httpResponse = httpRequest.execute();
// trace(httpRequest, httpResponse);
// context.setNotesFolderId(notesFolderIdBackup);
// if (! httpResponse.getStatusCode().series().equals(HttpStatus.Series.SUCCESSFUL)) {
// logger.error("Unknown server response while fetching Meeting Notice for user \""+context.getUserName()+"\": " + httpResponse.getRawStatusCode() + ' ' + httpResponse.getStatusText());
// httpResponse.close();
// return null;
// }
// Calendar ics;
// try {
// ics = new MeetingNoticeJSONConverter().convertJSON(httpResponse.getBody(), context.getCharset(httpResponse));
// } finally {
// httpResponse.close();
// }
// return ics;
// }
//
// /**
// * Don't forget to call {@link LineIterator#close()} when done with the response!
// *
// * @param message
// * @return
// * @throws IOException
// */
// public IteratorChain<String> getMessageMIME(MeetingNoticeMetaData message) throws IOException {
// Calendar ics = getMeetingNoticeICS(message);
// try {
// Part email = ICSUtils.toEMail(ics);
// ByteArrayOutputStream out = new ByteArrayOutputStream(Math.max(1024, (int)(email.getSize()*1.1)));
// email.writeTo(out);
// BufferedReader in = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(out.toByteArray())));
// out = null;
// return new IteratorChain<String>(getINotesData(message).iterator(), new LineIterator(in));
// } catch (MessagingException e) {
// throw new IOException(e);
// }
// }
//
// /**
// * Don't forget to call {@link LineIterator#close()} when done with the response!
// *
// * @param message
// * @return
// * @throws IOException
// */
// public IteratorChain<String> getMessageMIMEHeaders(MeetingNoticeMetaData message) throws IOException {
// Calendar ics = getMeetingNoticeICS(message);
// try {
// Part email = ICSUtils.toEMail(ics);
// ByteArrayOutputStream out = new ByteArrayOutputStream(Math.max(1024, (int)(email.getSize()*1.1)));
// email.writeTo(out);
// BufferedReader in = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(out.toByteArray())));
// out = null;
// List<String> headers = new ArrayList<String>();
// String currentLine;
// while (StringUtils.isNotBlank(currentLine = in.readLine())) {
// headers.add(currentLine);
// }
// return new IteratorChain<String>(getINotesData(message).iterator(), headers.iterator());
// } catch (MessagingException e) {
// throw new IOException(e);
// }
// }
/**
* Don't forget to call {@link LineIterator#close()} when done with the response!
*
* @param message
* @return
* @throws IOException
* @throws MailParseException if the content of the email is invalid (i.e. iNotes http session has expired)
*/
@Override
public IteratorChain<String> getMessageMIME(Message message) throws IOException, MailParseException {
if (message instanceof MessageMetaData) {
return getMessageMIME((MessageMetaData)message);
// } else if (message instanceof MeetingNoticeMetaData) {
// return getMessageMIME((MeetingNoticeMetaData)message);
} else {
throw new IllegalArgumentException(message.getClass().toString());
}
}
/**
* Don't forget to call {@link LineIterator#close()} when done with the response!
*
* @param message
* @return
* @throws IOException
* @throws MailParseException if the content of the email is invalid (i.e. iNotes http session has expired)
*/
@Override
public IteratorChain<String> getMessageMIMEHeaders(Message message) throws IOException, MailParseException {
if (message instanceof MessageMetaData) {
return getMessageMIMEHeaders((MessageMetaData)message);
// } else if (message instanceof MeetingNoticeMetaData) {
// return getMessageMIMEHeaders((MeetingNoticeMetaData)message);
} else {
throw new IllegalArgumentException(message.getClass().toString());
}
}
public MessagesMetaData<? extends Message> getMessagesAndMeetingNoticesMetaData() throws IOException {
return getMessagesMetaData();
//FIXME uncomment when we find a way to export meeting invites!
// if (Folder.INBOX.equals(context.getNotesFolderId()) || Folder.ALL.equals(context.getNotesFolderId())) {
// MessagesMetaData<MessageMetaData> messagesMetaData = getMessagesMetaData();
// MessagesMetaData<MeetingNoticeMetaData> noticesMetaData = getMeetingNoticesMetaData();
// MessagesMetaData<Message> result = messagesMetaData.clone();
// result.entries.clear();
// result.entries.addAll(messagesMetaData.entries);
// result.entries.addAll(noticesMetaData.entries);
// Collections.sort(result.entries, new Comparator<Message>() {
// @Override
// public int compare(Message o1, Message o2) {
// return o1.getDate().compareTo(o2.getDate());
// }
// });
// return result;
// } else {
// return getMessagesMetaData();
// }
}
public MessagesMetaData<? extends BaseINotesMessage> getMessagesAndMeetingNoticesMetaData(Date oldestMessageToFetch) throws IOException {
return getMessagesAndMeetingNoticesMetaData(oldestMessageToFetch, null);
}
public MessagesMetaData<? extends BaseINotesMessage> getMessagesAndMeetingNoticesMetaData(Date oldestMessageToFetch, Date newestMessageToFetch) throws IOException {
return getMessagesMetaData(oldestMessageToFetch, newestMessageToFetch);
//FIXME uncomment when we find a way to export meeting invites!
// if (Folder.INBOX.equals(context.getNotesFolderId()) || Folder.ALL.equals(context.getNotesFolderId())) {
// MessagesMetaData<MessageMetaData> messagesMetaData = getMessagesMetaData(oldestMessageToFetch, newestMessageToFetch);
// MessagesMetaData<MeetingNoticeMetaData> noticesMetaData = getMeetingNoticesMetaData(oldestMessageToFetch, newestMessageToFetch);
// MessagesMetaData<Message> result = messagesMetaData.clone();
// result.entries.clear();
// result.entries.addAll(messagesMetaData.entries);
// result.entries.addAll(noticesMetaData.entries);
// Collections.sort(result.entries, new Comparator<Message>() {
// @Override
// public int compare(Message o1, Message o2) {
// return o1.getDate().compareTo(o2.getDate());
// }
// });
// return result;
// } else {
// return getMessagesMetaData(oldestMessageToFetch);
// }
}
protected void cleanup() throws IOException {
// do mark messages unread
doMarkMessagesUnread();
// do mark messages read
doMarkMessagesRead();
// do delete messages
doDeleteMessages();
// do delete notices
doDeleteNotices();
}
@Override
public boolean logout() throws IOException {
if (! isLoggedIn) {
return true;
}
cleanup();
// and now: logout!
Map<String, Object> params = new HashMap<String, Object>();
params.put("Form", "s_Logout");
// params.put("PresetFields", "s_CacheScrubType;0");
ClientHttpRequest httpRequest = context.createRequest(new URL(context.getProxyBaseURL()+"&PresetFields=s_CacheScrubType;0"), HttpMethod.GET, params);
ClientHttpResponse httpResponse = httpRequest.execute();
trace(httpRequest, httpResponse);
context.rememberCookies(httpRequest, httpResponse);
if (logger.isTraceEnabled()) {
traceBody(httpResponse);
}
try {
if (httpResponse.getStatusCode().series().equals(HttpStatus.Series.SUCCESSFUL)) {
logger.info("Logout successful for user \"" + context.getUserName() + '"');
} else {
logger.warn("ERROR while logging out user \""+context.getUserName()+"\": " + httpResponse.getRawStatusCode() + ' ' + httpResponse.getStatusText());
return false;
}
} finally {
httpResponse.close();
}
context.getCookieStore().removeAll();
isLoggedIn = false;
allMessagesCache = null;
allNoticesCache = null;
mailsToDelete.clear();
mailsToMarkRead.clear();
mailsToMarkReadAll.clear();
mailsToMarkUnread.clear();
mailsToMarkUnreadAll.clear();
noticesToDelete.clear();
return true;
}
private class HttpCleaningLineIterator extends LineIterator implements Iterator<String>, Closeable {
private final Logger logger = LoggerFactory.getLogger(HttpCleaningLineIterator.class);
private final ClientHttpResponse httpResponse;
private boolean inHeaders = true;
private boolean firstHeaderLine = true;
public HttpCleaningLineIterator(final ClientHttpResponse httpResponse) throws IOException, MailParseException {
super(new InputStreamReader(httpResponse.getBody(), context.getCharset(httpResponse)));
this.httpResponse = httpResponse;
if (context.isFixLotusNotesDateMIMEHeader()) {
inHeaders = true;
} else {
inHeaders = false;
}
hasNext();// will call isValidLine() to validate first line
}
@Override
protected boolean isValidLine(String line) throws MailParseException {
if (firstHeaderLine) {
firstHeaderLine = false;
// check that this is a correct RFC 5322 Header Field line
if (! MIME_HEADER.matcher(line).matches()) {
close();
// throw new IllegalStateException("Bad MIME header: " + line);
// throw new java.util.IllegalFormatException("Bad MIME header: " + line);
// throw new java.text.ParseException("Bad MIME header: " + line, 0);
// throw new java.util.regex.PatternSyntaxException("Bad MIME header", line, 0);
// throw new javax.mail.internet.ParseException("Bad MIME header: " + line);
// throw new javax.activation.MimeTypeParseException("Bad MIME header: " + line);
throw new org.springframework.mail.MailParseException("Bad MIME header: " + line);
}
}
return super.isValidLine(line);
}
@Override
public String nextLine() {
String line = super.nextLine();
CharSequence data = line;
// delete html tags
if (line.endsWith("<br>")) {
data = new StringBuilder(line).delete(line.length()-"<br>".length(), line.length());
}
// convert " -> ", & -> &, < -> <, > -> >
line = new LookupTranslator(EntityArrays.BASIC_UNESCAPE()).translate(data);
if (StringUtils.isEmpty(line)) {
inHeaders = false;
}
// fix Lotus Notes broken date pattern: 29-Oct-2012 19:23:20 CET 10-Oct-2012 11:25:11 CEDT
if (inHeaders) {
line = fr.cedrik.util.DateUtils.fixLotusMIMEDateHeader(line);
}
return line;
}
@Override
public void close() {
super.close();
httpResponse.close();
}
}
static final Pattern MIME_HEADER = Pattern.compile("^[^:]+:.*$");//$NON-NLS-1$
}