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