Refactored ExchangeSession creation : improved Http connection handling

git-svn-id: http://svn.code.sf.net/p/davmail/code/trunk@158 3d1905a2-6b24-0410-a738-b14d5a86fcbd
This commit is contained in:
mguessan 2008-11-04 01:47:10 +00:00
parent 60907607e7
commit 1828202211
5 changed files with 297 additions and 209 deletions

View File

@ -1,7 +1,7 @@
package davmail;
import davmail.http.DavGatewaySSLProtocolSocketFactory;
import davmail.http.DavGatewayHttpClientFactory;
import davmail.http.DavGatewayHttpClientFacade;
import davmail.pop.PopServer;
import davmail.smtp.SmtpServer;
import davmail.tray.DavGatewayTray;
@ -103,9 +103,9 @@ public class DavGateway {
public static String getReleasedVersion() {
String version = null;
BufferedReader versionReader = null;
GetMethod getMethod = new GetMethod("http://davmail.sourceforge.net/version.txt");
try {
HttpClient httpClient = DavGatewayHttpClientFactory.getInstance();
GetMethod getMethod = new GetMethod("http://davmail.sourceforge.net/version.txt");
HttpClient httpClient = DavGatewayHttpClientFacade.getInstance();
int status = httpClient.executeMethod(getMethod);
if (status == HttpStatus.SC_OK) {
versionReader = new BufferedReader(new InputStreamReader(getMethod.getResponseBodyAsStream()));
@ -121,6 +121,7 @@ public class DavGateway {
// ignore
}
}
getMethod.releaseConnection();
}
return version;
}

View File

@ -1,7 +1,7 @@
package davmail.exchange;
import davmail.Settings;
import davmail.http.DavGatewayHttpClientFactory;
import davmail.http.DavGatewayHttpClientFacade;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
@ -63,6 +63,11 @@ public class ExchangeSession {
*/
private final SimpleDateFormat dateParser;
/**
* Base Exchange URL
*/
private String baseUrl;
/**
* Various standard mail boxes Urls
*/
@ -97,32 +102,150 @@ public class ExchangeSession {
* @throws java.io.IOException unable to connect to exchange
*/
protected boolean isBasicAuthentication(String url) throws IOException {
// create an HttpClient instance
HttpClient httpClient = DavGatewayHttpClientFactory.getInstance();
HttpMethod testMethod = new GetMethod(url);
int status = httpClient.executeMethod(testMethod);
testMethod.releaseConnection();
return status == HttpStatus.SC_UNAUTHORIZED;
return DavGatewayHttpClientFacade.getHttpStatus(url) == HttpStatus.SC_UNAUTHORIZED;
}
/**
* Try to find logon method path from logon form body.
*
* @param initmethod form body http method
* @return logon method path
*/
protected String getLogonMethodPathAndBaseUrl(HttpMethod initmethod) {
// default logon method path
String logonMethodPath = "/exchweb/bin/auth/owaauth.dll";
// try to parse login form to determine logon url and destination
BufferedReader loginFormReader = null;
try {
loginFormReader = new BufferedReader(new InputStreamReader(initmethod.getResponseBodyAsStream()));
String line;
// skip to form action
final String FORM_ACTION = "<form action=\"";
final String DESTINATION_INPUT = "name=\"destination\" value=\"";
//noinspection StatementWithEmptyBody
while ((line = loginFormReader.readLine()) != null && line.toLowerCase().indexOf(FORM_ACTION) == -1) {
}
if (line != null) {
int start = line.toLowerCase().indexOf(FORM_ACTION) + FORM_ACTION.length();
int end = line.indexOf("\"", start);
logonMethodPath = line.substring(start, end);
//noinspection StatementWithEmptyBody
while ((line = loginFormReader.readLine()) != null && line.indexOf(DESTINATION_INPUT) == -1) {
}
if (line != null) {
start = line.indexOf(DESTINATION_INPUT) + DESTINATION_INPUT.length();
end = line.indexOf("\"", start);
baseUrl = line.substring(start, end);
}
}
// allow relative URLs
if (!logonMethodPath.startsWith("/")) {
String path = initmethod.getPath();
int end = path.lastIndexOf('/');
if (end >= 0) {
logonMethodPath = path.substring(0, end + 1) + logonMethodPath;
}
}
} catch (IOException e) {
LOGGER.error("Error parsing login form at " + initmethod.getPath());
} finally {
if (loginFormReader != null) {
try {
loginFormReader.close();
} catch (IOException e) {
LOGGER.error("Error parsing login form at " + initmethod.getPath());
}
}
initmethod.releaseConnection();
}
return logonMethodPath;
}
protected void formLogin(HttpClient httpClient, HttpMethod initmethod, String userName, String password) throws IOException {
LOGGER.debug("Form based authentication detected");
// get logon method path and actual destination (baseUrl)
String logonMethodPath = getLogonMethodPathAndBaseUrl(initmethod);
PostMethod logonMethod = new PostMethod(
logonMethodPath + "?ForcedBasic=false&Basic=false&Private=true&Language=No_Value"
);
logonMethod.addParameter("destination", baseUrl);
logonMethod.addParameter("flags", "4");
// logonMethod.addParameter("visusername", userName.substring(userName.lastIndexOf('\\')));
logonMethod.addParameter("username", userName);
logonMethod.addParameter("password", password);
// logonMethod.addParameter("SubmitCreds", "Log On");
// logonMethod.addParameter("forcedownlevel", "0");
logonMethod.addParameter("trusted", "4");
try {
httpClient.executeMethod(logonMethod);
} finally {
logonMethod.releaseConnection();
}
Header locationHeader = logonMethod.getResponseHeader("Location");
if (logonMethod.getStatusCode() != HttpURLConnection.HTTP_MOVED_TEMP ||
locationHeader == null ||
!baseUrl.equals(locationHeader.getValue())) {
throw new HttpException("Authentication failed");
}
}
protected String getMailPath(HttpMethod method) {
String result = null;
// get user mail URL from html body (multi frame)
BufferedReader mainPageReader = null;
try {
mainPageReader = new BufferedReader(new InputStreamReader(method.getResponseBodyAsStream()));
String line;
// find base url
final String BASE_HREF = "<base href=\"";
//noinspection StatementWithEmptyBody
while ((line = mainPageReader.readLine()) != null && line.toLowerCase().indexOf(BASE_HREF) == -1) {
}
if (line != null) {
int start = line.toLowerCase().indexOf(BASE_HREF) + BASE_HREF.length();
int end = line.indexOf("\"", start);
String mailBoxBaseHref = line.substring(start, end);
URL baseURL = new URL(mailBoxBaseHref);
result = baseURL.getPath();
}
} catch (IOException e) {
LOGGER.error("Error parsing main page at " + method.getPath());
} finally {
if (mainPageReader != null) {
try {
mainPageReader.close();
} catch (IOException e) {
LOGGER.error("Error parsing main page at " + method.getPath());
}
}
method.releaseConnection();
}
return result;
}
void login(String userName, String password) throws IOException {
try {
String url = Settings.getProperty("davmail.url");
baseUrl = Settings.getProperty("davmail.url");
boolean isBasicAuthentication = isBasicAuthentication(url);
boolean isBasicAuthentication = isBasicAuthentication(baseUrl);
// get proxy configuration from setttings properties
URL urlObject = new URL(url);
URL urlObject = new URL(baseUrl);
// webdavresource is unable to create the correct url type
HttpURL httpURL;
if (url.startsWith("http://")) {
if (baseUrl.startsWith("http://")) {
httpURL = new HttpURL(userName, password,
urlObject.getHost(), urlObject.getPort());
} else if (url.startsWith("https://")) {
} else if (baseUrl.startsWith("https://")) {
httpURL = new HttpsURL(userName, password,
urlObject.getHost(), urlObject.getPort());
} else {
throw new IllegalArgumentException("Invalid URL: " + url);
throw new IllegalArgumentException("Invalid URL: " + baseUrl);
}
wdr = new WebdavResource(httpURL, WebdavResource.NOACTION, 0);
@ -132,107 +255,20 @@ public class ExchangeSession {
// get the internal HttpClient instance
HttpClient httpClient = wdr.retrieveSessionInstance();
DavGatewayHttpClientFactory.configureClient(httpClient);
DavGatewayHttpClientFacade.configureClient(httpClient);
// get webmail root url
// providing credentials
// manually follow redirect
HttpMethod initmethod = new GetMethod(url);
initmethod.setFollowRedirects(false);
httpClient.executeMethod(initmethod);
int redirectCount = 0;
while (redirectCount++ < 10 && initmethod.getStatusCode() == 302) {
initmethod.releaseConnection();
initmethod = new GetMethod(initmethod.getResponseHeader("Location").getValue());
initmethod.setFollowRedirects(false);
httpClient.executeMethod(initmethod);
}
if (initmethod.getStatusCode() == 302) {
throw new IOException("Maximum redirections reached");
}
// default destination is provided url
String destination = url;
HttpMethod initmethod = DavGatewayHttpClientFacade.executeFollowRedirects(httpClient, baseUrl);
if (!isBasicAuthentication) {
LOGGER.debug("Form based authentication detected");
// default logon method path
String logonMethodPath = "/exchweb/bin/auth/owaauth.dll";
// try to parse login form to determine logon url and destination
BufferedReader loginFormReader = null;
try {
loginFormReader = new BufferedReader(new InputStreamReader(initmethod.getResponseBodyAsStream()));
String line;
// skip to form action
final String FORM_ACTION = "<form action=\"";
final String DESTINATION_INPUT = "name=\"destination\" value=\"";
//noinspection StatementWithEmptyBody
while ((line = loginFormReader.readLine()) != null && line.toLowerCase().indexOf(FORM_ACTION) == -1){}
if (line != null) {
int start = line.toLowerCase().indexOf(FORM_ACTION) + FORM_ACTION.length();
int end = line.indexOf("\"", start);
logonMethodPath = line.substring(start, end);
//noinspection StatementWithEmptyBody
while ((line = loginFormReader.readLine()) != null && line.indexOf(DESTINATION_INPUT) == -1) {}
if (line != null) {
start = line.indexOf(DESTINATION_INPUT) + DESTINATION_INPUT.length();
end = line.indexOf("\"", start);
destination = line.substring(start, end);
}
}
// allow relative URLs
if (!logonMethodPath.startsWith("/")) {
String path = initmethod.getPath();
int end = path.lastIndexOf('/');
if (end >=0) {
logonMethodPath = path.substring(0, end+1) + logonMethodPath;
}
}
} catch (IOException e) {
LOGGER.error("Error parsing login form at " + initmethod.getPath());
} finally {
if (loginFormReader != null) {
loginFormReader.close();
}
}
PostMethod logonMethod = new PostMethod(
logonMethodPath + "?ForcedBasic=false&Basic=false&Private=true&Language=No_Value"
);
logonMethod.addParameter("destination", destination);
logonMethod.addParameter("flags", "4");
// logonMethod.addParameter("visusername", userName.substring(userName.lastIndexOf('\\')));
logonMethod.addParameter("username", userName);
logonMethod.addParameter("password", password);
// logonMethod.addParameter("SubmitCreds", "Log On");
// logonMethod.addParameter("forcedownlevel", "0");
logonMethod.addParameter("trusted", "4");
httpClient.executeMethod(logonMethod);
Header locationHeader = logonMethod.getResponseHeader("Location");
if (logonMethod.getStatusCode() != HttpURLConnection.HTTP_MOVED_TEMP ||
locationHeader == null ||
!destination.equals(locationHeader.getValue())) {
throw new HttpException("Authentication failed");
}
formLogin(httpClient, initmethod, userName, password);
}
// User may be authenticated, get various session information
HttpMethod method = new GetMethod(destination);
method.setFollowRedirects(false);
int status = httpClient.executeMethod(method);
redirectCount = 0;
while (redirectCount++ < 10 && method.getStatusCode() == 302) {
method.releaseConnection();
method = new GetMethod(method.getResponseHeader("Location").getValue());
method.setFollowRedirects(false);
status = httpClient.executeMethod(method);
}
if (method.getStatusCode() == 302) {
throw new IOException("Maximum redirections reached");
}
HttpMethod method = DavGatewayHttpClientFacade.executeFollowRedirects(httpClient, baseUrl);
int status = method.getStatusCode();
if (status != HttpStatus.SC_OK) {
HttpException ex = new HttpException();
ex.setReasonCode(status);
@ -242,35 +278,14 @@ public class ExchangeSession {
// test form based authentication
String queryString = method.getQueryString();
if (queryString != null && queryString.endsWith("reason=2")) {
throw new HttpException("Authentication failed: invalid user or password");
method.releaseConnection();
throw new HttpException("Authentication failed: invalid user or password");
}
// get user mail URL from html body (multi frame)
BufferedReader mainPageReader = null;
try {
mainPageReader = new BufferedReader(new InputStreamReader(method.getResponseBodyAsStream()));
String line;
// find base url
final String BASE_HREF = "<base href=\"";
//noinspection StatementWithEmptyBody
while ((line = mainPageReader.readLine()) != null && line.toLowerCase().indexOf(BASE_HREF) == -1){}
if (line != null) {
int start = line.toLowerCase().indexOf(BASE_HREF) + BASE_HREF.length();
int end = line.indexOf("\"", start);
String mailBoxBaseHref = line.substring(start, end);
URL baseURL = new URL(mailBoxBaseHref);
mailPath = baseURL.getPath();
}
} catch (IOException e) {
LOGGER.error("Error parsing main page at " + method.getPath());
} finally {
if (mainPageReader != null) {
mainPageReader.close();
}
}
mailPath = getMailPath(method);
if (mailPath == null) {
throw new HttpException(destination + " not found in body, authentication failed: password expired ?");
throw new HttpException(baseUrl + " not found in body, authentication failed: password expired ?");
}
// got base http mailbox http url

View File

@ -1,7 +1,7 @@
package davmail.exchange;
import davmail.Settings;
import davmail.http.DavGatewayHttpClientFactory;
import davmail.http.DavGatewayHttpClientFacade;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpStatus;
@ -41,14 +41,14 @@ public class ExchangeSessionFactory {
public static void checkConfig() throws IOException {
String url = Settings.getProperty("davmail.url");
HttpMethod testMethod = new GetMethod(url);
try {
String url = Settings.getProperty("davmail.url");
// create an HttpClient instance
HttpClient httpClient = DavGatewayHttpClientFactory.getInstance();
HttpClient httpClient = DavGatewayHttpClientFacade.getInstance();
// get webmail root url (will not follow redirects)
HttpMethod testMethod = new GetMethod(url);
testMethod.setFollowRedirects(false);
int status = httpClient.executeMethod(testMethod);
testMethod.releaseConnection();
@ -72,6 +72,8 @@ public class ExchangeSessionFactory {
} catch (Exception exc) {
ExchangeSession.LOGGER.error("DavMail configuration exception: \n" + exc.getMessage(), exc);
throw new IOException("DavMail configuration exception: \n" + exc.getMessage());
} finally {
testMethod.releaseConnection();
}
}

View File

@ -0,0 +1,142 @@
package davmail.http;
import davmail.Settings;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.NTCredentials;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.methods.GetMethod;
import java.io.IOException;
/**
* Create HttpClient instance according to DavGateway Settings
*/
public class DavGatewayHttpClientFacade {
/**
* Create a configured HttpClient instance.
*
* @return httpClient
*/
public static HttpClient getInstance() {
// create an HttpClient instance
HttpClient httpClient = new HttpClient();
configureClient(httpClient);
return httpClient;
}
/**
* Update http client configuration (proxy)
*
* @param httpClient current Http client
*/
public static void configureClient(HttpClient httpClient) {
// do not send basic auth automatically
httpClient.getState().setAuthenticationPreemptive(false);
boolean enableProxy = Settings.getBooleanProperty("davmail.enableProxy");
String proxyHost = null;
int proxyPort = 0;
String proxyUser = null;
String proxyPassword = null;
if (enableProxy) {
proxyHost = Settings.getProperty("davmail.proxyHost");
proxyPort = Settings.getIntProperty("davmail.proxyPort");
proxyUser = Settings.getProperty("davmail.proxyUser");
proxyPassword = Settings.getProperty("davmail.proxyPassword");
}
// configure proxy
if (proxyHost != null && proxyHost.length() > 0) {
httpClient.getHostConfiguration().setProxy(proxyHost, proxyPort);
if (proxyUser != null && proxyUser.length() > 0) {
/* // Only available in newer HttpClient releases, not compatible with slide library
List authPrefs = new ArrayList();
authPrefs.add(AuthPolicy.BASIC);
httpClient.getParams().setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY,authPrefs);
*/
// instead detect ntlm authentication (windows domain name in user name)
int backslashindex = proxyUser.indexOf("\\");
if (backslashindex > 0) {
httpClient.getState().setProxyCredentials(null, proxyHost,
new NTCredentials(proxyUser.substring(backslashindex + 1),
proxyPassword, null,
proxyUser.substring(0, backslashindex)));
} else {
httpClient.getState().setProxyCredentials(null, proxyHost,
new UsernamePasswordCredentials(proxyUser, proxyPassword));
}
}
}
}
/**
* Get Http Status code for the given URL
*
* @param url url string
* @return HttpStatus code
* @throws IOException on error
*/
public static int getHttpStatus(String url) throws IOException {
int status = 0;
HttpClient httpClient = DavGatewayHttpClientFacade.getInstance();
HttpMethod testMethod = new GetMethod(url);
try {
status = httpClient.executeMethod(testMethod);
} finally {
testMethod.releaseConnection();
}
return status;
}
public static boolean isRedirect(int status) {
return status == HttpStatus.SC_MOVED_PERMANENTLY
|| status == HttpStatus.SC_MOVED_TEMPORARILY
|| status == HttpStatus.SC_SEE_OTHER
|| status == HttpStatus.SC_TEMPORARY_REDIRECT;
}
/**
* Execute given url, manually follow redirects.
* Workaround for HttpClient bug (GET full URL over HTTPS and proxy)
*
* @param httpClient HttpClient instance
* @param url url string
* @return executed method
* @throws IOException on error
*/
public static HttpMethod executeFollowRedirects(HttpClient httpClient, String url) throws IOException {
HttpMethod method = new GetMethod(url);
try {
method.setFollowRedirects(false);
httpClient.executeMethod(method);
Header location = method.getResponseHeader("Location");
int redirectCount = 0;
while (redirectCount++ < 10
&& location != null
&& isRedirect(method.getStatusCode())) {
method.releaseConnection();
method = new GetMethod(method.getResponseHeader("Location").getValue());
method.setFollowRedirects(false);
httpClient.executeMethod(method);
location = method.getResponseHeader("Location");
}
if (location != null && isRedirect(method.getStatusCode())) {
method.releaseConnection();
throw new HttpException("Maximum redirections reached");
}
} catch (IOException e) {
method.releaseConnection();
throw e;
}
// caller will need to release connection
return method;
}
}

View File

@ -1,72 +0,0 @@
package davmail.http;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.NTCredentials;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import davmail.Settings;
/**
* Create HttpClient instance according to DavGateway Settings
*/
public class DavGatewayHttpClientFactory {
/**
* Create a configured HttpClient instance.
*
* @return httpClient
*/
public static HttpClient getInstance() {
// create an HttpClient instance
HttpClient httpClient = new HttpClient();
configureClient(httpClient);
return httpClient;
}
/**
* Update http client configuration (proxy)
*
* @param httpClient current Http client
*/
public static void configureClient(HttpClient httpClient) {
// do not send basic auth automatically
httpClient.getState().setAuthenticationPreemptive(false);
boolean enableProxy = Settings.getBooleanProperty("davmail.enableProxy");
String proxyHost = null;
int proxyPort = 0;
String proxyUser = null;
String proxyPassword = null;
if (enableProxy) {
proxyHost = Settings.getProperty("davmail.proxyHost");
proxyPort = Settings.getIntProperty("davmail.proxyPort");
proxyUser = Settings.getProperty("davmail.proxyUser");
proxyPassword = Settings.getProperty("davmail.proxyPassword");
}
// configure proxy
if (proxyHost != null && proxyHost.length() > 0) {
httpClient.getHostConfiguration().setProxy(proxyHost, proxyPort);
if (proxyUser != null && proxyUser.length() > 0) {
/* // Only available in newer HttpClient releases, not compatible with slide library
List authPrefs = new ArrayList();
authPrefs.add(AuthPolicy.BASIC);
httpClient.getParams().setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY,authPrefs);
*/
// instead detect ntlm authentication (windows domain name in user name)
int backslashindex = proxyUser.indexOf("\\");
if (backslashindex > 0) {
httpClient.getState().setProxyCredentials(null, proxyHost,
new NTCredentials(proxyUser.substring(backslashindex + 1),
proxyPassword, null,
proxyUser.substring(0, backslashindex)));
} else {
httpClient.getState().setProxyCredentials(null, proxyHost,
new UsernamePasswordCredentials(proxyUser, proxyPassword));
}
}
}
}
}