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

Tue, 10 Feb 2015 22:40:00 +0100

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

Merge https://github.com/gggard/AndroidCaldavSyncAdapater/pull/206/

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

mercurial