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

Tue, 10 Feb 2015 18:12:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 10 Feb 2015 18:12:00 +0100
changeset 0
fb9019fb1bf7
child 8
ec8af0e3fbc2
permissions
-rw-r--r--

Import initial revisions of existing project AndroidCaldavSyncAdapater,
forked from upstream repository at 27e8a0f8495c92e0780d450bdf0c7cec77a03a55.

     1 /**
     2  * Copyright (c) 2012-2013, Gerald Garcia, David Wiesner, Timo Berger
     3  * 
     4  * This file is part of Andoid Caldav Sync Adapter Free.
     5  *
     6  * Andoid Caldav Sync Adapter Free is free software: you can redistribute 
     7  * it and/or modify it under the terms of the GNU General Public License 
     8  * as published by the Free Software Foundation, either version 3 of the 
     9  * License, or at your option any later version.
    10  *
    11  * Andoid Caldav Sync Adapter Free is distributed in the hope that 
    12  * it will be useful, but WITHOUT ANY WARRANTY; without even the implied 
    13  * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  * GNU General Public License for more details.
    15  *
    16  * You should have received a copy of the GNU General Public License
    17  * along with Andoid Caldav Sync Adapter Free.  
    18  * If not, see <http://www.gnu.org/licenses/>.
    19  * 
    20  */
    22 package org.gege.caldavsyncadapter.caldav;
    24 import java.io.BufferedReader;
    25 import java.io.ByteArrayInputStream;
    26 import java.io.FileNotFoundException;
    27 import java.io.IOException;
    28 import java.io.InputStream;
    29 import java.io.InputStreamReader;
    30 import java.io.UnsupportedEncodingException;
    31 import java.net.MalformedURLException;
    32 import java.net.SocketException;
    33 import java.net.URI;
    34 import java.net.URISyntaxException;
    35 import java.net.URL;
    36 import java.util.ArrayList;
    37 import java.util.List;
    39 import javax.xml.parsers.DocumentBuilder;
    40 import javax.xml.parsers.DocumentBuilderFactory;
    41 import javax.xml.parsers.ParserConfigurationException;
    42 import javax.xml.parsers.SAXParser;
    43 import javax.xml.parsers.SAXParserFactory;
    45 import org.apache.http.HttpException;
    46 import org.apache.http.HttpHost;
    47 import org.apache.http.HttpRequest;
    48 import org.apache.http.HttpRequestInterceptor;
    49 import org.apache.http.HttpResponse;
    50 import org.apache.http.HttpVersion;
    51 import org.apache.http.auth.AuthScope;
    52 import org.apache.http.auth.AuthState;
    53 import org.apache.http.auth.AuthenticationException;
    54 import org.apache.http.auth.UsernamePasswordCredentials;
    55 import org.apache.http.client.ClientProtocolException;
    56 import org.apache.http.client.CredentialsProvider;
    57 import org.apache.http.client.HttpClient;
    58 import org.apache.http.client.methods.HttpDelete;
    59 import org.apache.http.client.methods.HttpGet;
    60 import org.apache.http.client.methods.HttpPut;
    61 import org.apache.http.client.protocol.ClientContext;
    62 import org.apache.http.conn.HttpHostConnectException;
    63 import org.apache.http.conn.params.ConnManagerPNames;
    64 import org.apache.http.conn.params.ConnPerRouteBean;
    65 import org.apache.http.conn.scheme.PlainSocketFactory;
    66 import org.apache.http.conn.scheme.Scheme;
    67 import org.apache.http.conn.scheme.SchemeRegistry;
    68 import org.apache.http.conn.ssl.SSLSocketFactory;
    69 import org.apache.http.entity.StringEntity;
    70 import org.apache.http.impl.client.AbstractHttpClient;
    71 import org.apache.http.impl.client.DefaultHttpClient;
    72 import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
    73 import org.apache.http.params.BasicHttpParams;
    74 import org.apache.http.params.CoreProtocolPNames;
    75 import org.apache.http.params.HttpParams;
    76 import org.apache.http.params.HttpProtocolParams;
    77 import org.apache.http.protocol.BasicHttpContext;
    78 import org.apache.http.protocol.HttpContext;
    79 import org.gege.caldavsyncadapter.BuildConfig;
    80 import org.gege.caldavsyncadapter.caldav.entities.DavCalendar;
    81 import org.gege.caldavsyncadapter.caldav.entities.DavCalendar.CalendarSource;
    82 import org.gege.caldavsyncadapter.caldav.entities.CalendarEvent;
    83 import org.gege.caldavsyncadapter.caldav.entities.CalendarList;
    84 import org.gege.caldavsyncadapter.caldav.http.HttpPropFind;
    85 import org.gege.caldavsyncadapter.caldav.http.HttpReport;
    86 import org.gege.caldavsyncadapter.caldav.xml.CalendarHomeHandler;
    87 import org.gege.caldavsyncadapter.caldav.xml.CalendarsHandler;
    88 import org.gege.caldavsyncadapter.caldav.xml.ServerInfoHandler;
    89 import org.gege.caldavsyncadapter.syncadapter.notifications.NotificationsHelper;
    90 import org.w3c.dom.Document;
    91 import org.w3c.dom.Element;
    92 import org.w3c.dom.Node;
    93 import org.w3c.dom.NodeList;
    94 import org.xml.sax.ContentHandler;
    95 import org.xml.sax.InputSource;
    96 import org.xml.sax.SAXException;
    97 import org.xml.sax.XMLReader;
    99 import android.accounts.Account;
   100 import android.content.ContentProviderClient;
   101 import android.content.Context;
   102 import android.util.Log;
   104 public class CaldavFacade {
   105 	private static final String TAG = "CaldavFacade";
   107 	private final static String XML_VERSION = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
   109 	private String USER_AGENT = "CalDAV Sync Adapter (Android) https://github.com/gggard/AndroidCaldavSyncAdapater";
   110 	private String VERSION = ""; 
   112 	private static HttpClient httpClient;
   113 	private HttpContext mContext = null;
   114 	private AuthState mLastAuthState = null;
   115 	private AuthScope mLastAuthScope = null;
   117 	private boolean trustAll = true;
   119 	private URL url;
   121 	private static HttpHost targetHost;
   123 	private int lastStatusCode;
   124 	private String lastETag;
   125 	private String lastDav;
   127 	private String mstrcHeaderIfMatch = "If-Match";
   128 	private String mstrcHeaderIfNoneMatch = "If-None-Match";
   130 	private Account mAccount = null;
   131 	private ContentProviderClient mProvider;
   133 	protected HttpClient getHttpClient() {
   135 		HttpParams params = new BasicHttpParams();
   136 		params.setParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 30);
   137 		params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, new ConnPerRouteBean(30));
   138 		params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, false);
   139 		HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
   141 		SchemeRegistry registry = new SchemeRegistry();
   142 		registry.register(new Scheme("http", new PlainSocketFactory(), 80));
   143 		registry.register(new Scheme("https", (trustAll ? EasySSLSocketFactory.getSocketFactory() : SSLSocketFactory.getSocketFactory()), 443));
   144 		DefaultHttpClient client = new DefaultHttpClient(new ThreadSafeClientConnManager(params, registry), params);
   146 		return client;
   147 	}
   149 	public CaldavFacade(String mUser, String mPassword, String mURL) throws MalformedURLException {
   150 		url = new URL(mURL);
   152 		httpClient = getHttpClient();
   153 		UsernamePasswordCredentials upc = new UsernamePasswordCredentials(mUser, mPassword);
   155 		AuthScope as = null;
   156 		as = new AuthScope(url.getHost(), -1);
   157 		((AbstractHttpClient) httpClient).getCredentialsProvider().setCredentials(as, upc);
   159 		mContext = new BasicHttpContext();
   160 		CredentialsProvider credProvider = ((AbstractHttpClient) httpClient).getCredentialsProvider();
   161 		mContext.setAttribute(ClientContext.CREDS_PROVIDER, credProvider);
   163 		//http://dlinsin.blogspot.de/2009/08/http-basic-authentication-with-android.html
   164 		((AbstractHttpClient) httpClient).addRequestInterceptor(preemptiveAuth, 0);
   166 		String proto = "http";
   167 		int port = 80;
   169 		if (url.getProtocol().equalsIgnoreCase("https")) {
   170 			proto = "https";
   171 			if (url.getPort() == -1)
   172 				port = 443;
   173 			else
   174 				port = url.getPort();
   175 		}
   177 		if (url.getProtocol().equalsIgnoreCase("http")) {
   178 			proto = "http";
   179 			if (url.getPort() == -1)
   180 				port = 80;
   181 			else
   182 				port = url.getPort();
   183 		}
   184 		targetHost = new HttpHost(url.getHost(), port, proto);
   185 	}
   187 	//http://dlinsin.blogspot.de/2009/08/http-basic-authentication-with-android.html
   188 	HttpRequestInterceptor preemptiveAuth = new HttpRequestInterceptor() {
   189 		@Override
   190 	    public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException {
   191 	        AuthState authState = (AuthState) context.getAttribute(ClientContext.TARGET_AUTH_STATE);
   193 	        if (authState.getAuthScheme() == null) {
   194 	        	if (mLastAuthState != null) {
   195 	        		Log.d(TAG, "LastAuthState: restored with user " + mLastAuthState.getCredentials().getUserPrincipal().getName());
   196 	        		authState.setAuthScheme(mLastAuthState.getAuthScheme());
   197 	        		authState.setCredentials(mLastAuthState.getCredentials());
   198 	        	} else {
   199 	        		Log.d(TAG, "LastAuthState: nothing to do");
   200 	        	}
   201 	        	if (mLastAuthScope != null) {
   202 	        		authState.setAuthScope(mLastAuthScope);
   203 	        		Log.d(TAG, "LastAuthScope: restored");
   204 	        	} else {
   205 	        		Log.d(TAG, "LastAuthScope: nothing to do");
   206 	        	}
   207 	        } else {
   208 	        	//AuthState and AuthScope have to be saved separate because of the AuthScope within AuthState gets lost, so we save it in a separate var.
   209 	        	mLastAuthState = authState;
   210 	        	Log.d(TAG, "LastAuthState: new with user " + mLastAuthState.getCredentials().getUserPrincipal().getName());
   211 	        	if (authState.getAuthScope() != null) {
   212 	        		mLastAuthScope = authState.getAuthScope();
   213 	        		Log.d(TAG, "LastAuthScope: new");
   214 	        	}
   215 	        }
   216 	    }
   217 	};
   219 	public enum TestConnectionResult {
   220 		WRONG_CREDENTIAL, 
   221 		WRONG_URL, 
   222 		WRONG_SERVER_STATUS, 
   223 		WRONG_ANSWER, 
   224 		SUCCESS
   225 	}
   227 	/**
   228 	 * TODO: testConnection should return only an instance of
   229 	 * TestConnectionResult without throwing an exception or only throw checked
   230 	 * exceptions so you don't have to check the result of this function AND
   231 	 * handle the exceptions
   232 	 * @param context 
   233 	 * 
   234 	 * @return {@link TestConnectionResult}
   235 	 * @throws HttpHostConnectException
   236 	 * @throws IOException
   237 	 * @throws URISyntaxException
   238 	 * @throws ParserConfigurationException
   239 	 * @throws SAXException
   240 	 */
   241 	public TestConnectionResult testConnection() throws HttpHostConnectException, IOException, URISyntaxException, ParserConfigurationException, SAXException {
   242 		Log.d(TAG, "start testConnection ");
   243 		try {
   244 			List<DavCalendar> calendars = new ArrayList<DavCalendar>();
   245 			calendars = forceGetCalendarsFromUri(null, url.toURI());
   246 			if (calendars.size() != 0) {
   247 				return TestConnectionResult.SUCCESS;
   248 			}
   250 			URI userPrincipal = getUserPrincipal();
   251 			List<URI> calendarSets = getCalendarHomes(userPrincipal);
   252 			for (URI calendarSet : calendarSets) {
   253 				List<DavCalendar> calendarSetCalendars = getCalendarsFromSet(calendarSet);
   254 				calendars.addAll(calendarSetCalendars);
   255 			}
   256 			if (calendarSets.size() == 0) {
   257 				return TestConnectionResult.WRONG_ANSWER;
   258 			}
   259 		} catch (FileNotFoundException e) {
   260 			return TestConnectionResult.WRONG_URL;
   261 		} catch (SocketException e) {
   262 			return TestConnectionResult.WRONG_URL;
   263 		} catch (AuthenticationException e) {
   264 			return TestConnectionResult.WRONG_CREDENTIAL;
   265 		} catch (ClientProtocolException e) {
   266 			return TestConnectionResult.WRONG_SERVER_STATUS;
   267 		} catch (CaldavProtocolException e) {
   268 			return TestConnectionResult.WRONG_ANSWER;
   269 		}
   270 		return TestConnectionResult.SUCCESS;
   271 	}
   273 	/**
   274 	 * @param context May be null if no notification is needed
   275 	 * @param uri
   276 	 * @return
   277 	 * @throws AuthenticationException
   278 	 * @throws FileNotFoundException
   279 	 */
   280 	private List<DavCalendar> forceGetCalendarsFromUri(Context context, URI uri) throws AuthenticationException, FileNotFoundException {
   281 		List<DavCalendar> calendars = new ArrayList<DavCalendar>();
   282 		Exception exception = null;
   283 		try {
   284 			calendars = getCalendarsFromSet(uri);
   285 		} catch (ClientProtocolException e) {
   286 			if (context != null) {
   287 				NotificationsHelper.signalSyncErrors(context, "Caldav sync problem", e.getMessage());
   288 				//NotificationsHelper.getCurrentSyncLog().addException(e);
   289 			}
   290 			exception = e;
   291 		} catch (FileNotFoundException e) {
   292 			if (context != null) {
   293 				NotificationsHelper.signalSyncErrors(context, "Caldav sync problem", e.getMessage());
   294 				//NotificationsHelper.getCurrentSyncLog().addException(e);
   295 			}
   296 			throw e;
   297 		} catch (IOException e) {
   298 			if (context != null) {
   299 				NotificationsHelper.signalSyncErrors(context, "Caldav sync problem", e.getMessage());
   300 				//NotificationsHelper.getCurrentSyncLog().addException(e);
   301 			}
   302 			exception = e;
   303 		} catch (CaldavProtocolException e) {
   305 			if (context != null) {
   306 				NotificationsHelper.signalSyncErrors(context, "Caldav sync problem", e.getMessage());
   307 				//NotificationsHelper.getCurrentSyncLog().addException(e);
   308 			}
   309 			exception = e;
   310 		}
   311 		if (exception != null && BuildConfig.DEBUG) {
   312 			Log.e(TAG, "Force get calendars from '" + uri.toString()
   313 					+ "' failed " + exception.getClass().getCanonicalName()
   314 					+ ": " + exception.getMessage());
   315 		}
   316 		return calendars;
   317 	}
   319 	private final static String PROPFIND_USER_PRINCIPAL = XML_VERSION +
   320 			"<d:propfind xmlns:d=\"DAV:\">" +
   321 				"<d:prop>" +
   322 					"<d:current-user-principal />" +
   323 					"<d:principal-URL />" +
   324 				"</d:prop>" +
   325 			"</d:propfind>";
   327 	private URI getUserPrincipal() throws SocketException,
   328 			ClientProtocolException, AuthenticationException,
   329 			FileNotFoundException, IOException, CaldavProtocolException,
   330 			URISyntaxException {
   331 		URI uri = this.url.toURI();
   332 		HttpPropFind request = createPropFindRequest(uri,
   333 				PROPFIND_USER_PRINCIPAL, 0);
   334 		HttpResponse response = httpClient.execute(targetHost, request, mContext);
   335 		checkStatus(response);
   336 		ServerInfoHandler serverInfoHandler = new ServerInfoHandler();
   337 		parseXML(response, serverInfoHandler);
   338 		String userPrincipal = null;
   339 		if (serverInfoHandler.currentUserPrincipal != null) {
   340 			userPrincipal = serverInfoHandler.currentUserPrincipal;
   341 		} else if (serverInfoHandler.principalUrl != null) {
   342 			userPrincipal = serverInfoHandler.principalUrl;
   343 		} else {
   344 			throw new CaldavProtocolException("no principal url found");
   345 		}
   346 		try {
   347 			URI userPrincipalUri = new URI(userPrincipal);
   348 			userPrincipalUri = uri.resolve(userPrincipalUri);
   349 			if (BuildConfig.DEBUG) {
   350 				Log.d(TAG,
   351 						"Found userPrincipal: " + userPrincipalUri.toString());
   352 			}
   353 			return userPrincipalUri;
   354 		} catch (URISyntaxException e) {
   355 			throw new CaldavProtocolException("principal url '" + userPrincipal
   356 					+ "' malformed");
   357 		}
   358 	}
   360 	private final static String PROPFIND_CALENDAR_HOME_SET = XML_VERSION
   361 			+ "<d:propfind xmlns:d=\"DAV:\" xmlns:c=\"urn:ietf:params:xml:ns:caldav\"><d:prop><c:calendar-home-set/></d:prop></d:propfind>";
   363 	private List<URI> getCalendarHomes(URI userPrincipal)
   364 			throws ClientProtocolException, IOException,
   365 			AuthenticationException, FileNotFoundException,
   366 			CaldavProtocolException {
   367 		HttpPropFind request = createPropFindRequest(userPrincipal,
   368 				PROPFIND_CALENDAR_HOME_SET, 0);
   369 		HttpResponse response = httpClient.execute(targetHost, request, mContext);
   370 		checkStatus(response);
   371 		CalendarHomeHandler calendarHomeHandler = new CalendarHomeHandler(
   372 				userPrincipal);
   373 		parseXML(response, calendarHomeHandler);
   374 		List<URI> result = calendarHomeHandler.calendarHomeSet;
   375 		if (BuildConfig.DEBUG) {
   376 			Log.d(TAG, result.size() + " calendar-home-set found in "
   377 					+ userPrincipal.toString());
   378 		}
   379 		return result;
   380 	}
   382 	private final static String PROPFIND_CALENDER_LIST = XML_VERSION
   383 			+ "<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/\">"
   384 			+ "<d:prop><d:displayname /><d:resourcetype />"
   385 			// +
   386 			// "<d:supported-method-set /><d:supported-report-set /><c:supported-calendar-component-set />"
   387 			// +
   388 			// "<c:calendar-description /><c:calendar-timezone /><c:calendar-free-busy-set />
   389 			+ "<ic:calendar-color />"
   390 			//<ic:calendar-order />"
   391 			+ "<cs:getctag /></d:prop></d:propfind>";
   394 	private List<DavCalendar> getCalendarsFromSet(URI calendarSet)
   395 			throws ClientProtocolException, IOException,
   396 			CaldavProtocolException, AuthenticationException,
   397 			FileNotFoundException {
   398 		HttpPropFind request = createPropFindRequest(calendarSet, PROPFIND_CALENDER_LIST, 1);
   399 		HttpResponse response = httpClient.execute(targetHost, request, mContext);
   400 		checkStatus(response);
   401 		CalendarsHandler calendarsHandler = new CalendarsHandler(calendarSet);
   402 		parseXML(response, calendarsHandler);
   403 		List<DavCalendar> result = calendarsHandler.calendars;
   404 		if (BuildConfig.DEBUG) {
   405 			Log.i(TAG,
   406 					result.size() + " calendars found in set "
   407 							+ calendarSet.toString());
   408 		}
   409 		return result;
   410 	}
   412 	/**
   413 	 * Discover CalDAV Calendars Mentioned in
   414 	 * http://tools.ietf.org/html/draft-daboo-srv-caldav-10#section-6 and
   415 	 * http://code.google.com/p/sabredav/wiki/BuildingACalDAVClient#Discovery
   416 	 * <ol>
   417 	 * <li>PROPFIND calendar-home-set on url
   418 	 * <li>PROPFIND DAV:current-user-principal or principal-URL on url
   419 	 * <li>PROPFIND calendar-home-set on current-user-principal or principal-URL
   420 	 * <li>PROPFIND displayname, resourcetype, getctag on CalendarHomeSets
   421 	 * </ol>
   422 	 * @param context 
   423 	 * 
   424 	 * @return List of {@link DavCalendar}
   425 	 * @throws ClientProtocolException
   426 	 *             http protocol error
   427 	 * @throws IOException
   428 	 *             Connection lost
   429 	 * @throws URISyntaxException
   430 	 *             url in Constructor malformed
   431 	 * @throws CaldavProtocolException
   432 	 *             caldav protocol error
   433 	 */
   434 	//public Iterable<Calendar> getCalendarList(Context context) throws ClientProtocolException,
   435 	public CalendarList getCalendarList(Context context) throws ClientProtocolException,
   436 			IOException, URISyntaxException, ParserConfigurationException,
   437 			CaldavProtocolException {
   438 		try {
   439 			CalendarList Result = new CalendarList(this.mAccount, this.mProvider, CalendarSource.CalDAV, this.url.toString());
   440 			List<DavCalendar> calendars = new ArrayList<DavCalendar>();
   442 			calendars = forceGetCalendarsFromUri(context, this.url.toURI());
   444 			if (calendars.size() == 0) {
   445 				// no calendars found, try the home-set
   446 				URI userPrincipal = getUserPrincipal();
   447 				List<URI> calendarSets = getCalendarHomes(userPrincipal);
   448 				for (URI calendarSet : calendarSets) {
   449 					List<DavCalendar> calendarSetCalendars = getCalendarsFromSet(calendarSet);
   450 					calendars.addAll(calendarSetCalendars);
   451 				}
   452 			}
   453 			for (DavCalendar cal : calendars) {
   454 				Result.addCalendar(cal);
   455 			}
   457 			//return calendars;
   458 			return Result;
   459 		} catch (AuthenticationException e) {
   460 			throw new IOException(e);
   461 		}
   462 	}
   464 	//public Iterable<CalendarEvent> getCalendarEvents(DavCalendar calendar)
   465 	public ArrayList<CalendarEvent> getCalendarEvents(DavCalendar calendar)
   466 			throws URISyntaxException, ClientProtocolException, IOException,
   467 			ParserConfigurationException, SAXException {
   469 		ArrayList<CalendarEvent> calendarEventList = new ArrayList<CalendarEvent>();
   471 		String requestBody = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
   472 				+ "<D:propfind xmlns:D=\"DAV:\">" + "<D:prop>" + "<D:getetag/>"
   473 				+ "</D:prop>" + "</D:propfind>";
   475 		HttpPropFind request = null;
   477 		String EventUri;
   479 		/*request = new HttpPropFind();
   480 		request.setURI(calendar.getURI());
   481 		request.setHeader("Host", targetHost.getHostName());
   482 		request.setHeader("Depth", "1");
   483 		request.setHeader("Content-Type", "application/xml;charset=\"UTF-8\"");
   485 		try {
   486 			request.setEntity(new StringEntity(requestBody, "UTF-8"));
   487 		} catch (UnsupportedEncodingException e) {
   488 			throw new AssertionError("UTF-8 is unknown");
   489 		}*/
   490 		request = this.createPropFindRequest(calendar.getURI(), requestBody, 1);
   492 		Log.d(TAG, "Getting eTag by PROPFIND at " + request.getURI());
   494 		HttpResponse response = httpClient.execute(targetHost, request, mContext);
   496 		BufferedReader reader = new BufferedReader(new InputStreamReader(
   497 				response.getEntity().getContent(), "UTF-8"));
   499 		String line;
   500 		String body = "";
   501 		do {
   502 			line = reader.readLine();
   503 			if (line != null)
   504 				body += line;
   505 		} while (line != null);
   507 		Log.d(TAG, "HttpResponse status=" + response.getStatusLine()
   508 				+ " body= " + body);
   510 		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
   511 		factory.setNamespaceAware(true);
   512 		DocumentBuilder builder = factory.newDocumentBuilder();
   513 		Document dom = builder.parse(new InputSource(new ByteArrayInputStream(
   514 				body.getBytes("utf-8"))));
   515 		Element root = dom.getDocumentElement();
   516 		NodeList items = root.getElementsByTagNameNS("*", "getetag");
   518 		for (int i = 0; i < items.getLength(); i++) {
   519 			CalendarEvent calendarEvent = new CalendarEvent(this.mAccount, this.mProvider);
   521 			Node node = items.item(i);
   523 			if (node.getTextContent().trim().length() == 0)
   524 				continue; // not an event
   526 			calendarEvent.setETag(node.getTextContent().trim());
   527 			//calendarEvent.calendarURL = this.url;
   528 			calendarEvent.calendarURL = calendar.getURI().toURL();
   530 			node = node.getParentNode(); // prop
   531 			node = node.getParentNode(); // propstat
   532 			node = node.getParentNode(); // response
   534 			NodeList children = node.getChildNodes();
   535 			for (int j = 0; j < children.getLength(); j++) {
   536 				Node childNode = children.item(j);
   537 				if ((childNode.getLocalName()!=null) && (childNode.getLocalName().equalsIgnoreCase("href"))) {
   538 					EventUri = childNode.getTextContent().trim();
   539 					//HINT: bugfix for zimbra calendar: replace("@", "%40")
   540 					EventUri = EventUri.replace("@", "%40");
   541 					calendarEvent.setUri(new URI(EventUri));
   542 				}
   543 			}
   545 			calendarEventList.add(calendarEvent);
   547 		}
   549 		return calendarEventList;
   550 	}
   552 	private void parseXML(HttpResponse response, ContentHandler contentHandler)
   553 			throws IOException, CaldavProtocolException {
   554 		InputStream is = response.getEntity().getContent();
   555 		/*BufferedReader bReader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
   556 		String Content = "";
   557 		String Line = bReader.readLine();
   559 		while (Line != null) {
   560 			Content += Line;
   561 			Line = bReader.readLine();
   562 		}*/
   564 		SAXParserFactory factory = SAXParserFactory.newInstance();
   565 		try {
   566 			SAXParser parser = factory.newSAXParser();
   567 			XMLReader reader = parser.getXMLReader();
   568 			reader.setContentHandler(contentHandler);
   569 			reader.parse(new InputSource(is));
   570 		} catch (ParserConfigurationException e) {
   571 			throw new AssertionError("ParserConfigurationException "
   572 					+ e.getMessage());
   573 		} catch (IllegalStateException e) {
   574 			throw new CaldavProtocolException(e.getMessage());
   575 		} catch (SAXException e) {
   576 			throw new CaldavProtocolException(e.getMessage());
   577 		}
   578 	}
   580 	private void checkStatus(HttpResponse response)
   581 			throws AuthenticationException, FileNotFoundException,
   582 			ClientProtocolException {
   583 		final int statusCode = response.getStatusLine().getStatusCode();
   584 		lastStatusCode = statusCode;
   585 		if (response.containsHeader("ETag"))
   586 			lastETag = response.getFirstHeader("ETag").getValue();
   587 		else
   588 			lastETag = "";
   589 		if (response.containsHeader("DAV"))
   590 			lastDav = response.getFirstHeader("DAV").getValue();
   591 		else
   592 			lastDav = "";
   594 		switch (statusCode) {
   595 		case 401:
   596 			throw new AuthenticationException();
   597 		case 404:
   598 			throw new FileNotFoundException();
   599 		case 409: //Conflict
   600 		case 412:
   601 		case 200:
   602 		case 201:
   603 		case 204:
   604 		case 207:
   605 			return;
   606 		default:
   607 			throw new ClientProtocolException("StatusCode: " + statusCode);
   608 		}
   609 	}
   611 	private HttpPropFind createPropFindRequest(URI uri, String data, int depth) {
   612 		HttpPropFind request = new HttpPropFind();
   614 		request.setURI(uri);
   615 		//request.setHeader("Host", targetHost.getHostName());
   616 		request.setHeader("Host", targetHost.getHostName() + ":" + String.valueOf(targetHost.getPort()));
   617 		request.setHeader("Depth", Integer.toString(depth));
   618 		request.setHeader("Content-Type", "application/xml;charset=\"UTF-8\"");
   619 		try {
   620 			request.setEntity(new StringEntity(data, "UTF-8"));
   621 		} catch (UnsupportedEncodingException e) {
   622 			throw new AssertionError("UTF-8 is unknown");
   623 		}
   624 		return request;
   625 	}
   627 	private HttpDelete createDeleteRequest(URI uri) {
   628 		HttpDelete request = new HttpDelete();
   629 		request.setURI(uri);
   630 		//request.setHeader("Host", targetHost.getHostName());
   631 		request.setHeader("Host", targetHost.getHostName() + ":" + String.valueOf(targetHost.getPort()));
   632 		request.setHeader("Content-Type", "application/xml;charset=\"UTF-8\"");
   633 		return request;
   634 	}
   636 	private HttpPut createPutRequest(URI uri, String data, int depth) {
   637 		HttpPut request = new HttpPut();
   638 		request.setURI(uri);
   639 		//request.setHeader("Host", targetHost.getHostName());
   640 		request.setHeader("Host", targetHost.getHostName() + ":" + String.valueOf(targetHost.getPort()));
   641 		//request.setHeader("Content-Type", "application/xml;charset=\"UTF-8\"");
   642 		request.setHeader("Content-Type", "text/calendar; charset=UTF-8");
   643 		try {
   644 			request.setEntity(new StringEntity(data, "UTF-8"));
   645 			//request.setEntity(new StringEntity(data));
   646 		} catch (UnsupportedEncodingException e) {
   647 			throw new AssertionError("UTF-8 is unknown");
   648 		}
   649 		return request;
   650 	}
   652 	private static HttpReport createReportRequest(URI uri, String data, int depth) {
   653 		HttpReport request = new HttpReport();
   654 		request.setURI(uri);
   655 		//request.setHeader("Host", targetHost.getHostName());
   656 		request.setHeader("Host", targetHost.getHostName() + ":" + String.valueOf(targetHost.getPort()));
   657 		request.setHeader("Depth", Integer.toString(depth));
   658 		request.setHeader("Content-Type", "application/xml;charset=\"UTF-8\"");
   659 		//request.setHeader("Content-Type", "text/xml;charset=\"UTF-8\"");
   660 		try {
   661 			request.setEntity(new StringEntity(data));
   662 		} catch (UnsupportedEncodingException e) {
   663 			throw new AssertionError("UTF-8 is unknown");
   664 		}
   665 		return request;
   666 	}
   668 	public static void fetchEvent_old(CalendarEvent calendarEvent)
   669 			throws ClientProtocolException, IOException {
   670 		HttpGet request = null;
   672 		request = new HttpGet();
   673 		request.setURI(calendarEvent.getUri());
   674 		request.setHeader("Host", targetHost.getHostName());
   675 		request.setHeader("Content-Type", "application/xml;charset=\"UTF-8\"");
   677 		HttpResponse response = httpClient.execute(targetHost, request);
   679 		BufferedReader reader = new BufferedReader(new InputStreamReader(
   680 				response.getEntity().getContent(), "UTF-8"));
   682 		String line;
   683 		String body = "";
   684 		do {
   685 			line = reader.readLine();
   686 			if (line != null)
   687 				body += line + "\n";
   688 		} while (line != null);
   690 		calendarEvent.setICSasString(body);
   692 		Log.d(TAG, "HttpResponse GET event status=" + response.getStatusLine()
   693 				+ " body= " + body);
   694 	}
   696 	public static boolean getEvent(CalendarEvent calendarEvent) throws ClientProtocolException, IOException {
   697 		boolean Result = false;
   698 		HttpReport request = null;
   700 		//HINT: bugfix for google calendar: replace("@", "%40")
   701 		String data = XML_VERSION +
   702 				"<C:calendar-multiget xmlns:D=\"DAV:\" xmlns:C=\"urn:ietf:params:xml:ns:caldav\">" +
   703 					"<D:prop>" +
   704 						"<D:getetag />" +
   705 						"<C:calendar-data />" +
   706 					"</D:prop>" +
   707 					"<D:href>" + calendarEvent.getUri().getRawPath().replace("@", "%40") + "</D:href>" +
   708 				"</C:calendar-multiget>";
   710 		URI calendarURI = null;
   711 		try {
   712 			calendarURI = calendarEvent.calendarURL.toURI();
   713 		} catch (URISyntaxException e) {
   714 			e.printStackTrace();
   715 		}
   716 		//request = createReportRequest(calendarEvent.getUri(), data, 1);
   717 		request = createReportRequest(calendarURI, data, 1);
   719 		HttpResponse response = httpClient.execute(targetHost, request);
   721 		BufferedReader reader = new BufferedReader(new InputStreamReader(
   722 				response.getEntity().getContent(), "UTF-8"));
   724 		String line;
   725 		String body = "";
   726 		do {
   727 			line = reader.readLine();
   728 			if (line != null)
   729 				body += line + "\n";
   730 		} while (line != null);
   732 		if (calendarEvent.setICSasMultiStatus(body))
   733 			Result = true;
   735 		return Result;
   736 	}
   739 	/**
   740 	 * sends a update event request to the server 
   741 	 * @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
   742 	 * @param data the full ical-data for the event
   743 	 * @param ETag the ETAG of this event is send within the "If-Match" Parameter to tell the server only to update this version
   744 	 * @return
   745 	 */
   746 	public boolean updateEvent(URI uri, String data, String ETag) {
   747 		boolean Result = false;
   749 		try {
   750 			HttpPut request = createPutRequest(uri, data, 1);
   751 			request.addHeader(mstrcHeaderIfMatch, ETag);
   752 			HttpResponse response = httpClient.execute(targetHost, request, mContext);
   753 			checkStatus(response);
   754 			if ((lastStatusCode == 200) || (lastStatusCode == 201) || (lastStatusCode == 204)) {
   755 				Result = true;
   756 			} else if (lastStatusCode == 412) {
   757 				//Precondition failed
   758 				Result = false;
   759 			} else if (lastStatusCode == 409) {
   760 				//Conflict
   761 				Result = false;
   762 			} else {
   763 				Log.w(TAG, "Unkown StatusCode during creation of an event");
   764 			}
   765 		} catch (ClientProtocolException e) {
   766 			e.printStackTrace();
   767 		} catch (IOException e) {
   768 			e.printStackTrace();
   769 		} catch (AuthenticationException e) {
   770 			e.printStackTrace();
   771 		}
   772 		return Result;
   773 	}
   775 	/**
   776 	 * sends a create event request to server
   777 	 * @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
   778 	 * @param data the full ical-data for the new event
   779 	 * @return success of this function
   780 	 */
   781 	public boolean createEvent(URI uri, String data) {
   782 		boolean Result = false;
   784 		try {
   785 			HttpPut request = createPutRequest(uri, data, 1);
   786 			request.addHeader(mstrcHeaderIfNoneMatch, "*");
   787 			HttpResponse response = httpClient.execute(targetHost, request, mContext);
   788 			checkStatus(response);
   789 			if (lastStatusCode == 201) {
   790 				Result = true;
   791 			} else {
   792 				Log.w(TAG, "Unkown StatusCode during creation of an event");
   793 			}
   794 		} catch (ClientProtocolException e) {
   795 			e.printStackTrace();
   796 		} catch (IOException e) {
   797 			e.printStackTrace();
   798 		} catch (AuthenticationException e) {
   799 			e.printStackTrace();
   800 		}
   801 		return Result;
   802 	}
   804 	/**
   805 	 * sends a delete event request to the server
   806 	 * @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
   807 	 * @param ETag the ETAG of this event is send within the "If-Match" Parameter to tell the server only to delete this version
   808 	 * @return success of this function
   809 	 */
   810 	public boolean deleteEvent(URI calendarEventUri, String ETag) {
   811 		boolean Result = false;
   813 		try {
   814 			HttpDelete request = createDeleteRequest(calendarEventUri);
   815 			request.addHeader(mstrcHeaderIfMatch, ETag);
   816 			HttpResponse response = httpClient.execute(targetHost, request, mContext);
   817 			checkStatus(response);
   818 			if ((lastStatusCode == 204) || (lastStatusCode == 200)) {
   819 				Result = true;
   820 			} else {
   821 				Log.w(TAG, "Unkown StatusCode during deletion of an event");
   822 			}
   823 		} catch (ClientProtocolException e) {
   824 			e.printStackTrace();
   825 		} catch (IOException e) {
   826 			if (lastStatusCode == 404) {
   827 				//the event has already been deleted on server side. no action needed
   828 				Result = true;
   829 			} else {
   830 				e.printStackTrace();
   831 			}
   832 		} catch (AuthenticationException e) {
   833 			e.printStackTrace();
   834 		}
   836 		return Result;
   837 	}
   839 	/**
   840 	 * returns the ETAG send by the last server response.
   841 	 * @return the ETAG
   842 	 */
   843 	public String getLastETag() {
   844 		return lastETag;
   845 	}
   847 	/**
   848 	 * returns the DAV-Options send by the last server response.
   849 	 * @return the DAV-Options
   850 	 */
   851 	public String getLastDav() {
   852 		return lastDav;
   853 	}
   855 	public void setVersion(String version) {
   856 		VERSION = version;
   857 		((AbstractHttpClient) httpClient).getParams().setParameter(CoreProtocolPNames.USER_AGENT, this.USER_AGENT + " Version:" + VERSION);
   858 	}
   860 	public void setAccount(Account account) {
   861 		this.mAccount = account;
   862 	}
   863 	public void setProvider(ContentProviderClient provider) {
   864 		this.mProvider = provider;
   865 	}
   866 }

mercurial