1 package com
.morphoss
.acal
.activity
.serverconfig
;
3 import java
.net
.URLEncoder
;
4 import java
.util
.ArrayList
;
5 import java
.util
.Iterator
;
8 import org
.apache
.http
.Header
;
9 import org
.apache
.http
.message
.BasicHeader
;
11 import android
.util
.Log
;
13 import com
.morphoss
.acal
.Constants
;
14 import com
.morphoss
.acal
.providers
.Servers
;
15 import com
.morphoss
.acal
.service
.connector
.AcalRequestor
;
16 import com
.morphoss
.acal
.service
.connector
.SendRequestFailedException
;
17 import com
.morphoss
.acal
.xml
.DavNode
;
19 public class TestPort
{
20 private static final String TAG
= "aCal TestPort";
21 private static final String pPathRequestData
= "<?xml version=\"1.0\" encoding=\"utf-8\"?>"+
22 "<propfind xmlns=\"DAV:\">"+
25 "<current-user-principal/>"+
26 "<principal-collection-set/>"+
30 private static final Header
[] pPathHeaders
= new Header
[] {
31 new BasicHeader("Depth","0"),
32 new BasicHeader("Content-Type","text/xml; charset=UTF-8")
35 private final AcalRequestor requestor
;
38 private String hostName
;
42 private Boolean isOpen
;
43 private Boolean authOK
;
44 private Boolean hasDAV
;
45 private Boolean hasCalDAV
;
48 * Construct based on values from the AcalRequestor
51 public TestPort(AcalRequestor requestorIn
) {
52 this.requestor
= requestorIn
;
53 this.path
= requestor
.getPath();
54 this.hostName
= requestor
.getHostName();
55 this.port
= requestor
.getPort();
56 this.useSSL
= requestor
.getProtocol().equals("https");
57 connectTimeOut
= 200 + (useSSL ?
300 : 0);
67 * Construct based on values from the AcalRequestor, but overriding port/SSL
72 TestPort(AcalRequestor requestorIn
, int port
, boolean useSSL
) {
81 * Test whether the port is open.
86 if ( this.isOpen
== null ) {
87 requestor
.setTimeOuts(connectTimeOut
,socketTimeOut
);
88 requestor
.setPath(path
);
89 requestor
.setHostName(hostName
);
90 requestor
.setPortProtocol( port
, (useSSL?
1:0) );
91 if ( Constants
.debugCheckServerDialog
) Log
.println(Constants
.LOGD
,TAG
, "Checking port open "+requestor
.protocolHostPort());
94 requestor
.doRequest("HEAD", null, null, null);
95 if ( Constants
.debugCheckServerDialog
) Log
.println(Constants
.LOGD
,TAG
, "Probe "+requestor
.fullUrl()+" success: status " + requestor
.getStatusCode());
97 // No exception, so it worked!
99 if ( requestor
.getStatusCode() == 401 ) this.authOK
= false;
100 checkCalendarAccess(requestor
.getResponseHeaders());
102 this.socketTimeOut
= 15000;
103 this.connectTimeOut
= 15000;
104 requestor
.setTimeOuts(connectTimeOut
,socketTimeOut
);
106 catch (Exception e
) {
107 if ( Constants
.debugCheckServerDialog
)
108 Log
.println(Constants
.LOGD
, TAG
, "Probe "+requestor
.fullUrl()+" failed: " + e
.getMessage());
111 if ( Constants
.debugCheckServerDialog
) Log
.println(Constants
.LOGD
,TAG
, "Port "+(isOpen ?
"":"not")+" open on "+requestor
.protocolHostPort() );
117 * Increases the connection timeout and attempts another probe.
121 connectTimeOut
+= 1000;
130 * Checks whether the calendar supports CalDAV by looking through the headers for a "DAV:" header which
131 * includes "calendar-access". Appends to the successMessage we will return to the user, as well as
132 * setting the hasCalendarAccess for later update to the DB.
136 * @return true if the calendar does support CalDAV.
138 private boolean checkCalendarAccess(Header
[] headers
) {
139 if ( headers
!= null ) {
140 for (Header h
: headers
) {
141 if (h
.getName().equalsIgnoreCase("DAV")) {
142 if (h
.getValue().toLowerCase().contains("calendar-access")) {
143 if ( Constants
.debugCheckServerDialog
) Log
.println(Constants
.LOGI
,TAG
,
144 "Discovered server supports CalDAV on URL "+requestor
.fullUrl());
146 hasDAV
= true; // by implication
157 * Does a PROPFIND request on the given path.
161 private boolean doPropfindPrincipal( String requestPath
) {
162 if ( requestPath
!= null ) requestor
.setPath(requestPath
);
163 if ( Constants
.debugCheckServerDialog
) Log
.println(Constants
.LOGD
,TAG
,
164 "Doing PROPFIND for current-user-principal on " + requestor
.fullUrl() );
166 DavNode root
= requestor
.doXmlRequest("PROPFIND", null, pPathHeaders
, pPathRequestData
);
168 int status
= requestor
.getStatusCode();
169 if ( Constants
.debugCheckServerDialog
)
170 Log
.println(Constants
.LOGD
,TAG
, "PROPFIND request " + status
+ " on " + requestor
.fullUrl() );
172 checkCalendarAccess(requestor
.getResponseHeaders());
174 if ( status
== 207 ) {
175 if ( Constants
.debugCheckServerDialog
) Log
.println(Constants
.LOGD
,TAG
, "Checking for principal path in response...");
176 List
<DavNode
> unAuthenticated
= root
.getNodesFromPath("multistatus/response/propstat/prop/current-user-principal/unauthenticated");
177 if ( ! unAuthenticated
.isEmpty() ) {
178 if ( Constants
.debugCheckServerDialog
) Log
.println(Constants
.LOGD
,TAG
, "Found unauthenticated principal");
179 requestor
.setAuthRequired();
180 if ( Constants
.debugCheckServerDialog
) Log
.println(Constants
.LOGD
,TAG
, "We are unauthenticated, so try forcing authentication on");
181 if ( requestor
.getAuthType() == Servers
.AUTH_NONE
) {
182 requestor
.setAuthType(Servers
.AUTH_BASIC
);
183 if ( Constants
.debugCheckServerDialog
) Log
.println(Constants
.LOGD
,TAG
, "Guessing Basic Authentication");
185 else if ( requestor
.getAuthType() == Servers
.AUTH_BASIC
) {
186 requestor
.setAuthType(Servers
.AUTH_DIGEST
);
187 if ( Constants
.debugCheckServerDialog
) Log
.println(Constants
.LOGD
,TAG
, "Guessing Digest Authentication");
189 return doPropfindPrincipal(requestPath
);
192 String principalCollectionHref
= null;
193 for ( DavNode response
: root
.getNodesFromPath("multistatus/response") ) {
194 String responseHref
= response
.getFirstNodeText("href");
195 if ( Constants
.debugCheckServerDialog
) Log
.println(Constants
.LOGD
, TAG
, "Checking response for "+responseHref
);
196 for ( DavNode propStat
: response
.getNodesFromPath("propstat") ) {
197 if ( Constants
.debugCheckServerDialog
) Log
.println(Constants
.LOGD
, TAG
, "Checking in propstat for "+responseHref
);
198 if ( propStat
.getFirstNodeText("status").equalsIgnoreCase("HTTP/1.1 200 OK") ) {
199 if ( Constants
.debugCheckServerDialog
) Log
.println(Constants
.LOGD
, TAG
, "Found propstat 200 OK response for "+responseHref
);
200 for ( DavNode prop
: propStat
.getNodesFromPath("prop/*") ) {
201 String thisTag
= prop
.getTagName();
202 if ( Constants
.debugCheckServerDialog
) Log
.println(Constants
.LOGD
, TAG
, "Examining tag "+thisTag
);
203 if ( thisTag
.equals("resourcetype") && ! prop
.getNodesFromPath("principal").isEmpty() ) {
204 if ( Constants
.debugCheckServerDialog
) Log
.println(Constants
.LOGD
, TAG
, "This is a principal URL :-)");
205 requestor
.interpretUriString(responseHref
);
206 setFieldsFromRequestor();
209 else if ( thisTag
.equals("current-user-principal") ) {
210 if ( Constants
.debugCheckServerDialog
) Log
.println(Constants
.LOGD
, TAG
, "Found the principal URL :-)");
211 requestor
.interpretUriString(prop
.getFirstNodeText("href"));
212 setFieldsFromRequestor();
215 else if ( thisTag
.equals("principal-collection-set") ) {
216 principalCollectionHref
= prop
.getFirstNodeText("href");
217 String userName
= URLEncoder
.encode(requestor
.getUserName(), "UTF-8");
218 if ( principalCollectionHref
.length() > 0 && userName
!= null ) {
219 principalCollectionHref
= principalCollectionHref
+
220 (principalCollectionHref
.length() > 0 && principalCollectionHref
.charAt(principalCollectionHref
.length()-1) == '/' ?
"" : "/") +
222 if ( !principalCollectionHref
.equals(requestPath
) ) {
223 return doPropfindPrincipal(principalCollectionHref
);
225 if ( Constants
.debugCheckServerDialog
) Log
.println(Constants
.LOGD
, TAG
,
226 "We've tried this URL already. Let's move on... "+requestPath
);
228 // @todo Next: Try a Depth: 1 propfind on the principalCollectionHref trying to match it to this user
235 if ( principalCollectionHref
!= null ) {
239 if ( status
< 300 ) authOK
= true;
241 catch (Exception e
) {
242 Log
.e(TAG
, "PROPFIND Error: " + e
.getMessage());
243 Log
.println(Constants
.LOGD
,TAG
, Log
.getStackTraceString(e
));
249 private void setFieldsFromRequestor() {
250 useSSL
= requestor
.getProtocol().equals("https");
251 hostName
= requestor
.getHostName();
252 path
= requestor
.getPath();
253 port
= requestor
.getPort();
258 * Probes for whether the server has DAV support. It seems odd to use the PROPFIND
259 * for this, rather than OPTIONS which was intended for the purpose, but every working
260 * DAV server will support PROPFIND on every URL which supports DAV, whereas OPTIONS
261 * may only be available on some specific URLs in weird cases.
264 if ( Constants
.debugCheckServerDialog
) Log
.println(Constants
.LOGD
,TAG
, "Starting DAV discovery on "+requestor
.fullUrl());
265 if ( !isOpen() ) return false;
266 if ( hasDAV
== null ) {
268 if ( doPropfindPrincipal(this.path
) ) hasDAV
= true;
269 else if ( !hasDAV
&& doPropfindPrincipal("/.well-known/caldav") ) hasDAV
= true;
270 else if ( !hasDAV
&& doPropfindPrincipal("/") ) hasDAV
= true;
272 if ( Constants
.debugCheckServerDialog
) Log
.println(Constants
.LOGD
,TAG
, "DAV "+(hasDAV?
"":"not")+" found on "+requestor
.fullUrl());
278 * Probes for CalDAV support on the server using previous path used for DAV.
280 boolean hasCalDAV() {
281 requestor
.setTimeOuts(connectTimeOut
,socketTimeOut
);
282 requestor
.setPath(path
);
283 requestor
.setHostName(hostName
);
284 requestor
.setPortProtocol( port
, (useSSL?
1:0) );
286 if ( Constants
.debugCheckServerDialog
) Log
.println(Constants
.LOGI
,TAG
,
287 "Starting CalDAV dependency discovery on "+requestor
.fullUrl());
288 if ( !isOpen() || !hasDAV() || !authOK() ) return false;
290 if ( Constants
.debugCheckServerDialog
) Log
.println(Constants
.LOGI
,TAG
, "All CalDAV dependencies are present.");
291 if ( hasCalDAV
== null ) {
292 if ( Constants
.debugCheckServerDialog
) Log
.println(Constants
.LOGI
,TAG
, "Still discovering actual CalDAV support.");
295 path
= requestor
.getPath();
296 if ( Constants
.debugCheckServerDialog
) Log
.println(Constants
.LOGI
,TAG
, "Starting OPTIONS on "+path
);
297 requestor
.doRequest("OPTIONS", path
, null, null);
298 int status
= requestor
.getStatusCode();
299 if ( Constants
.debugCheckServerDialog
)
300 Log
.println(Constants
.LOGD
,TAG
, "OPTIONS request " + status
+ " on " + requestor
.fullUrl() );
301 checkCalendarAccess(requestor
.getResponseHeaders()); // Updates 'hasCalDAV' if it finds it
303 catch (SendRequestFailedException e
) {
304 Log
.println(Constants
.LOGD
,TAG
, "OPTIONS Error connecting to server: " + e
.getMessage());
306 catch (Exception e
) {
307 Log
.e(TAG
,"OPTIONS Error: " + e
.getMessage());
308 if ( Constants
.debugCheckServerDialog
)
309 Log
.println(Constants
.LOGD
,TAG
,Log
.getStackTraceString(e
));
312 if ( Constants
.debugCheckServerDialog
) Log
.println(Constants
.LOGI
,TAG
,
313 "CalDAV "+(hasCalDAV?
"":"not")+" found on "+requestor
.fullUrl());
319 * Return whether the auth was OK. If nothing's managed to tell us it failed
320 * then we give it the benefit of the doubt.
323 public boolean authOK() {
324 if ( Constants
.debugCheckServerDialog
) Log
.println(Constants
.LOGI
,TAG
,
325 "Checking authOK which was: "+(authOK
== null ?
"uncertain, assumed OK" : (authOK ?
"OK" : "bad")));
326 return (authOK
== null || authOK ?
true : false);
330 * Returns a default ArrayList<TestPort> which can be used for probing a server to try
331 * and discover where the CalDAV / CardDAV server is hiding.
332 * @param requestor The requestor which will be used for probing.
333 * @return The ArrayList of default ports.
335 private static ArrayList
<TestPort
> testPortSet
= null;
336 public static Iterator
<TestPort
> defaultIterator(AcalRequestor requestor
) {
337 if ( testPortSet
== null )
338 testPortSet
= new ArrayList
<TestPort
>(10);
342 testPortSet
.add( new TestPort(requestor
,443,true) );
343 testPortSet
.add( new TestPort(requestor
,8443,true) );
344 testPortSet
.add( new TestPort(requestor
,80,false) );
345 testPortSet
.add( new TestPort(requestor
,8008,false) );
346 testPortSet
.add( new TestPort(requestor
,8843,true) );
347 testPortSet
.add( new TestPort(requestor
,4443,true) );
348 testPortSet
.add( new TestPort(requestor
,8043,true) );
349 testPortSet
.add( new TestPort(requestor
,8800,false) );
350 testPortSet
.add( new TestPort(requestor
,8888,false) );
351 testPortSet
.add( new TestPort(requestor
,7777,false) );
353 return testPortSet
.iterator();
358 * Return a URL Prefix like 'https://'
361 public String
getProtocolUrlPrefix() {
362 return "http" + (useSSL?
"s":"") + "://";