src/org/gege/caldavsyncadapter/caldav/CaldavFacade.java

changeset 0
fb9019fb1bf7
child 8
ec8af0e3fbc2
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/src/org/gege/caldavsyncadapter/caldav/CaldavFacade.java	Tue Feb 10 18:12:00 2015 +0100
     1.3 @@ -0,0 +1,866 @@
     1.4 +/**
     1.5 + * Copyright (c) 2012-2013, Gerald Garcia, David Wiesner, Timo Berger
     1.6 + * 
     1.7 + * This file is part of Andoid Caldav Sync Adapter Free.
     1.8 + *
     1.9 + * Andoid Caldav Sync Adapter Free is free software: you can redistribute 
    1.10 + * it and/or modify it under the terms of the GNU General Public License 
    1.11 + * as published by the Free Software Foundation, either version 3 of the 
    1.12 + * License, or at your option any later version.
    1.13 + *
    1.14 + * Andoid Caldav Sync Adapter Free is distributed in the hope that 
    1.15 + * it will be useful, but WITHOUT ANY WARRANTY; without even the implied 
    1.16 + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    1.17 + * GNU General Public License for more details.
    1.18 + *
    1.19 + * You should have received a copy of the GNU General Public License
    1.20 + * along with Andoid Caldav Sync Adapter Free.  
    1.21 + * If not, see <http://www.gnu.org/licenses/>.
    1.22 + * 
    1.23 + */
    1.24 +
    1.25 +package org.gege.caldavsyncadapter.caldav;
    1.26 +
    1.27 +import java.io.BufferedReader;
    1.28 +import java.io.ByteArrayInputStream;
    1.29 +import java.io.FileNotFoundException;
    1.30 +import java.io.IOException;
    1.31 +import java.io.InputStream;
    1.32 +import java.io.InputStreamReader;
    1.33 +import java.io.UnsupportedEncodingException;
    1.34 +import java.net.MalformedURLException;
    1.35 +import java.net.SocketException;
    1.36 +import java.net.URI;
    1.37 +import java.net.URISyntaxException;
    1.38 +import java.net.URL;
    1.39 +import java.util.ArrayList;
    1.40 +import java.util.List;
    1.41 +
    1.42 +import javax.xml.parsers.DocumentBuilder;
    1.43 +import javax.xml.parsers.DocumentBuilderFactory;
    1.44 +import javax.xml.parsers.ParserConfigurationException;
    1.45 +import javax.xml.parsers.SAXParser;
    1.46 +import javax.xml.parsers.SAXParserFactory;
    1.47 +
    1.48 +import org.apache.http.HttpException;
    1.49 +import org.apache.http.HttpHost;
    1.50 +import org.apache.http.HttpRequest;
    1.51 +import org.apache.http.HttpRequestInterceptor;
    1.52 +import org.apache.http.HttpResponse;
    1.53 +import org.apache.http.HttpVersion;
    1.54 +import org.apache.http.auth.AuthScope;
    1.55 +import org.apache.http.auth.AuthState;
    1.56 +import org.apache.http.auth.AuthenticationException;
    1.57 +import org.apache.http.auth.UsernamePasswordCredentials;
    1.58 +import org.apache.http.client.ClientProtocolException;
    1.59 +import org.apache.http.client.CredentialsProvider;
    1.60 +import org.apache.http.client.HttpClient;
    1.61 +import org.apache.http.client.methods.HttpDelete;
    1.62 +import org.apache.http.client.methods.HttpGet;
    1.63 +import org.apache.http.client.methods.HttpPut;
    1.64 +import org.apache.http.client.protocol.ClientContext;
    1.65 +import org.apache.http.conn.HttpHostConnectException;
    1.66 +import org.apache.http.conn.params.ConnManagerPNames;
    1.67 +import org.apache.http.conn.params.ConnPerRouteBean;
    1.68 +import org.apache.http.conn.scheme.PlainSocketFactory;
    1.69 +import org.apache.http.conn.scheme.Scheme;
    1.70 +import org.apache.http.conn.scheme.SchemeRegistry;
    1.71 +import org.apache.http.conn.ssl.SSLSocketFactory;
    1.72 +import org.apache.http.entity.StringEntity;
    1.73 +import org.apache.http.impl.client.AbstractHttpClient;
    1.74 +import org.apache.http.impl.client.DefaultHttpClient;
    1.75 +import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
    1.76 +import org.apache.http.params.BasicHttpParams;
    1.77 +import org.apache.http.params.CoreProtocolPNames;
    1.78 +import org.apache.http.params.HttpParams;
    1.79 +import org.apache.http.params.HttpProtocolParams;
    1.80 +import org.apache.http.protocol.BasicHttpContext;
    1.81 +import org.apache.http.protocol.HttpContext;
    1.82 +import org.gege.caldavsyncadapter.BuildConfig;
    1.83 +import org.gege.caldavsyncadapter.caldav.entities.DavCalendar;
    1.84 +import org.gege.caldavsyncadapter.caldav.entities.DavCalendar.CalendarSource;
    1.85 +import org.gege.caldavsyncadapter.caldav.entities.CalendarEvent;
    1.86 +import org.gege.caldavsyncadapter.caldav.entities.CalendarList;
    1.87 +import org.gege.caldavsyncadapter.caldav.http.HttpPropFind;
    1.88 +import org.gege.caldavsyncadapter.caldav.http.HttpReport;
    1.89 +import org.gege.caldavsyncadapter.caldav.xml.CalendarHomeHandler;
    1.90 +import org.gege.caldavsyncadapter.caldav.xml.CalendarsHandler;
    1.91 +import org.gege.caldavsyncadapter.caldav.xml.ServerInfoHandler;
    1.92 +import org.gege.caldavsyncadapter.syncadapter.notifications.NotificationsHelper;
    1.93 +import org.w3c.dom.Document;
    1.94 +import org.w3c.dom.Element;
    1.95 +import org.w3c.dom.Node;
    1.96 +import org.w3c.dom.NodeList;
    1.97 +import org.xml.sax.ContentHandler;
    1.98 +import org.xml.sax.InputSource;
    1.99 +import org.xml.sax.SAXException;
   1.100 +import org.xml.sax.XMLReader;
   1.101 +
   1.102 +import android.accounts.Account;
   1.103 +import android.content.ContentProviderClient;
   1.104 +import android.content.Context;
   1.105 +import android.util.Log;
   1.106 +
   1.107 +public class CaldavFacade {
   1.108 +	private static final String TAG = "CaldavFacade";
   1.109 +
   1.110 +	private final static String XML_VERSION = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
   1.111 +
   1.112 +	private String USER_AGENT = "CalDAV Sync Adapter (Android) https://github.com/gggard/AndroidCaldavSyncAdapater";
   1.113 +	private String VERSION = ""; 
   1.114 +
   1.115 +	private static HttpClient httpClient;
   1.116 +	private HttpContext mContext = null;
   1.117 +	private AuthState mLastAuthState = null;
   1.118 +	private AuthScope mLastAuthScope = null;
   1.119 +	
   1.120 +	private boolean trustAll = true;
   1.121 +
   1.122 +	private URL url;
   1.123 +
   1.124 +	private static HttpHost targetHost;
   1.125 +	
   1.126 +	private int lastStatusCode;
   1.127 +	private String lastETag;
   1.128 +	private String lastDav;
   1.129 +
   1.130 +	private String mstrcHeaderIfMatch = "If-Match";
   1.131 +	private String mstrcHeaderIfNoneMatch = "If-None-Match";
   1.132 +	
   1.133 +	private Account mAccount = null;
   1.134 +	private ContentProviderClient mProvider;
   1.135 +	
   1.136 +	protected HttpClient getHttpClient() {
   1.137 +
   1.138 +		HttpParams params = new BasicHttpParams();
   1.139 +		params.setParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 30);
   1.140 +		params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, new ConnPerRouteBean(30));
   1.141 +		params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, false);
   1.142 +		HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
   1.143 +
   1.144 +		SchemeRegistry registry = new SchemeRegistry();
   1.145 +		registry.register(new Scheme("http", new PlainSocketFactory(), 80));
   1.146 +		registry.register(new Scheme("https", (trustAll ? EasySSLSocketFactory.getSocketFactory() : SSLSocketFactory.getSocketFactory()), 443));
   1.147 +		DefaultHttpClient client = new DefaultHttpClient(new ThreadSafeClientConnManager(params, registry), params);
   1.148 +		
   1.149 +		return client;
   1.150 +	}
   1.151 +
   1.152 +	public CaldavFacade(String mUser, String mPassword, String mURL) throws MalformedURLException {
   1.153 +		url = new URL(mURL);
   1.154 +
   1.155 +		httpClient = getHttpClient();
   1.156 +		UsernamePasswordCredentials upc = new UsernamePasswordCredentials(mUser, mPassword);
   1.157 +
   1.158 +		AuthScope as = null;
   1.159 +		as = new AuthScope(url.getHost(), -1);
   1.160 +		((AbstractHttpClient) httpClient).getCredentialsProvider().setCredentials(as, upc);
   1.161 +		
   1.162 +		mContext = new BasicHttpContext();
   1.163 +		CredentialsProvider credProvider = ((AbstractHttpClient) httpClient).getCredentialsProvider();
   1.164 +		mContext.setAttribute(ClientContext.CREDS_PROVIDER, credProvider);
   1.165 +				
   1.166 +		//http://dlinsin.blogspot.de/2009/08/http-basic-authentication-with-android.html
   1.167 +		((AbstractHttpClient) httpClient).addRequestInterceptor(preemptiveAuth, 0);
   1.168 +
   1.169 +		String proto = "http";
   1.170 +		int port = 80;
   1.171 +
   1.172 +		if (url.getProtocol().equalsIgnoreCase("https")) {
   1.173 +			proto = "https";
   1.174 +			if (url.getPort() == -1)
   1.175 +				port = 443;
   1.176 +			else
   1.177 +				port = url.getPort();
   1.178 +		}
   1.179 +
   1.180 +		if (url.getProtocol().equalsIgnoreCase("http")) {
   1.181 +			proto = "http";
   1.182 +			if (url.getPort() == -1)
   1.183 +				port = 80;
   1.184 +			else
   1.185 +				port = url.getPort();
   1.186 +		}
   1.187 +		targetHost = new HttpHost(url.getHost(), port, proto);
   1.188 +	}
   1.189 +	
   1.190 +	//http://dlinsin.blogspot.de/2009/08/http-basic-authentication-with-android.html
   1.191 +	HttpRequestInterceptor preemptiveAuth = new HttpRequestInterceptor() {
   1.192 +		@Override
   1.193 +	    public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException {
   1.194 +	        AuthState authState = (AuthState) context.getAttribute(ClientContext.TARGET_AUTH_STATE);
   1.195 +
   1.196 +	        if (authState.getAuthScheme() == null) {
   1.197 +	        	if (mLastAuthState != null) {
   1.198 +	        		Log.d(TAG, "LastAuthState: restored with user " + mLastAuthState.getCredentials().getUserPrincipal().getName());
   1.199 +	        		authState.setAuthScheme(mLastAuthState.getAuthScheme());
   1.200 +	        		authState.setCredentials(mLastAuthState.getCredentials());
   1.201 +	        	} else {
   1.202 +	        		Log.d(TAG, "LastAuthState: nothing to do");
   1.203 +	        	}
   1.204 +	        	if (mLastAuthScope != null) {
   1.205 +	        		authState.setAuthScope(mLastAuthScope);
   1.206 +	        		Log.d(TAG, "LastAuthScope: restored");
   1.207 +	        	} else {
   1.208 +	        		Log.d(TAG, "LastAuthScope: nothing to do");
   1.209 +	        	}
   1.210 +	        } else {
   1.211 +	        	//AuthState and AuthScope have to be saved separate because of the AuthScope within AuthState gets lost, so we save it in a separate var.
   1.212 +	        	mLastAuthState = authState;
   1.213 +	        	Log.d(TAG, "LastAuthState: new with user " + mLastAuthState.getCredentials().getUserPrincipal().getName());
   1.214 +	        	if (authState.getAuthScope() != null) {
   1.215 +	        		mLastAuthScope = authState.getAuthScope();
   1.216 +	        		Log.d(TAG, "LastAuthScope: new");
   1.217 +	        	}
   1.218 +	        }
   1.219 +	    }
   1.220 +	};
   1.221 +
   1.222 +	public enum TestConnectionResult {
   1.223 +		WRONG_CREDENTIAL, 
   1.224 +		WRONG_URL, 
   1.225 +		WRONG_SERVER_STATUS, 
   1.226 +		WRONG_ANSWER, 
   1.227 +		SUCCESS
   1.228 +	}
   1.229 +
   1.230 +	/**
   1.231 +	 * TODO: testConnection should return only an instance of
   1.232 +	 * TestConnectionResult without throwing an exception or only throw checked
   1.233 +	 * exceptions so you don't have to check the result of this function AND
   1.234 +	 * handle the exceptions
   1.235 +	 * @param context 
   1.236 +	 * 
   1.237 +	 * @return {@link TestConnectionResult}
   1.238 +	 * @throws HttpHostConnectException
   1.239 +	 * @throws IOException
   1.240 +	 * @throws URISyntaxException
   1.241 +	 * @throws ParserConfigurationException
   1.242 +	 * @throws SAXException
   1.243 +	 */
   1.244 +	public TestConnectionResult testConnection() throws HttpHostConnectException, IOException, URISyntaxException, ParserConfigurationException, SAXException {
   1.245 +		Log.d(TAG, "start testConnection ");
   1.246 +		try {
   1.247 +			List<DavCalendar> calendars = new ArrayList<DavCalendar>();
   1.248 +			calendars = forceGetCalendarsFromUri(null, url.toURI());
   1.249 +			if (calendars.size() != 0) {
   1.250 +				return TestConnectionResult.SUCCESS;
   1.251 +			}
   1.252 +
   1.253 +			URI userPrincipal = getUserPrincipal();
   1.254 +			List<URI> calendarSets = getCalendarHomes(userPrincipal);
   1.255 +			for (URI calendarSet : calendarSets) {
   1.256 +				List<DavCalendar> calendarSetCalendars = getCalendarsFromSet(calendarSet);
   1.257 +				calendars.addAll(calendarSetCalendars);
   1.258 +			}
   1.259 +			if (calendarSets.size() == 0) {
   1.260 +				return TestConnectionResult.WRONG_ANSWER;
   1.261 +			}
   1.262 +		} catch (FileNotFoundException e) {
   1.263 +			return TestConnectionResult.WRONG_URL;
   1.264 +		} catch (SocketException e) {
   1.265 +			return TestConnectionResult.WRONG_URL;
   1.266 +		} catch (AuthenticationException e) {
   1.267 +			return TestConnectionResult.WRONG_CREDENTIAL;
   1.268 +		} catch (ClientProtocolException e) {
   1.269 +			return TestConnectionResult.WRONG_SERVER_STATUS;
   1.270 +		} catch (CaldavProtocolException e) {
   1.271 +			return TestConnectionResult.WRONG_ANSWER;
   1.272 +		}
   1.273 +		return TestConnectionResult.SUCCESS;
   1.274 +	}
   1.275 +
   1.276 +	/**
   1.277 +	 * @param context May be null if no notification is needed
   1.278 +	 * @param uri
   1.279 +	 * @return
   1.280 +	 * @throws AuthenticationException
   1.281 +	 * @throws FileNotFoundException
   1.282 +	 */
   1.283 +	private List<DavCalendar> forceGetCalendarsFromUri(Context context, URI uri) throws AuthenticationException, FileNotFoundException {
   1.284 +		List<DavCalendar> calendars = new ArrayList<DavCalendar>();
   1.285 +		Exception exception = null;
   1.286 +		try {
   1.287 +			calendars = getCalendarsFromSet(uri);
   1.288 +		} catch (ClientProtocolException e) {
   1.289 +			if (context != null) {
   1.290 +				NotificationsHelper.signalSyncErrors(context, "Caldav sync problem", e.getMessage());
   1.291 +				//NotificationsHelper.getCurrentSyncLog().addException(e);
   1.292 +			}
   1.293 +			exception = e;
   1.294 +		} catch (FileNotFoundException e) {
   1.295 +			if (context != null) {
   1.296 +				NotificationsHelper.signalSyncErrors(context, "Caldav sync problem", e.getMessage());
   1.297 +				//NotificationsHelper.getCurrentSyncLog().addException(e);
   1.298 +			}
   1.299 +			throw e;
   1.300 +		} catch (IOException e) {
   1.301 +			if (context != null) {
   1.302 +				NotificationsHelper.signalSyncErrors(context, "Caldav sync problem", e.getMessage());
   1.303 +				//NotificationsHelper.getCurrentSyncLog().addException(e);
   1.304 +			}
   1.305 +			exception = e;
   1.306 +		} catch (CaldavProtocolException e) {
   1.307 +
   1.308 +			if (context != null) {
   1.309 +				NotificationsHelper.signalSyncErrors(context, "Caldav sync problem", e.getMessage());
   1.310 +				//NotificationsHelper.getCurrentSyncLog().addException(e);
   1.311 +			}
   1.312 +			exception = e;
   1.313 +		}
   1.314 +		if (exception != null && BuildConfig.DEBUG) {
   1.315 +			Log.e(TAG, "Force get calendars from '" + uri.toString()
   1.316 +					+ "' failed " + exception.getClass().getCanonicalName()
   1.317 +					+ ": " + exception.getMessage());
   1.318 +		}
   1.319 +		return calendars;
   1.320 +	}
   1.321 +
   1.322 +	private final static String PROPFIND_USER_PRINCIPAL = XML_VERSION +
   1.323 +			"<d:propfind xmlns:d=\"DAV:\">" +
   1.324 +				"<d:prop>" +
   1.325 +					"<d:current-user-principal />" +
   1.326 +					"<d:principal-URL />" +
   1.327 +				"</d:prop>" +
   1.328 +			"</d:propfind>";
   1.329 +	
   1.330 +	private URI getUserPrincipal() throws SocketException,
   1.331 +			ClientProtocolException, AuthenticationException,
   1.332 +			FileNotFoundException, IOException, CaldavProtocolException,
   1.333 +			URISyntaxException {
   1.334 +		URI uri = this.url.toURI();
   1.335 +		HttpPropFind request = createPropFindRequest(uri,
   1.336 +				PROPFIND_USER_PRINCIPAL, 0);
   1.337 +		HttpResponse response = httpClient.execute(targetHost, request, mContext);
   1.338 +		checkStatus(response);
   1.339 +		ServerInfoHandler serverInfoHandler = new ServerInfoHandler();
   1.340 +		parseXML(response, serverInfoHandler);
   1.341 +		String userPrincipal = null;
   1.342 +		if (serverInfoHandler.currentUserPrincipal != null) {
   1.343 +			userPrincipal = serverInfoHandler.currentUserPrincipal;
   1.344 +		} else if (serverInfoHandler.principalUrl != null) {
   1.345 +			userPrincipal = serverInfoHandler.principalUrl;
   1.346 +		} else {
   1.347 +			throw new CaldavProtocolException("no principal url found");
   1.348 +		}
   1.349 +		try {
   1.350 +			URI userPrincipalUri = new URI(userPrincipal);
   1.351 +			userPrincipalUri = uri.resolve(userPrincipalUri);
   1.352 +			if (BuildConfig.DEBUG) {
   1.353 +				Log.d(TAG,
   1.354 +						"Found userPrincipal: " + userPrincipalUri.toString());
   1.355 +			}
   1.356 +			return userPrincipalUri;
   1.357 +		} catch (URISyntaxException e) {
   1.358 +			throw new CaldavProtocolException("principal url '" + userPrincipal
   1.359 +					+ "' malformed");
   1.360 +		}
   1.361 +	}
   1.362 +
   1.363 +	private final static String PROPFIND_CALENDAR_HOME_SET = XML_VERSION
   1.364 +			+ "<d:propfind xmlns:d=\"DAV:\" xmlns:c=\"urn:ietf:params:xml:ns:caldav\"><d:prop><c:calendar-home-set/></d:prop></d:propfind>";
   1.365 +
   1.366 +	private List<URI> getCalendarHomes(URI userPrincipal)
   1.367 +			throws ClientProtocolException, IOException,
   1.368 +			AuthenticationException, FileNotFoundException,
   1.369 +			CaldavProtocolException {
   1.370 +		HttpPropFind request = createPropFindRequest(userPrincipal,
   1.371 +				PROPFIND_CALENDAR_HOME_SET, 0);
   1.372 +		HttpResponse response = httpClient.execute(targetHost, request, mContext);
   1.373 +		checkStatus(response);
   1.374 +		CalendarHomeHandler calendarHomeHandler = new CalendarHomeHandler(
   1.375 +				userPrincipal);
   1.376 +		parseXML(response, calendarHomeHandler);
   1.377 +		List<URI> result = calendarHomeHandler.calendarHomeSet;
   1.378 +		if (BuildConfig.DEBUG) {
   1.379 +			Log.d(TAG, result.size() + " calendar-home-set found in "
   1.380 +					+ userPrincipal.toString());
   1.381 +		}
   1.382 +		return result;
   1.383 +	}
   1.384 +
   1.385 +	private final static String PROPFIND_CALENDER_LIST = XML_VERSION
   1.386 +			+ "<d:propfind xmlns:d=\"DAV:\" xmlns:c=\"urn:ietf:params:xml:ns:caldav\" xmlns:cs=\"http://calendarserver.org/ns/\" xmlns:ic=\"http://apple.com/ns/ical/\">"
   1.387 +			+ "<d:prop><d:displayname /><d:resourcetype />"
   1.388 +			// +
   1.389 +			// "<d:supported-method-set /><d:supported-report-set /><c:supported-calendar-component-set />"
   1.390 +			// +
   1.391 +			// "<c:calendar-description /><c:calendar-timezone /><c:calendar-free-busy-set />
   1.392 +			+ "<ic:calendar-color />"
   1.393 +			//<ic:calendar-order />"
   1.394 +			+ "<cs:getctag /></d:prop></d:propfind>";
   1.395 +
   1.396 +	
   1.397 +	private List<DavCalendar> getCalendarsFromSet(URI calendarSet)
   1.398 +			throws ClientProtocolException, IOException,
   1.399 +			CaldavProtocolException, AuthenticationException,
   1.400 +			FileNotFoundException {
   1.401 +		HttpPropFind request = createPropFindRequest(calendarSet, PROPFIND_CALENDER_LIST, 1);
   1.402 +		HttpResponse response = httpClient.execute(targetHost, request, mContext);
   1.403 +		checkStatus(response);
   1.404 +		CalendarsHandler calendarsHandler = new CalendarsHandler(calendarSet);
   1.405 +		parseXML(response, calendarsHandler);
   1.406 +		List<DavCalendar> result = calendarsHandler.calendars;
   1.407 +		if (BuildConfig.DEBUG) {
   1.408 +			Log.i(TAG,
   1.409 +					result.size() + " calendars found in set "
   1.410 +							+ calendarSet.toString());
   1.411 +		}
   1.412 +		return result;
   1.413 +	}
   1.414 +	
   1.415 +	/**
   1.416 +	 * Discover CalDAV Calendars Mentioned in
   1.417 +	 * http://tools.ietf.org/html/draft-daboo-srv-caldav-10#section-6 and
   1.418 +	 * http://code.google.com/p/sabredav/wiki/BuildingACalDAVClient#Discovery
   1.419 +	 * <ol>
   1.420 +	 * <li>PROPFIND calendar-home-set on url
   1.421 +	 * <li>PROPFIND DAV:current-user-principal or principal-URL on url
   1.422 +	 * <li>PROPFIND calendar-home-set on current-user-principal or principal-URL
   1.423 +	 * <li>PROPFIND displayname, resourcetype, getctag on CalendarHomeSets
   1.424 +	 * </ol>
   1.425 +	 * @param context 
   1.426 +	 * 
   1.427 +	 * @return List of {@link DavCalendar}
   1.428 +	 * @throws ClientProtocolException
   1.429 +	 *             http protocol error
   1.430 +	 * @throws IOException
   1.431 +	 *             Connection lost
   1.432 +	 * @throws URISyntaxException
   1.433 +	 *             url in Constructor malformed
   1.434 +	 * @throws CaldavProtocolException
   1.435 +	 *             caldav protocol error
   1.436 +	 */
   1.437 +	//public Iterable<Calendar> getCalendarList(Context context) throws ClientProtocolException,
   1.438 +	public CalendarList getCalendarList(Context context) throws ClientProtocolException,
   1.439 +			IOException, URISyntaxException, ParserConfigurationException,
   1.440 +			CaldavProtocolException {
   1.441 +		try {
   1.442 +			CalendarList Result = new CalendarList(this.mAccount, this.mProvider, CalendarSource.CalDAV, this.url.toString());
   1.443 +			List<DavCalendar> calendars = new ArrayList<DavCalendar>();
   1.444 +			
   1.445 +			calendars = forceGetCalendarsFromUri(context, this.url.toURI());
   1.446 +			
   1.447 +			if (calendars.size() == 0) {
   1.448 +				// no calendars found, try the home-set
   1.449 +				URI userPrincipal = getUserPrincipal();
   1.450 +				List<URI> calendarSets = getCalendarHomes(userPrincipal);
   1.451 +				for (URI calendarSet : calendarSets) {
   1.452 +					List<DavCalendar> calendarSetCalendars = getCalendarsFromSet(calendarSet);
   1.453 +					calendars.addAll(calendarSetCalendars);
   1.454 +				}
   1.455 +			}
   1.456 +			for (DavCalendar cal : calendars) {
   1.457 +				Result.addCalendar(cal);
   1.458 +			}
   1.459 +			
   1.460 +			//return calendars;
   1.461 +			return Result;
   1.462 +		} catch (AuthenticationException e) {
   1.463 +			throw new IOException(e);
   1.464 +		}
   1.465 +	}
   1.466 +
   1.467 +	//public Iterable<CalendarEvent> getCalendarEvents(DavCalendar calendar)
   1.468 +	public ArrayList<CalendarEvent> getCalendarEvents(DavCalendar calendar)
   1.469 +			throws URISyntaxException, ClientProtocolException, IOException,
   1.470 +			ParserConfigurationException, SAXException {
   1.471 +
   1.472 +		ArrayList<CalendarEvent> calendarEventList = new ArrayList<CalendarEvent>();
   1.473 +
   1.474 +		String requestBody = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
   1.475 +				+ "<D:propfind xmlns:D=\"DAV:\">" + "<D:prop>" + "<D:getetag/>"
   1.476 +				+ "</D:prop>" + "</D:propfind>";
   1.477 +
   1.478 +		HttpPropFind request = null;
   1.479 +		
   1.480 +		String EventUri;
   1.481 +
   1.482 +		/*request = new HttpPropFind();
   1.483 +		request.setURI(calendar.getURI());
   1.484 +		request.setHeader("Host", targetHost.getHostName());
   1.485 +		request.setHeader("Depth", "1");
   1.486 +		request.setHeader("Content-Type", "application/xml;charset=\"UTF-8\"");
   1.487 +		
   1.488 +		try {
   1.489 +			request.setEntity(new StringEntity(requestBody, "UTF-8"));
   1.490 +		} catch (UnsupportedEncodingException e) {
   1.491 +			throw new AssertionError("UTF-8 is unknown");
   1.492 +		}*/
   1.493 +		request = this.createPropFindRequest(calendar.getURI(), requestBody, 1);
   1.494 +		
   1.495 +		Log.d(TAG, "Getting eTag by PROPFIND at " + request.getURI());
   1.496 +
   1.497 +		HttpResponse response = httpClient.execute(targetHost, request, mContext);
   1.498 +
   1.499 +		BufferedReader reader = new BufferedReader(new InputStreamReader(
   1.500 +				response.getEntity().getContent(), "UTF-8"));
   1.501 +
   1.502 +		String line;
   1.503 +		String body = "";
   1.504 +		do {
   1.505 +			line = reader.readLine();
   1.506 +			if (line != null)
   1.507 +				body += line;
   1.508 +		} while (line != null);
   1.509 +
   1.510 +		Log.d(TAG, "HttpResponse status=" + response.getStatusLine()
   1.511 +				+ " body= " + body);
   1.512 +
   1.513 +		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
   1.514 +		factory.setNamespaceAware(true);
   1.515 +		DocumentBuilder builder = factory.newDocumentBuilder();
   1.516 +		Document dom = builder.parse(new InputSource(new ByteArrayInputStream(
   1.517 +				body.getBytes("utf-8"))));
   1.518 +		Element root = dom.getDocumentElement();
   1.519 +		NodeList items = root.getElementsByTagNameNS("*", "getetag");
   1.520 +
   1.521 +		for (int i = 0; i < items.getLength(); i++) {
   1.522 +			CalendarEvent calendarEvent = new CalendarEvent(this.mAccount, this.mProvider);
   1.523 +
   1.524 +			Node node = items.item(i);
   1.525 +
   1.526 +			if (node.getTextContent().trim().length() == 0)
   1.527 +				continue; // not an event
   1.528 +
   1.529 +			calendarEvent.setETag(node.getTextContent().trim());
   1.530 +			//calendarEvent.calendarURL = this.url;
   1.531 +			calendarEvent.calendarURL = calendar.getURI().toURL();
   1.532 +
   1.533 +			node = node.getParentNode(); // prop
   1.534 +			node = node.getParentNode(); // propstat
   1.535 +			node = node.getParentNode(); // response
   1.536 +
   1.537 +			NodeList children = node.getChildNodes();
   1.538 +			for (int j = 0; j < children.getLength(); j++) {
   1.539 +				Node childNode = children.item(j);
   1.540 +				if ((childNode.getLocalName()!=null) && (childNode.getLocalName().equalsIgnoreCase("href"))) {
   1.541 +					EventUri = childNode.getTextContent().trim();
   1.542 +					//HINT: bugfix for zimbra calendar: replace("@", "%40")
   1.543 +					EventUri = EventUri.replace("@", "%40");
   1.544 +					calendarEvent.setUri(new URI(EventUri));
   1.545 +				}
   1.546 +			}
   1.547 +
   1.548 +			calendarEventList.add(calendarEvent);
   1.549 +
   1.550 +		}
   1.551 +
   1.552 +		return calendarEventList;
   1.553 +	}
   1.554 +
   1.555 +	private void parseXML(HttpResponse response, ContentHandler contentHandler)
   1.556 +			throws IOException, CaldavProtocolException {
   1.557 +		InputStream is = response.getEntity().getContent();
   1.558 +		/*BufferedReader bReader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
   1.559 +		String Content = "";
   1.560 +		String Line = bReader.readLine();
   1.561 +
   1.562 +		while (Line != null) {
   1.563 +			Content += Line;
   1.564 +			Line = bReader.readLine();
   1.565 +		}*/
   1.566 +		
   1.567 +		SAXParserFactory factory = SAXParserFactory.newInstance();
   1.568 +		try {
   1.569 +			SAXParser parser = factory.newSAXParser();
   1.570 +			XMLReader reader = parser.getXMLReader();
   1.571 +			reader.setContentHandler(contentHandler);
   1.572 +			reader.parse(new InputSource(is));
   1.573 +		} catch (ParserConfigurationException e) {
   1.574 +			throw new AssertionError("ParserConfigurationException "
   1.575 +					+ e.getMessage());
   1.576 +		} catch (IllegalStateException e) {
   1.577 +			throw new CaldavProtocolException(e.getMessage());
   1.578 +		} catch (SAXException e) {
   1.579 +			throw new CaldavProtocolException(e.getMessage());
   1.580 +		}
   1.581 +	}
   1.582 +
   1.583 +	private void checkStatus(HttpResponse response)
   1.584 +			throws AuthenticationException, FileNotFoundException,
   1.585 +			ClientProtocolException {
   1.586 +		final int statusCode = response.getStatusLine().getStatusCode();
   1.587 +		lastStatusCode = statusCode;
   1.588 +		if (response.containsHeader("ETag"))
   1.589 +			lastETag = response.getFirstHeader("ETag").getValue();
   1.590 +		else
   1.591 +			lastETag = "";
   1.592 +		if (response.containsHeader("DAV"))
   1.593 +			lastDav = response.getFirstHeader("DAV").getValue();
   1.594 +		else
   1.595 +			lastDav = "";
   1.596 +		
   1.597 +		switch (statusCode) {
   1.598 +		case 401:
   1.599 +			throw new AuthenticationException();
   1.600 +		case 404:
   1.601 +			throw new FileNotFoundException();
   1.602 +		case 409: //Conflict
   1.603 +		case 412:
   1.604 +		case 200:
   1.605 +		case 201:
   1.606 +		case 204:
   1.607 +		case 207:
   1.608 +			return;
   1.609 +		default:
   1.610 +			throw new ClientProtocolException("StatusCode: " + statusCode);
   1.611 +		}
   1.612 +	}
   1.613 +
   1.614 +	private HttpPropFind createPropFindRequest(URI uri, String data, int depth) {
   1.615 +		HttpPropFind request = new HttpPropFind();
   1.616 +
   1.617 +		request.setURI(uri);
   1.618 +		//request.setHeader("Host", targetHost.getHostName());
   1.619 +		request.setHeader("Host", targetHost.getHostName() + ":" + String.valueOf(targetHost.getPort()));
   1.620 +		request.setHeader("Depth", Integer.toString(depth));
   1.621 +		request.setHeader("Content-Type", "application/xml;charset=\"UTF-8\"");
   1.622 +		try {
   1.623 +			request.setEntity(new StringEntity(data, "UTF-8"));
   1.624 +		} catch (UnsupportedEncodingException e) {
   1.625 +			throw new AssertionError("UTF-8 is unknown");
   1.626 +		}
   1.627 +		return request;
   1.628 +	}
   1.629 +	
   1.630 +	private HttpDelete createDeleteRequest(URI uri) {
   1.631 +		HttpDelete request = new HttpDelete();
   1.632 +		request.setURI(uri);
   1.633 +		//request.setHeader("Host", targetHost.getHostName());
   1.634 +		request.setHeader("Host", targetHost.getHostName() + ":" + String.valueOf(targetHost.getPort()));
   1.635 +		request.setHeader("Content-Type", "application/xml;charset=\"UTF-8\"");
   1.636 +		return request;
   1.637 +	}
   1.638 +
   1.639 +	private HttpPut createPutRequest(URI uri, String data, int depth) {
   1.640 +		HttpPut request = new HttpPut();
   1.641 +		request.setURI(uri);
   1.642 +		//request.setHeader("Host", targetHost.getHostName());
   1.643 +		request.setHeader("Host", targetHost.getHostName() + ":" + String.valueOf(targetHost.getPort()));
   1.644 +		//request.setHeader("Content-Type", "application/xml;charset=\"UTF-8\"");
   1.645 +		request.setHeader("Content-Type", "text/calendar; charset=UTF-8");
   1.646 +		try {
   1.647 +			request.setEntity(new StringEntity(data, "UTF-8"));
   1.648 +			//request.setEntity(new StringEntity(data));
   1.649 +		} catch (UnsupportedEncodingException e) {
   1.650 +			throw new AssertionError("UTF-8 is unknown");
   1.651 +		}
   1.652 +		return request;
   1.653 +	}
   1.654 +	
   1.655 +	private static HttpReport createReportRequest(URI uri, String data, int depth) {
   1.656 +		HttpReport request = new HttpReport();
   1.657 +		request.setURI(uri);
   1.658 +		//request.setHeader("Host", targetHost.getHostName());
   1.659 +		request.setHeader("Host", targetHost.getHostName() + ":" + String.valueOf(targetHost.getPort()));
   1.660 +		request.setHeader("Depth", Integer.toString(depth));
   1.661 +		request.setHeader("Content-Type", "application/xml;charset=\"UTF-8\"");
   1.662 +		//request.setHeader("Content-Type", "text/xml;charset=\"UTF-8\"");
   1.663 +		try {
   1.664 +			request.setEntity(new StringEntity(data));
   1.665 +		} catch (UnsupportedEncodingException e) {
   1.666 +			throw new AssertionError("UTF-8 is unknown");
   1.667 +		}
   1.668 +		return request;
   1.669 +	}
   1.670 +	
   1.671 +	public static void fetchEvent_old(CalendarEvent calendarEvent)
   1.672 +			throws ClientProtocolException, IOException {
   1.673 +		HttpGet request = null;
   1.674 +
   1.675 +		request = new HttpGet();
   1.676 +		request.setURI(calendarEvent.getUri());
   1.677 +		request.setHeader("Host", targetHost.getHostName());
   1.678 +		request.setHeader("Content-Type", "application/xml;charset=\"UTF-8\"");
   1.679 +
   1.680 +		HttpResponse response = httpClient.execute(targetHost, request);
   1.681 +
   1.682 +		BufferedReader reader = new BufferedReader(new InputStreamReader(
   1.683 +				response.getEntity().getContent(), "UTF-8"));
   1.684 +
   1.685 +		String line;
   1.686 +		String body = "";
   1.687 +		do {
   1.688 +			line = reader.readLine();
   1.689 +			if (line != null)
   1.690 +				body += line + "\n";
   1.691 +		} while (line != null);
   1.692 +
   1.693 +		calendarEvent.setICSasString(body);
   1.694 +
   1.695 +		Log.d(TAG, "HttpResponse GET event status=" + response.getStatusLine()
   1.696 +				+ " body= " + body);
   1.697 +	}
   1.698 +	
   1.699 +	public static boolean getEvent(CalendarEvent calendarEvent) throws ClientProtocolException, IOException {
   1.700 +		boolean Result = false;
   1.701 +		HttpReport request = null;
   1.702 +
   1.703 +		//HINT: bugfix for google calendar: replace("@", "%40")
   1.704 +		String data = XML_VERSION +
   1.705 +				"<C:calendar-multiget xmlns:D=\"DAV:\" xmlns:C=\"urn:ietf:params:xml:ns:caldav\">" +
   1.706 +					"<D:prop>" +
   1.707 +						"<D:getetag />" +
   1.708 +						"<C:calendar-data />" +
   1.709 +					"</D:prop>" +
   1.710 +					"<D:href>" + calendarEvent.getUri().getRawPath().replace("@", "%40") + "</D:href>" +
   1.711 +				"</C:calendar-multiget>";
   1.712 +
   1.713 +		URI calendarURI = null;
   1.714 +		try {
   1.715 +			calendarURI = calendarEvent.calendarURL.toURI();
   1.716 +		} catch (URISyntaxException e) {
   1.717 +			e.printStackTrace();
   1.718 +		}
   1.719 +		//request = createReportRequest(calendarEvent.getUri(), data, 1);
   1.720 +		request = createReportRequest(calendarURI, data, 1);
   1.721 +
   1.722 +		HttpResponse response = httpClient.execute(targetHost, request);
   1.723 +
   1.724 +		BufferedReader reader = new BufferedReader(new InputStreamReader(
   1.725 +				response.getEntity().getContent(), "UTF-8"));
   1.726 +
   1.727 +		String line;
   1.728 +		String body = "";
   1.729 +		do {
   1.730 +			line = reader.readLine();
   1.731 +			if (line != null)
   1.732 +				body += line + "\n";
   1.733 +		} while (line != null);
   1.734 +
   1.735 +		if (calendarEvent.setICSasMultiStatus(body))
   1.736 +			Result = true;
   1.737 +
   1.738 +		return Result;
   1.739 +	}
   1.740 +	
   1.741 +		
   1.742 +	/**
   1.743 +	 * sends a update event request to the server 
   1.744 +	 * @param uri the full URI of the event on server side. example: http://caldav.example.com/principal/user/calendar/e6be67c6-eff0-44f8-a1a0-6c2cb1029944-caldavsyncadapter.ics
   1.745 +	 * @param data the full ical-data for the event
   1.746 +	 * @param ETag the ETAG of this event is send within the "If-Match" Parameter to tell the server only to update this version
   1.747 +	 * @return
   1.748 +	 */
   1.749 +	public boolean updateEvent(URI uri, String data, String ETag) {
   1.750 +		boolean Result = false;
   1.751 +		
   1.752 +		try {
   1.753 +			HttpPut request = createPutRequest(uri, data, 1);
   1.754 +			request.addHeader(mstrcHeaderIfMatch, ETag);
   1.755 +			HttpResponse response = httpClient.execute(targetHost, request, mContext);
   1.756 +			checkStatus(response);
   1.757 +			if ((lastStatusCode == 200) || (lastStatusCode == 201) || (lastStatusCode == 204)) {
   1.758 +				Result = true;
   1.759 +			} else if (lastStatusCode == 412) {
   1.760 +				//Precondition failed
   1.761 +				Result = false;
   1.762 +			} else if (lastStatusCode == 409) {
   1.763 +				//Conflict
   1.764 +				Result = false;
   1.765 +			} else {
   1.766 +				Log.w(TAG, "Unkown StatusCode during creation of an event");
   1.767 +			}
   1.768 +		} catch (ClientProtocolException e) {
   1.769 +			e.printStackTrace();
   1.770 +		} catch (IOException e) {
   1.771 +			e.printStackTrace();
   1.772 +		} catch (AuthenticationException e) {
   1.773 +			e.printStackTrace();
   1.774 +		}
   1.775 +		return Result;
   1.776 +	}
   1.777 +	
   1.778 +	/**
   1.779 +	 * sends a create event request to server
   1.780 +	 * @param uri the full URI of the new event on server side. example: http://caldav.example.com/principal/user/calendar/e6be67c6-eff0-44f8-a1a0-6c2cb1029944-caldavsyncadapter.ics
   1.781 +	 * @param data the full ical-data for the new event
   1.782 +	 * @return success of this function
   1.783 +	 */
   1.784 +	public boolean createEvent(URI uri, String data) {
   1.785 +		boolean Result = false;
   1.786 +		
   1.787 +		try {
   1.788 +			HttpPut request = createPutRequest(uri, data, 1);
   1.789 +			request.addHeader(mstrcHeaderIfNoneMatch, "*");
   1.790 +			HttpResponse response = httpClient.execute(targetHost, request, mContext);
   1.791 +			checkStatus(response);
   1.792 +			if (lastStatusCode == 201) {
   1.793 +				Result = true;
   1.794 +			} else {
   1.795 +				Log.w(TAG, "Unkown StatusCode during creation of an event");
   1.796 +			}
   1.797 +		} catch (ClientProtocolException e) {
   1.798 +			e.printStackTrace();
   1.799 +		} catch (IOException e) {
   1.800 +			e.printStackTrace();
   1.801 +		} catch (AuthenticationException e) {
   1.802 +			e.printStackTrace();
   1.803 +		}
   1.804 +		return Result;
   1.805 +	}
   1.806 +	
   1.807 +	/**
   1.808 +	 * sends a delete event request to the server
   1.809 +	 * @param calendarEventUri  the full URI of the event on server side. example: http://caldav.example.com/principal/user/calendar/e6be67c6-eff0-44f8-a1a0-6c2cb1029944-caldavsyncadapter.ics
   1.810 +	 * @param ETag the ETAG of this event is send within the "If-Match" Parameter to tell the server only to delete this version
   1.811 +	 * @return success of this function
   1.812 +	 */
   1.813 +	public boolean deleteEvent(URI calendarEventUri, String ETag) {
   1.814 +		boolean Result = false;
   1.815 +		
   1.816 +		try {
   1.817 +			HttpDelete request = createDeleteRequest(calendarEventUri);
   1.818 +			request.addHeader(mstrcHeaderIfMatch, ETag);
   1.819 +			HttpResponse response = httpClient.execute(targetHost, request, mContext);
   1.820 +			checkStatus(response);
   1.821 +			if ((lastStatusCode == 204) || (lastStatusCode == 200)) {
   1.822 +				Result = true;
   1.823 +			} else {
   1.824 +				Log.w(TAG, "Unkown StatusCode during deletion of an event");
   1.825 +			}
   1.826 +		} catch (ClientProtocolException e) {
   1.827 +			e.printStackTrace();
   1.828 +		} catch (IOException e) {
   1.829 +			if (lastStatusCode == 404) {
   1.830 +				//the event has already been deleted on server side. no action needed
   1.831 +				Result = true;
   1.832 +			} else {
   1.833 +				e.printStackTrace();
   1.834 +			}
   1.835 +		} catch (AuthenticationException e) {
   1.836 +			e.printStackTrace();
   1.837 +		}
   1.838 +		
   1.839 +		return Result;
   1.840 +	}
   1.841 +	
   1.842 +	/**
   1.843 +	 * returns the ETAG send by the last server response.
   1.844 +	 * @return the ETAG
   1.845 +	 */
   1.846 +	public String getLastETag() {
   1.847 +		return lastETag;
   1.848 +	}
   1.849 +	
   1.850 +	/**
   1.851 +	 * returns the DAV-Options send by the last server response.
   1.852 +	 * @return the DAV-Options
   1.853 +	 */
   1.854 +	public String getLastDav() {
   1.855 +		return lastDav;
   1.856 +	}
   1.857 +	
   1.858 +	public void setVersion(String version) {
   1.859 +		VERSION = version;
   1.860 +		((AbstractHttpClient) httpClient).getParams().setParameter(CoreProtocolPNames.USER_AGENT, this.USER_AGENT + " Version:" + VERSION);
   1.861 +	}
   1.862 +	
   1.863 +	public void setAccount(Account account) {
   1.864 +		this.mAccount = account;
   1.865 +	}
   1.866 +	public void setProvider(ContentProviderClient provider) {
   1.867 +		this.mProvider = provider;
   1.868 +	}
   1.869 +}
   1.870 \ No newline at end of file

mercurial