michael@0: /**
michael@0: * Copyright (c) 2012-2013, Gerald Garcia, David Wiesner, Timo Berger
michael@8: *
michael@0: * This file is part of Andoid Caldav Sync Adapter Free.
michael@0: *
michael@0: * Andoid Caldav Sync Adapter Free is free software: you can redistribute
michael@0: * it and/or modify it under the terms of the GNU General Public License
michael@0: * as published by the Free Software Foundation, either version 3 of the
michael@0: * License, or at your option any later version.
michael@0: *
michael@0: * Andoid Caldav Sync Adapter Free is distributed in the hope that
michael@0: * it will be useful, but WITHOUT ANY WARRANTY; without even the implied
michael@0: * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
michael@0: * GNU General Public License for more details.
michael@0: *
michael@0: * You should have received a copy of the GNU General Public License
michael@0: * along with Andoid Caldav Sync Adapter Free.
michael@0: * If not, see .
michael@8: *
michael@0: */
michael@0:
michael@0: package org.gege.caldavsyncadapter.caldav;
michael@0:
michael@8: import android.accounts.Account;
michael@8: import android.content.ContentProviderClient;
michael@8: import android.content.Context;
michael@8: import android.util.Log;
michael@0:
michael@0: import org.apache.http.HttpException;
michael@0: import org.apache.http.HttpHost;
michael@0: import org.apache.http.HttpRequest;
michael@0: import org.apache.http.HttpRequestInterceptor;
michael@0: import org.apache.http.HttpResponse;
michael@0: import org.apache.http.HttpVersion;
michael@0: import org.apache.http.auth.AuthScope;
michael@0: import org.apache.http.auth.AuthState;
michael@0: import org.apache.http.auth.AuthenticationException;
michael@0: import org.apache.http.auth.UsernamePasswordCredentials;
michael@0: import org.apache.http.client.ClientProtocolException;
michael@0: import org.apache.http.client.CredentialsProvider;
michael@0: import org.apache.http.client.HttpClient;
michael@0: import org.apache.http.client.methods.HttpDelete;
michael@0: import org.apache.http.client.methods.HttpGet;
michael@0: import org.apache.http.client.methods.HttpPut;
michael@0: import org.apache.http.client.protocol.ClientContext;
michael@0: import org.apache.http.conn.HttpHostConnectException;
michael@0: import org.apache.http.conn.params.ConnManagerPNames;
michael@0: import org.apache.http.conn.params.ConnPerRouteBean;
michael@0: import org.apache.http.conn.scheme.PlainSocketFactory;
michael@0: import org.apache.http.conn.scheme.Scheme;
michael@0: import org.apache.http.conn.scheme.SchemeRegistry;
michael@0: import org.apache.http.conn.ssl.SSLSocketFactory;
michael@0: import org.apache.http.entity.StringEntity;
michael@0: import org.apache.http.impl.client.AbstractHttpClient;
michael@0: import org.apache.http.impl.client.DefaultHttpClient;
michael@0: import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
michael@0: import org.apache.http.params.BasicHttpParams;
michael@0: import org.apache.http.params.CoreProtocolPNames;
michael@0: import org.apache.http.params.HttpParams;
michael@0: import org.apache.http.params.HttpProtocolParams;
michael@0: import org.apache.http.protocol.BasicHttpContext;
michael@0: import org.apache.http.protocol.HttpContext;
michael@8: import org.apache.http.util.EntityUtils;
michael@0: import org.gege.caldavsyncadapter.BuildConfig;
michael@8: import org.gege.caldavsyncadapter.caldav.entities.CalendarEvent;
michael@8: import org.gege.caldavsyncadapter.caldav.entities.CalendarList;
michael@0: import org.gege.caldavsyncadapter.caldav.entities.DavCalendar;
michael@0: import org.gege.caldavsyncadapter.caldav.entities.DavCalendar.CalendarSource;
michael@0: import org.gege.caldavsyncadapter.caldav.http.HttpPropFind;
michael@0: import org.gege.caldavsyncadapter.caldav.http.HttpReport;
michael@0: import org.gege.caldavsyncadapter.caldav.xml.CalendarHomeHandler;
michael@0: import org.gege.caldavsyncadapter.caldav.xml.CalendarsHandler;
michael@0: import org.gege.caldavsyncadapter.caldav.xml.ServerInfoHandler;
michael@0: import org.gege.caldavsyncadapter.syncadapter.notifications.NotificationsHelper;
michael@0: import org.w3c.dom.Document;
michael@0: import org.w3c.dom.Element;
michael@0: import org.w3c.dom.Node;
michael@0: import org.w3c.dom.NodeList;
michael@0: import org.xml.sax.ContentHandler;
michael@0: import org.xml.sax.InputSource;
michael@0: import org.xml.sax.SAXException;
michael@0: import org.xml.sax.XMLReader;
michael@0:
michael@8: import java.io.ByteArrayInputStream;
michael@8: import java.io.FileNotFoundException;
michael@8: import java.io.IOException;
michael@8: import java.io.InputStream;
michael@8: import java.io.UnsupportedEncodingException;
michael@8: import java.net.MalformedURLException;
michael@8: import java.net.SocketException;
michael@8: import java.net.URI;
michael@8: import java.net.URISyntaxException;
michael@8: import java.net.URL;
michael@8: import java.util.ArrayList;
michael@8: import java.util.List;
michael@8:
michael@8: import javax.net.ssl.SSLException;
michael@8: import javax.xml.parsers.DocumentBuilder;
michael@8: import javax.xml.parsers.DocumentBuilderFactory;
michael@8: import javax.xml.parsers.ParserConfigurationException;
michael@8: import javax.xml.parsers.SAXParser;
michael@8: import javax.xml.parsers.SAXParserFactory;
michael@0:
michael@0: public class CaldavFacade {
michael@8: private static final String TAG = "CaldavFacade";
michael@0:
michael@8: private final static String XML_VERSION = "\n";
michael@0:
michael@8: private String USER_AGENT = "CalDAV Sync Adapter (Android) https://github.com/gggard/AndroidCaldavSyncAdapater";
michael@8: private String VERSION = "";
michael@0:
michael@8: private static HttpClient httpClient;
michael@8: private HttpContext mContext = null;
michael@8: private AuthState mLastAuthState = null;
michael@8: private AuthScope mLastAuthScope = null;
michael@0:
michael@8: private boolean mTrustAll = true;
michael@0:
michael@8: private URL url;
michael@0:
michael@8: private static HttpHost targetHost;
michael@0:
michael@8: private int lastStatusCode;
michael@8: private String lastETag;
michael@8: private String lastDav;
michael@0:
michael@8: private String mstrcHeaderIfMatch = "If-Match";
michael@8: private String mstrcHeaderIfNoneMatch = "If-None-Match";
michael@0:
michael@8: private Account mAccount = null;
michael@8: private ContentProviderClient mProvider;
michael@0:
michael@8: protected HttpClient getHttpClient() {
michael@0:
michael@8: HttpParams params = new BasicHttpParams();
michael@8: params.setParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 30);
michael@8: params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, new ConnPerRouteBean(30));
michael@8: params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, false);
michael@8: HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
michael@0:
michael@8: SchemeRegistry registry = new SchemeRegistry();
michael@8: registry.register(new Scheme("http", new PlainSocketFactory(), 80));
michael@8: registry.register(new Scheme("https", (mTrustAll ? EasySSLSocketFactory.getSocketFactory() : SSLSocketFactory
michael@8: .getSocketFactory()), 443));
michael@8: DefaultHttpClient client = new DefaultHttpClient(new ThreadSafeClientConnManager(params, registry), params);
michael@0:
michael@8: return client;
michael@8: }
michael@0:
michael@8: public CaldavFacade(String mUser, String mPassword, String mURL, String trustAll) throws MalformedURLException {
michael@8: url = new URL(mURL);
michael@0:
michael@8: this.mTrustAll = Boolean.valueOf(trustAll);
michael@0:
michael@8: httpClient = getHttpClient();
michael@8: UsernamePasswordCredentials upc = new UsernamePasswordCredentials(mUser, mPassword);
michael@0:
michael@8: AuthScope as = null;
michael@8: as = new AuthScope(url.getHost(), -1);
michael@8: ((AbstractHttpClient) httpClient).getCredentialsProvider().setCredentials(as, upc);
michael@0:
michael@8: mContext = new BasicHttpContext();
michael@8: CredentialsProvider credProvider = ((AbstractHttpClient) httpClient).getCredentialsProvider();
michael@8: mContext.setAttribute(ClientContext.CREDS_PROVIDER, credProvider);
michael@0:
michael@8: //http://dlinsin.blogspot.de/2009/08/http-basic-authentication-with-android.html
michael@8: ((AbstractHttpClient) httpClient).addRequestInterceptor(preemptiveAuth, 0);
michael@0:
michael@8: String proto = "http";
michael@8: int port = 80;
michael@0:
michael@8: if (url.getProtocol().equalsIgnoreCase("https")) {
michael@8: proto = "https";
michael@8: if (url.getPort() == -1)
michael@8: port = 443;
michael@8: else
michael@8: port = url.getPort();
michael@8: }
michael@0:
michael@8: if (url.getProtocol().equalsIgnoreCase("http")) {
michael@8: proto = "http";
michael@8: if (url.getPort() == -1)
michael@8: port = 80;
michael@8: else
michael@8: port = url.getPort();
michael@8: }
michael@8: targetHost = new HttpHost(url.getHost(), port, proto);
michael@8: }
michael@0:
michael@8: //http://dlinsin.blogspot.de/2009/08/http-basic-authentication-with-android.html
michael@8: HttpRequestInterceptor preemptiveAuth = new HttpRequestInterceptor() {
michael@8: @Override
michael@8: public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException {
michael@8: AuthState authState = (AuthState) context.getAttribute(ClientContext.TARGET_AUTH_STATE);
michael@0:
michael@8: if (authState.getAuthScheme() == null) {
michael@8: if (mLastAuthState != null) {
michael@8: Log.d(TAG, "LastAuthState: restored with user " + mLastAuthState.getCredentials()
michael@8: .getUserPrincipal()
michael@8: .getName());
michael@8: authState.setAuthScheme(mLastAuthState.getAuthScheme());
michael@8: authState.setCredentials(mLastAuthState.getCredentials());
michael@8: } else {
michael@8: Log.d(TAG, "LastAuthState: nothing to do");
michael@8: }
michael@8: if (mLastAuthScope != null) {
michael@8: authState.setAuthScope(mLastAuthScope);
michael@8: Log.d(TAG, "LastAuthScope: restored");
michael@8: } else {
michael@8: Log.d(TAG, "LastAuthScope: nothing to do");
michael@8: }
michael@8: } else {
michael@8: //AuthState and AuthScope have to be saved separate because of the AuthScope within AuthState gets lost, so we save it in a separate var.
michael@8: mLastAuthState = authState;
michael@8: Log.d(TAG, "LastAuthState: new with user " + mLastAuthState.getCredentials()
michael@8: .getUserPrincipal()
michael@8: .getName());
michael@8: if (authState.getAuthScope() != null) {
michael@8: mLastAuthScope = authState.getAuthScope();
michael@8: Log.d(TAG, "LastAuthScope: new");
michael@8: }
michael@8: }
michael@8: }
michael@8: };
michael@0:
michael@8: public enum TestConnectionResult {
michael@8: WRONG_CREDENTIAL,
michael@8: WRONG_URL,
michael@8: WRONG_SERVER_STATUS,
michael@8: WRONG_ANSWER,
michael@8: SSL_ERROR,
michael@8: SUCCESS
michael@8: }
michael@0:
michael@8: /**
michael@8: * TODO: testConnection should return only an instance of
michael@8: * TestConnectionResult without throwing an exception or only throw checked
michael@8: * exceptions so you don't have to check the result of this function AND
michael@8: * handle the exceptions
michael@8: *
michael@8: * @return {@link TestConnectionResult}
michael@8: * @throws HttpHostConnectException
michael@8: * @throws IOException
michael@8: * @throws URISyntaxException
michael@8: * @throws ParserConfigurationException
michael@8: * @throws SAXException
michael@8: */
michael@8: public TestConnectionResult testConnection() throws IOException, URISyntaxException, ParserConfigurationException, SAXException {
michael@8: Log.d(TAG, "start testConnection ");
michael@8: try {
michael@8: List calendars = new ArrayList();
michael@8: calendars = forceGetCalendarsFromUri(null, url.toURI());
michael@8: if (calendars.size() != 0) {
michael@8: return TestConnectionResult.SUCCESS;
michael@8: }
michael@0:
michael@8: URI userPrincipal = getUserPrincipal();
michael@8: List calendarSets = getCalendarHomes(userPrincipal);
michael@8: for (URI calendarSet : calendarSets) {
michael@8: List calendarSetCalendars = getCalendarsFromSet(calendarSet);
michael@8: calendars.addAll(calendarSetCalendars);
michael@8: }
michael@8: if (calendarSets.size() == 0) {
michael@8: return TestConnectionResult.WRONG_ANSWER;
michael@8: }
michael@8: } catch (FileNotFoundException e) {
michael@8: return TestConnectionResult.WRONG_URL;
michael@8: } catch (SSLException e) {
michael@8: return TestConnectionResult.SSL_ERROR;
michael@8: } catch (SocketException e) {
michael@8: return TestConnectionResult.WRONG_URL;
michael@8: } catch (AuthenticationException e) {
michael@8: return TestConnectionResult.WRONG_CREDENTIAL;
michael@8: } catch (ClientProtocolException e) {
michael@8: return TestConnectionResult.WRONG_SERVER_STATUS;
michael@8: } catch (CaldavProtocolException e) {
michael@8: return TestConnectionResult.WRONG_ANSWER;
michael@8: }
michael@8: return TestConnectionResult.SUCCESS;
michael@8: }
michael@0:
michael@8: /**
michael@8: * @param context May be null if no notification is needed
michael@8: * @param uri
michael@8: * @return
michael@8: * @throws AuthenticationException
michael@8: * @throws FileNotFoundException
michael@8: */
michael@8: private List forceGetCalendarsFromUri(Context context, URI uri) throws AuthenticationException, FileNotFoundException {
michael@8: List calendars = new ArrayList();
michael@8: Exception exception = null;
michael@8: try {
michael@8: calendars = getCalendarsFromSet(uri);
michael@8: } catch (ClientProtocolException e) {
michael@8: if (context != null) {
michael@8: NotificationsHelper.signalSyncErrors(context, "Caldav sync problem", e.getMessage());
michael@8: //NotificationsHelper.getCurrentSyncLog().addException(e);
michael@8: }
michael@8: exception = e;
michael@8: } catch (FileNotFoundException e) {
michael@8: if (context != null) {
michael@8: NotificationsHelper.signalSyncErrors(context, "Caldav sync problem", e.getMessage());
michael@8: //NotificationsHelper.getCurrentSyncLog().addException(e);
michael@8: }
michael@8: throw e;
michael@8: } catch (IOException e) {
michael@8: if (context != null) {
michael@8: NotificationsHelper.signalSyncErrors(context, "Caldav sync problem", e.getMessage());
michael@8: //NotificationsHelper.getCurrentSyncLog().addException(e);
michael@8: }
michael@8: exception = e;
michael@8: } catch (CaldavProtocolException e) {
michael@0:
michael@8: if (context != null) {
michael@8: NotificationsHelper.signalSyncErrors(context, "Caldav sync problem", e.getMessage());
michael@8: //NotificationsHelper.getCurrentSyncLog().addException(e);
michael@8: }
michael@8: exception = e;
michael@8: }
michael@8: if (exception != null && BuildConfig.DEBUG) {
michael@8: Log.e(TAG, "Force get calendars from '" + uri.toString()
michael@8: + "' failed " + exception.getClass().getCanonicalName()
michael@8: + ": " + exception.getMessage());
michael@8: }
michael@8: return calendars;
michael@8: }
michael@8:
michael@8: private final static String PROPFIND_USER_PRINCIPAL = XML_VERSION +
michael@8: "" +
michael@8: "" +
michael@8: "" +
michael@8: "" +
michael@8: "" +
michael@8: "";
michael@8:
michael@8: private URI getUserPrincipal() throws
michael@8: AuthenticationException,
michael@8: IOException, CaldavProtocolException,
michael@8: URISyntaxException {
michael@8: URI uri = this.url.toURI();
michael@8: HttpPropFind request = createPropFindRequest(uri,
michael@8: PROPFIND_USER_PRINCIPAL, 0);
michael@8: HttpResponse response = httpClient.execute(targetHost, request, mContext);
michael@8: checkStatus(response);
michael@8: ServerInfoHandler serverInfoHandler = new ServerInfoHandler();
michael@8: parseXML(response, serverInfoHandler);
michael@8: String userPrincipal = null;
michael@8: if (serverInfoHandler.currentUserPrincipal != null) {
michael@8: userPrincipal = serverInfoHandler.currentUserPrincipal;
michael@8: } else if (serverInfoHandler.principalUrl != null) {
michael@8: userPrincipal = serverInfoHandler.principalUrl;
michael@8: } else {
michael@8: throw new CaldavProtocolException("no principal url found");
michael@8: }
michael@8: try {
michael@8: URI userPrincipalUri = new URI(userPrincipal);
michael@8: userPrincipalUri = uri.resolve(userPrincipalUri);
michael@8: if (BuildConfig.DEBUG) {
michael@8: Log.d(TAG,
michael@8: "Found userPrincipal: " + userPrincipalUri.toString());
michael@8: }
michael@8: return userPrincipalUri;
michael@8: } catch (URISyntaxException e) {
michael@8: throw new CaldavProtocolException("principal url '" + userPrincipal
michael@8: + "' malformed");
michael@8: }
michael@8: }
michael@8:
michael@8: private final static String PROPFIND_CALENDAR_HOME_SET = XML_VERSION
michael@8: + "";
michael@8:
michael@8: private List getCalendarHomes(URI userPrincipal)
michael@8: throws ClientProtocolException, IOException,
michael@8: AuthenticationException, FileNotFoundException,
michael@8: CaldavProtocolException {
michael@8: HttpPropFind request = createPropFindRequest(userPrincipal,
michael@8: PROPFIND_CALENDAR_HOME_SET, 0);
michael@8: HttpResponse response = httpClient.execute(targetHost, request, mContext);
michael@8: checkStatus(response);
michael@8: CalendarHomeHandler calendarHomeHandler = new CalendarHomeHandler(
michael@8: userPrincipal);
michael@8: parseXML(response, calendarHomeHandler);
michael@8: List result = calendarHomeHandler.calendarHomeSet;
michael@8: if (BuildConfig.DEBUG) {
michael@8: Log.d(TAG, result.size() + " calendar-home-set found in "
michael@8: + userPrincipal.toString());
michael@8: }
michael@8: return result;
michael@8: }
michael@8:
michael@8: private final static String PROPFIND_CALENDER_LIST = XML_VERSION
michael@8: + ""
michael@8: + ""
michael@8: // +
michael@8: // ""
michael@8: // +
michael@8: // "
michael@8: + ""
michael@8: //"
michael@8: + "";
michael@8:
michael@8:
michael@8: private List getCalendarsFromSet(URI calendarSet)
michael@8: throws ClientProtocolException, IOException,
michael@8: CaldavProtocolException, AuthenticationException,
michael@8: FileNotFoundException {
michael@8: HttpPropFind request = createPropFindRequest(calendarSet, PROPFIND_CALENDER_LIST, 1);
michael@8: HttpResponse response = httpClient.execute(targetHost, request, mContext);
michael@8: checkStatus(response);
michael@8: CalendarsHandler calendarsHandler = new CalendarsHandler(calendarSet);
michael@8: parseXML(response, calendarsHandler);
michael@8: List result = calendarsHandler.calendars;
michael@8: if (BuildConfig.DEBUG) {
michael@8: Log.i(TAG,
michael@8: result.size() + " calendars found in set "
michael@8: + calendarSet.toString()
michael@8: );
michael@8: }
michael@8: return result;
michael@8: }
michael@8:
michael@8: /**
michael@8: * Discover CalDAV Calendars Mentioned in
michael@8: * http://tools.ietf.org/html/draft-daboo-srv-caldav-10#section-6 and
michael@8: * http://code.google.com/p/sabredav/wiki/BuildingACalDAVClient#Discovery
michael@8: *
michael@8: * - PROPFIND calendar-home-set on url
michael@8: *
- PROPFIND DAV:current-user-principal or principal-URL on url
michael@8: *
- PROPFIND calendar-home-set on current-user-principal or principal-URL
michael@8: *
- PROPFIND displayname, resourcetype, getctag on CalendarHomeSets
michael@8: *
michael@8: *
michael@8: * @param context
michael@8: * @return List of {@link DavCalendar}
michael@8: * @throws ClientProtocolException http protocol error
michael@8: * @throws IOException Connection lost
michael@8: * @throws URISyntaxException url in Constructor malformed
michael@8: * @throws CaldavProtocolException caldav protocol error
michael@8: */
michael@8: //public Iterable getCalendarList(Context context) throws ClientProtocolException,
michael@8: public CalendarList getCalendarList(Context context) throws ClientProtocolException,
michael@8: IOException, URISyntaxException, ParserConfigurationException,
michael@8: CaldavProtocolException {
michael@8: try {
michael@8: CalendarList Result = new CalendarList(this.mAccount, this.mProvider, CalendarSource.CalDAV, this.url
michael@8: .toString());
michael@8: List calendars = new ArrayList();
michael@8:
michael@8: calendars = forceGetCalendarsFromUri(context, this.url.toURI());
michael@8:
michael@8: if (calendars.size() == 0) {
michael@8: // no calendars found, try the home-set
michael@8: URI userPrincipal = getUserPrincipal();
michael@8: List calendarSets = getCalendarHomes(userPrincipal);
michael@8: for (URI calendarSet : calendarSets) {
michael@8: List calendarSetCalendars = getCalendarsFromSet(calendarSet);
michael@8: calendars.addAll(calendarSetCalendars);
michael@8: }
michael@8: }
michael@8: for (DavCalendar cal : calendars) {
michael@8: Result.addCalendar(cal);
michael@8: }
michael@8:
michael@8: //return calendars;
michael@8: return Result;
michael@8: } catch (AuthenticationException e) {
michael@8: throw new IOException(e);
michael@8: }
michael@8: }
michael@8:
michael@8: //public Iterable getCalendarEvents(DavCalendar calendar)
michael@8: public ArrayList getCalendarEvents(DavCalendar calendar)
michael@8: throws URISyntaxException, ClientProtocolException, IOException,
michael@8: ParserConfigurationException, SAXException {
michael@8:
michael@8: ArrayList calendarEventList = new ArrayList();
michael@8:
michael@8: String requestBody = ""
michael@8: + "" + "" + ""
michael@8: + "" + "";
michael@8:
michael@8: HttpPropFind request = null;
michael@8:
michael@8: String EventUri;
michael@0:
michael@0: /*request = new HttpPropFind();
michael@8: request.setURI(calendar.getURI());
michael@0: request.setHeader("Host", targetHost.getHostName());
michael@0: request.setHeader("Depth", "1");
michael@0: request.setHeader("Content-Type", "application/xml;charset=\"UTF-8\"");
michael@0:
michael@0: try {
michael@0: request.setEntity(new StringEntity(requestBody, "UTF-8"));
michael@0: } catch (UnsupportedEncodingException e) {
michael@0: throw new AssertionError("UTF-8 is unknown");
michael@0: }*/
michael@8: request = this.createPropFindRequest(calendar.getURI(), requestBody, 1);
michael@0:
michael@8: Log.d(TAG, "Getting eTag by PROPFIND at " + request.getURI());
michael@0:
michael@8: HttpResponse response = httpClient.execute(targetHost, request, mContext);
michael@8: String body = EntityUtils.toString(response.getEntity(), "UTF-8");
michael@0:
michael@8: Log.d(TAG, "HttpResponse status=" + response.getStatusLine()
michael@8: + " body= " + body);
michael@0:
michael@8: DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
michael@8: factory.setNamespaceAware(true);
michael@8: DocumentBuilder builder = factory.newDocumentBuilder();
michael@8: Document dom = builder.parse(new InputSource(new ByteArrayInputStream(
michael@8: body.getBytes("utf-8"))));
michael@8: Element root = dom.getDocumentElement();
michael@8: NodeList items = root.getElementsByTagNameNS("*", "getetag");
michael@0:
michael@8: for (int i = 0; i < items.getLength(); i++) {
michael@8: CalendarEvent calendarEvent = new CalendarEvent(this.mAccount, this.mProvider);
michael@0:
michael@8: Node node = items.item(i);
michael@0:
michael@8: if (node.getTextContent().trim().length() == 0)
michael@8: continue; // not an event
michael@0:
michael@8: calendarEvent.setETag(node.getTextContent().trim());
michael@8: //calendarEvent.calendarURL = this.url;
michael@8: calendarEvent.calendarURL = calendar.getURI().toURL();
michael@0:
michael@8: node = node.getParentNode(); // prop
michael@8: node = node.getParentNode(); // propstat
michael@8: node = node.getParentNode(); // response
michael@0:
michael@8: NodeList children = node.getChildNodes();
michael@8: for (int j = 0; j < children.getLength(); j++) {
michael@8: Node childNode = children.item(j);
michael@8: if ((childNode.getLocalName() != null) && (childNode.getLocalName()
michael@8: .equalsIgnoreCase("href"))) {
michael@8: EventUri = childNode.getTextContent().trim();
michael@8: //HINT: bugfix for zimbra calendar: replace("@", "%40")
michael@8: EventUri = EventUri.replace("@", "%40");
michael@8: calendarEvent.setUri(new URI(EventUri));
michael@8: }
michael@8: }
michael@0:
michael@8: calendarEventList.add(calendarEvent);
michael@0:
michael@8: }
michael@0:
michael@8: return calendarEventList;
michael@8: }
michael@0:
michael@8: private void parseXML(HttpResponse response, ContentHandler contentHandler)
michael@8: throws IOException, CaldavProtocolException {
michael@8: InputStream is = response.getEntity().getContent();
michael@8: /*BufferedReader bReader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
michael@8: String Content = "";
michael@0: String Line = bReader.readLine();
michael@0:
michael@0: while (Line != null) {
michael@0: Content += Line;
michael@0: Line = bReader.readLine();
michael@0: }*/
michael@0:
michael@8: SAXParserFactory factory = SAXParserFactory.newInstance();
michael@8: try {
michael@8: SAXParser parser = factory.newSAXParser();
michael@8: XMLReader reader = parser.getXMLReader();
michael@8: reader.setContentHandler(contentHandler);
michael@8: reader.parse(new InputSource(is));
michael@8: } catch (ParserConfigurationException e) {
michael@8: throw new AssertionError("ParserConfigurationException "
michael@8: + e.getMessage());
michael@8: } catch (IllegalStateException e) {
michael@8: throw new CaldavProtocolException(e.getMessage());
michael@8: } catch (SAXException e) {
michael@8: throw new CaldavProtocolException(e.getMessage());
michael@8: }
michael@8: }
michael@0:
michael@8: private void checkStatus(HttpResponse response)
michael@8: throws AuthenticationException, FileNotFoundException,
michael@8: ClientProtocolException {
michael@8: final int statusCode = response.getStatusLine().getStatusCode();
michael@8: lastStatusCode = statusCode;
michael@8: if (response.containsHeader("ETag"))
michael@8: lastETag = response.getFirstHeader("ETag").getValue();
michael@8: else
michael@8: lastETag = "";
michael@8: if (response.containsHeader("DAV"))
michael@8: lastDav = response.getFirstHeader("DAV").getValue();
michael@8: else
michael@8: lastDav = "";
michael@0:
michael@8: switch (statusCode) {
michael@8: case 401:
michael@8: throw new AuthenticationException();
michael@8: case 404:
michael@8: throw new FileNotFoundException();
michael@8: case 409: //Conflict
michael@8: case 412:
michael@8: case 200:
michael@8: case 201:
michael@8: case 204:
michael@8: case 207:
michael@8: return;
michael@8: default:
michael@8: throw new ClientProtocolException("StatusCode: " + statusCode);
michael@8: }
michael@8: }
michael@0:
michael@8: private HttpPropFind createPropFindRequest(URI uri, String data, int depth) {
michael@8: HttpPropFind request = new HttpPropFind();
michael@0:
michael@8: request.setURI(uri);
michael@8: //request.setHeader("Host", targetHost.getHostName());
michael@8: request.setHeader("Host", targetHost.getHostName() + ":" + String.valueOf(targetHost.getPort()));
michael@8: request.setHeader("Depth", Integer.toString(depth));
michael@8: request.setHeader("Content-Type", "application/xml;charset=\"UTF-8\"");
michael@8: try {
michael@8: request.setEntity(new StringEntity(data, "UTF-8"));
michael@8: } catch (UnsupportedEncodingException e) {
michael@8: throw new AssertionError("UTF-8 is unknown");
michael@8: }
michael@8: return request;
michael@8: }
michael@0:
michael@8: private HttpDelete createDeleteRequest(URI uri) {
michael@8: HttpDelete request = new HttpDelete();
michael@8: request.setURI(uri);
michael@8: //request.setHeader("Host", targetHost.getHostName());
michael@8: request.setHeader("Host", targetHost.getHostName() + ":" + String.valueOf(targetHost.getPort()));
michael@8: request.setHeader("Content-Type", "application/xml;charset=\"UTF-8\"");
michael@8: return request;
michael@8: }
michael@0:
michael@8: private HttpPut createPutRequest(URI uri, String data, int depth) {
michael@8: HttpPut request = new HttpPut();
michael@8: request.setURI(uri);
michael@8: //request.setHeader("Host", targetHost.getHostName());
michael@8: request.setHeader("Host", targetHost.getHostName() + ":" + String.valueOf(targetHost.getPort()));
michael@8: //request.setHeader("Content-Type", "application/xml;charset=\"UTF-8\"");
michael@8: request.setHeader("Content-Type", "text/calendar; charset=UTF-8");
michael@8: try {
michael@8: request.setEntity(new StringEntity(data, "UTF-8"));
michael@8: //request.setEntity(new StringEntity(data));
michael@8: } catch (UnsupportedEncodingException e) {
michael@8: throw new AssertionError("UTF-8 is unknown");
michael@8: }
michael@8: return request;
michael@8: }
michael@0:
michael@8: private static HttpReport createReportRequest(URI uri, String data, int depth) {
michael@8: HttpReport request = new HttpReport();
michael@8: request.setURI(uri);
michael@8: //request.setHeader("Host", targetHost.getHostName());
michael@8: request.setHeader("Host", targetHost.getHostName() + ":" + String.valueOf(targetHost.getPort()));
michael@8: request.setHeader("Depth", Integer.toString(depth));
michael@8: request.setHeader("Content-Type", "application/xml;charset=\"UTF-8\"");
michael@8: //request.setHeader("Content-Type", "text/xml;charset=\"UTF-8\"");
michael@8: try {
michael@8: request.setEntity(new StringEntity(data));
michael@8: } catch (UnsupportedEncodingException e) {
michael@8: throw new AssertionError("UTF-8 is unknown");
michael@8: }
michael@8: return request;
michael@8: }
michael@0:
michael@8: public static void fetchEvent_old(CalendarEvent calendarEvent)
michael@8: throws ClientProtocolException, IOException {
michael@8: HttpGet request = null;
michael@0:
michael@8: request = new HttpGet();
michael@8: request.setURI(calendarEvent.getUri());
michael@8: request.setHeader("Host", targetHost.getHostName());
michael@8: request.setHeader("Content-Type", "application/xml;charset=\"UTF-8\"");
michael@0:
michael@8: HttpResponse response = httpClient.execute(targetHost, request);
michael@8: String body = EntityUtils.toString(response.getEntity(), "UTF-8");
michael@0:
michael@8: calendarEvent.setICSasString(body);
michael@0:
michael@8: Log.d(TAG, "HttpResponse GET event status=" + response.getStatusLine()
michael@8: + " body= " + body);
michael@8: }
michael@0:
michael@8: public static boolean getEvent(CalendarEvent calendarEvent) throws ClientProtocolException, IOException {
michael@8: boolean Result = false;
michael@8: HttpReport request = null;
michael@0:
michael@8: //HINT: bugfix for google calendar: replace("@", "%40")
michael@8: String data = XML_VERSION +
michael@8: "" +
michael@8: "" +
michael@8: "" +
michael@8: "" +
michael@8: "" +
michael@8: "" + calendarEvent.getUri().getRawPath().replace("@", "%40") + "" +
michael@8: "";
michael@0:
michael@8: URI calendarURI = null;
michael@8: try {
michael@8: calendarURI = calendarEvent.calendarURL.toURI();
michael@8: } catch (URISyntaxException e) {
michael@8: e.printStackTrace();
michael@8: }
michael@8: //request = createReportRequest(calendarEvent.getUri(), data, 1);
michael@8: request = createReportRequest(calendarURI, data, 1);
michael@0:
michael@8: HttpResponse response = httpClient.execute(targetHost, request);
michael@8: String body = EntityUtils.toString(response.getEntity(), "UTF-8");
michael@8:
michael@8: if (calendarEvent.setICSasMultiStatus(body))
michael@8: Result = true;
michael@8:
michael@8: return Result;
michael@8: }
michael@8:
michael@8:
michael@8: /**
michael@8: * sends a update event request to the server
michael@8: *
michael@8: * @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
michael@8: * @param data the full ical-data for the event
michael@8: * @param ETag the ETAG of this event is send within the "If-Match" Parameter to tell the server only to update this version
michael@8: * @return
michael@8: */
michael@8: public boolean updateEvent(URI uri, String data, String ETag) {
michael@8: boolean Result = false;
michael@8:
michael@8: try {
michael@8: HttpPut request = createPutRequest(uri, data, 1);
michael@8: request.addHeader(mstrcHeaderIfMatch, ETag);
michael@8: HttpResponse response = httpClient.execute(targetHost, request, mContext);
michael@8: checkStatus(response);
michael@8: if ((lastStatusCode == 200) || (lastStatusCode == 201) || (lastStatusCode == 204)) {
michael@8: Result = true;
michael@8: } else if (lastStatusCode == 412) {
michael@8: //Precondition failed
michael@8: Result = false;
michael@8: } else if (lastStatusCode == 409) {
michael@8: //Conflict
michael@8: Result = false;
michael@8: } else {
michael@8: Log.w(TAG, "Unkown StatusCode during creation of an event");
michael@8: }
michael@8: } catch (ClientProtocolException e) {
michael@8: e.printStackTrace();
michael@8: } catch (IOException e) {
michael@8: e.printStackTrace();
michael@8: } catch (AuthenticationException e) {
michael@8: e.printStackTrace();
michael@8: }
michael@8: return Result;
michael@8: }
michael@8:
michael@8: /**
michael@8: * sends a create event request to server
michael@8: *
michael@8: * @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
michael@8: * @param data the full ical-data for the new event
michael@8: * @return success of this function
michael@8: */
michael@8: public boolean createEvent(URI uri, String data) {
michael@8: boolean Result = false;
michael@8:
michael@8: try {
michael@8: HttpPut request = createPutRequest(uri, data, 1);
michael@8: request.addHeader(mstrcHeaderIfNoneMatch, "*");
michael@8: HttpResponse response = httpClient.execute(targetHost, request, mContext);
michael@8: checkStatus(response);
michael@8: if (lastStatusCode == 201) {
michael@8: Result = true;
michael@8: } else {
michael@8: Log.w(TAG, "Unkown StatusCode during creation of an event");
michael@8: }
michael@8: } catch (ClientProtocolException e) {
michael@8: e.printStackTrace();
michael@8: } catch (IOException e) {
michael@8: e.printStackTrace();
michael@8: } catch (AuthenticationException e) {
michael@8: e.printStackTrace();
michael@8: }
michael@8: return Result;
michael@8: }
michael@8:
michael@8: /**
michael@8: * sends a delete event request to the server
michael@8: *
michael@8: * @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
michael@8: * @param ETag the ETAG of this event is send within the "If-Match" Parameter to tell the server only to delete this version
michael@8: * @return success of this function
michael@8: */
michael@8: public boolean deleteEvent(URI calendarEventUri, String ETag) {
michael@8: boolean Result = false;
michael@8:
michael@8: try {
michael@8: HttpDelete request = createDeleteRequest(calendarEventUri);
michael@8: request.addHeader(mstrcHeaderIfMatch, ETag);
michael@8: HttpResponse response = httpClient.execute(targetHost, request, mContext);
michael@8: checkStatus(response);
michael@8: if ((lastStatusCode == 204) || (lastStatusCode == 200)) {
michael@8: Result = true;
michael@8: } else {
michael@8: Log.w(TAG, "Unkown StatusCode during deletion of an event");
michael@8: }
michael@8: } catch (ClientProtocolException e) {
michael@8: e.printStackTrace();
michael@8: } catch (IOException e) {
michael@8: if (lastStatusCode == 404) {
michael@8: //the event has already been deleted on server side. no action needed
michael@8: Result = true;
michael@8: } else {
michael@8: e.printStackTrace();
michael@8: }
michael@8: } catch (AuthenticationException e) {
michael@8: e.printStackTrace();
michael@8: }
michael@8:
michael@8: return Result;
michael@8: }
michael@8:
michael@8: /**
michael@8: * returns the ETAG send by the last server response.
michael@8: *
michael@8: * @return the ETAG
michael@8: */
michael@8: public String getLastETag() {
michael@8: return lastETag;
michael@8: }
michael@8:
michael@8: /**
michael@8: * returns the DAV-Options send by the last server response.
michael@8: *
michael@8: * @return the DAV-Options
michael@8: */
michael@8: public String getLastDav() {
michael@8: return lastDav;
michael@8: }
michael@8:
michael@8: public void setVersion(String version) {
michael@8: VERSION = version;
michael@8: ((AbstractHttpClient) httpClient).getParams()
michael@8: .setParameter(CoreProtocolPNames.USER_AGENT, this.USER_AGENT + " Version:" + VERSION);
michael@8: }
michael@8:
michael@8: public void setAccount(Account account) {
michael@8: this.mAccount = account;
michael@8: }
michael@8:
michael@8: public void setProvider(ContentProviderClient provider) {
michael@8: this.mProvider = provider;
michael@8: }
michael@8: }