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