Skip to content

Commit 76d46a8

Browse files
committed
Implement WebDAV detection via OPTIONS request with redirect support
1 parent acbfbac commit 76d46a8

File tree

2 files changed

+80
-0
lines changed

2 files changed

+80
-0
lines changed

src/main/kotlin/at/bitfire/dav4jvm/DavResource.kt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ open class DavResource @JvmOverloads constructor(
6868
companion object {
6969
const val MAX_REDIRECTS = 5
7070

71+
const val WEBDAV_VERSIONS = "1, 2, 3" // valid WebDAV versions
72+
7173
const val HTTP_MULTISTATUS = 207
7274
val MIME_XML = "application/xml; charset=utf-8".toMediaType()
7375

@@ -175,6 +177,39 @@ open class DavResource @JvmOverloads constructor(
175177
}
176178
}
177179

180+
/**
181+
* Finds WebDAV capable location by sending OPTIONS request to this resource without HTTP
182+
* compression (because some servers have broken compression for OPTIONS). Follows up
183+
* to [MAX_REDIRECTS] redirects.
184+
*
185+
* @return the location of the WebDAV resource if it supports WebDAV, `null` otherwise
186+
*
187+
* @throws IOException on I/O error
188+
* @throws HttpException on HTTP error
189+
* @throws DavException on HTTPS -> HTTP redirect
190+
*/
191+
@Throws(IOException::class, HttpException::class, DavException::class)
192+
fun detectWebDav(): HttpUrl? = followRedirects {
193+
httpClient.newCall(
194+
Request.Builder()
195+
.method("OPTIONS", null)
196+
.header("Content-Length", "0")
197+
.url(location)
198+
.header("Accept-Encoding", "identity") // disable compression
199+
.build()
200+
).execute()
201+
}.use { response ->
202+
checkStatus(response)
203+
204+
// check whether WebDAV is supported
205+
val davCapabilities = HttpUtils.listHeader(response, "DAV").map { it.trim() }.toSet()
206+
val supportsWebDav = davCapabilities.any { it in WEBDAV_VERSIONS }
207+
return if (supportsWebDav)
208+
location
209+
else
210+
null
211+
}
212+
178213
/**
179214
* Sends a MOVE request to this resource. Follows up to [MAX_REDIRECTS] redirects.
180215
* Updates [location] on success.

src/test/kotlin/at/bitfire/dav4jvm/DavResourceTest.kt

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,51 @@ class DavResourceTest {
484484
assertTrue(called)
485485
}
486486

487+
@Test
488+
fun testDetectWebDav() {
489+
val url = sampleUrl()
490+
val dav = DavResource(httpClient, url)
491+
492+
// Without WebDAV
493+
mockServer.enqueue(MockResponse()
494+
.setResponseCode(HttpURLConnection.HTTP_OK)
495+
.setHeader("DAV", " 0 ,, 4,5,6, hyperactive-access")
496+
)
497+
assertNull(dav.detectWebDav())
498+
499+
// With WebDAV
500+
mockServer.enqueue(MockResponse()
501+
.setResponseCode(HttpURLConnection.HTTP_OK)
502+
.setHeader("DAV", " 1 ") // Class 1
503+
)
504+
assertEquals(url.encodedPath, dav.detectWebDav()?.encodedPath)
505+
mockServer.enqueue(MockResponse()
506+
.setResponseCode(HttpURLConnection.HTTP_OK)
507+
.setHeader("DAV", "1, 2") // Class 2
508+
)
509+
assertEquals(url.encodedPath, dav.detectWebDav()?.encodedPath)
510+
mockServer.enqueue(MockResponse()
511+
.setResponseCode(HttpURLConnection.HTTP_OK)
512+
.setHeader("DAV", " 1, 3 ") // Class 3
513+
)
514+
assertEquals(url.encodedPath, dav.detectWebDav()?.encodedPath)
515+
516+
// Follows redirects
517+
mockServer.enqueue(MockResponse()
518+
.setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
519+
.setHeader("Location", "/moved")
520+
)
521+
mockServer.enqueue(MockResponse()
522+
.setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
523+
.setHeader("Location", "/redirected")
524+
)
525+
mockServer.enqueue(MockResponse()
526+
.setResponseCode(HttpURLConnection.HTTP_OK)
527+
.setHeader("DAV", "2")
528+
)
529+
assertEquals("/redirected", dav.detectWebDav()?.encodedPath)
530+
}
531+
487532
@Test
488533
fun testPropfindAndMultiStatus() {
489534
val url = sampleUrl()

0 commit comments

Comments
 (0)