Loading build.gradle.kts +1 −1 Original line number Original line Diff line number Diff line Loading @@ -2,7 +2,7 @@ import org.jetbrains.dokka.gradle.DokkaTask object Libs { object Libs { // okhttp HTTP library // okhttp HTTP library const val okhttpVersion = "4.7.2" const val okhttpVersion = "4.8.0" // XmlPullParser library // XmlPullParser library const val xpp3Version = "1.1.6" const val xpp3Version = "1.1.6" Loading src/main/kotlin/at/bitfire/dav4jvm/DavResource.kt +15 −4 Original line number Original line Diff line number Diff line Loading @@ -82,6 +82,7 @@ open class DavResource @JvmOverloads constructor( * * * @throws IOException on I/O error * @throws IOException on I/O error * @throws HttpException on HTTP error * @throws HttpException on HTTP error * @throws DavException on HTTPS -> HTTP redirect */ */ @Throws(IOException::class, HttpException::class) @Throws(IOException::class, HttpException::class) fun options(callback: (davCapabilities: Set<String>, response: Response) -> Unit) { fun options(callback: (davCapabilities: Set<String>, response: Response) -> Unit) { Loading @@ -104,7 +105,7 @@ open class DavResource @JvmOverloads constructor( * * * @throws IOException on I/O error * @throws IOException on I/O error * @throws HttpException on HTTP error * @throws HttpException on HTTP error * @throws DavException on WebDAV error * @throws DavException on WebDAV error or HTTPS -> HTTP redirect */ */ @Throws(IOException::class, HttpException::class, DavException::class) @Throws(IOException::class, HttpException::class, DavException::class) fun move(destination: HttpUrl, forceOverride: Boolean, callback: (response: Response) -> Unit) { fun move(destination: HttpUrl, forceOverride: Boolean, callback: (response: Response) -> Unit) { Loading Loading @@ -145,7 +146,7 @@ open class DavResource @JvmOverloads constructor( * * * @throws IOException on I/O error * @throws IOException on I/O error * @throws HttpException on HTTP error * @throws HttpException on HTTP error * @throws DavException on WebDAV error * @throws DavException on WebDAV error or HTTPS -> HTTP redirect */ */ @Throws(IOException::class, HttpException::class, DavException::class) @Throws(IOException::class, HttpException::class, DavException::class) fun copy(destination:HttpUrl, forceOverride:Boolean, callback: (response: Response) -> Unit) { fun copy(destination:HttpUrl, forceOverride:Boolean, callback: (response: Response) -> Unit) { Loading Loading @@ -179,6 +180,7 @@ open class DavResource @JvmOverloads constructor( * * * @throws IOException on I/O error * @throws IOException on I/O error * @throws HttpException on HTTP error * @throws HttpException on HTTP error * @throws DavException on HTTPS -> HTTP redirect */ */ @Throws(IOException::class, HttpException::class) @Throws(IOException::class, HttpException::class) fun mkCol(xmlBody: String?, callback: (response: Response) -> Unit) { fun mkCol(xmlBody: String?, callback: (response: Response) -> Unit) { Loading Loading @@ -206,6 +208,7 @@ open class DavResource @JvmOverloads constructor( * * * @throws IOException on I/O error * @throws IOException on I/O error * @throws HttpException on HTTP error * @throws HttpException on HTTP error * @throws DavException on HTTPS -> HTTP redirect */ */ @Throws(IOException::class, HttpException::class) @Throws(IOException::class, HttpException::class) fun get(accept: String, callback: (response: Response) -> Unit) { fun get(accept: String, callback: (response: Response) -> Unit) { Loading Loading @@ -235,6 +238,7 @@ open class DavResource @JvmOverloads constructor( * * * @throws IOException on I/O error * @throws IOException on I/O error * @throws HttpException on HTTP error * @throws HttpException on HTTP error * @throws DavException on HTTPS -> HTTP redirect */ */ @Throws(IOException::class, HttpException::class) @Throws(IOException::class, HttpException::class) fun put(body: RequestBody, ifETag: String? = null, ifScheduleTag: String? = null, ifNoneMatch: Boolean = false, callback: (Response) -> Unit) { fun put(body: RequestBody, ifETag: String? = null, ifScheduleTag: String? = null, ifNoneMatch: Boolean = false, callback: (Response) -> Unit) { Loading Loading @@ -273,6 +277,7 @@ open class DavResource @JvmOverloads constructor( * @throws IOException on I/O error * @throws IOException on I/O error * @throws HttpException on HTTP errors, or when 207 Multi-Status is returned * @throws HttpException on HTTP errors, or when 207 Multi-Status is returned * (because then there was probably a problem with a member resource) * (because then there was probably a problem with a member resource) * @throws DavException on HTTPS -> HTTP redirect */ */ @Throws(IOException::class, HttpException::class) @Throws(IOException::class, HttpException::class) fun delete(ifETag: String? = null, ifScheduleTag: String? = null, callback: (Response) -> Unit) { fun delete(ifETag: String? = null, ifScheduleTag: String? = null, callback: (Response) -> Unit) { Loading Loading @@ -310,7 +315,7 @@ open class DavResource @JvmOverloads constructor( * * * @throws IOException on I/O error * @throws IOException on I/O error * @throws HttpException on HTTP error * @throws HttpException on HTTP error * @throws DavException on WebDAV error (like no 207 Multi-Status response) * @throws DavException on WebDAV error (like no 207 Multi-Status response) or HTTPS -> HTTP redirect */ */ @Throws(IOException::class, HttpException::class, DavException::class) @Throws(IOException::class, HttpException::class, DavException::class) fun propfind(depth: Int, vararg reqProp: Property.Name, callback: DavResponseCallback) { fun propfind(depth: Int, vararg reqProp: Property.Name, callback: DavResponseCallback) { Loading Loading @@ -386,8 +391,10 @@ open class DavResource @JvmOverloads constructor( * @param sendRequest called to send the request (may be called multiple times) * @param sendRequest called to send the request (may be called multiple times) * * * @return response of the last request (whether it is a redirect or not) * @return response of the last request (whether it is a redirect or not) * * @throws DavException on HTTPS -> HTTP redirect */ */ protected fun followRedirects(sendRequest: () -> Response): Response { internal fun followRedirects(sendRequest: () -> Response): Response { lateinit var response: Response lateinit var response: Response for (attempt in 1..MAX_REDIRECTS) { for (attempt in 1..MAX_REDIRECTS) { response = sendRequest() response = sendRequest() Loading @@ -397,6 +404,10 @@ open class DavResource @JvmOverloads constructor( val target = it.header("Location")?.let { location.resolve(it) } val target = it.header("Location")?.let { location.resolve(it) } if (target != null) { if (target != null) { log.fine("Redirected, new location = $target") log.fine("Redirected, new location = $target") if (location.isHttps && !target.isHttps) throw DavException("Received redirect from HTTPS to HTTP") location = target location = target } else } else throw DavException("Redirected without new Location") throw DavException("Redirected without new Location") Loading src/test/kotlin/at/bitfire/dav4jvm/DavResourceTest.kt +57 −0 Original line number Original line Diff line number Diff line Loading @@ -13,9 +13,13 @@ import at.bitfire.dav4jvm.property.DisplayName import at.bitfire.dav4jvm.property.GetContentType import at.bitfire.dav4jvm.property.GetContentType import at.bitfire.dav4jvm.property.GetETag import at.bitfire.dav4jvm.property.GetETag import at.bitfire.dav4jvm.property.ResourceType import at.bitfire.dav4jvm.property.ResourceType import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.OkHttpClient import okhttp3.Protocol import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.ResponseBody.Companion.toResponseBody import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer import okhttp3.mockwebserver.MockWebServer import org.junit.After import org.junit.After Loading Loading @@ -722,4 +726,57 @@ class DavResourceTest { assertTrue(called) assertTrue(called) } } @Test fun testFollowRedirects_302() { val url = sampleUrl() val dav = DavResource(httpClient, url) var i = 0 dav.followRedirects { if (i++ == 0) okhttp3.Response.Builder() .protocol(Protocol.HTTP_1_1) .code(302) .message("Found") .header("Location", "http://to.com/") .request(Request.Builder() .get() .url("http://from.com/") .build()) .body("New location!".toResponseBody()) .build() else okhttp3.Response.Builder() .protocol(Protocol.HTTP_1_1) .code(204) .message("No Content") .request(Request.Builder() .get() .url("http://to.com/") .build()) .build() }.let { response -> assertEquals(204, response.code) assertEquals("http://to.com/".toHttpUrl(), dav.location) } } @Test(expected = DavException::class) fun testFollowRedirects_HttpsToHttp() { val dav = DavResource(httpClient, "https://from.com".toHttpUrl()) dav.followRedirects { okhttp3.Response.Builder() .protocol(Protocol.HTTP_1_1) .code(302) .message("Found") .header("Location", "http://to.com/") .request(Request.Builder() .get() .url("https://from.com/") .build()) .body("New location!".toResponseBody()) .build() } } } } Loading
build.gradle.kts +1 −1 Original line number Original line Diff line number Diff line Loading @@ -2,7 +2,7 @@ import org.jetbrains.dokka.gradle.DokkaTask object Libs { object Libs { // okhttp HTTP library // okhttp HTTP library const val okhttpVersion = "4.7.2" const val okhttpVersion = "4.8.0" // XmlPullParser library // XmlPullParser library const val xpp3Version = "1.1.6" const val xpp3Version = "1.1.6" Loading
src/main/kotlin/at/bitfire/dav4jvm/DavResource.kt +15 −4 Original line number Original line Diff line number Diff line Loading @@ -82,6 +82,7 @@ open class DavResource @JvmOverloads constructor( * * * @throws IOException on I/O error * @throws IOException on I/O error * @throws HttpException on HTTP error * @throws HttpException on HTTP error * @throws DavException on HTTPS -> HTTP redirect */ */ @Throws(IOException::class, HttpException::class) @Throws(IOException::class, HttpException::class) fun options(callback: (davCapabilities: Set<String>, response: Response) -> Unit) { fun options(callback: (davCapabilities: Set<String>, response: Response) -> Unit) { Loading @@ -104,7 +105,7 @@ open class DavResource @JvmOverloads constructor( * * * @throws IOException on I/O error * @throws IOException on I/O error * @throws HttpException on HTTP error * @throws HttpException on HTTP error * @throws DavException on WebDAV error * @throws DavException on WebDAV error or HTTPS -> HTTP redirect */ */ @Throws(IOException::class, HttpException::class, DavException::class) @Throws(IOException::class, HttpException::class, DavException::class) fun move(destination: HttpUrl, forceOverride: Boolean, callback: (response: Response) -> Unit) { fun move(destination: HttpUrl, forceOverride: Boolean, callback: (response: Response) -> Unit) { Loading Loading @@ -145,7 +146,7 @@ open class DavResource @JvmOverloads constructor( * * * @throws IOException on I/O error * @throws IOException on I/O error * @throws HttpException on HTTP error * @throws HttpException on HTTP error * @throws DavException on WebDAV error * @throws DavException on WebDAV error or HTTPS -> HTTP redirect */ */ @Throws(IOException::class, HttpException::class, DavException::class) @Throws(IOException::class, HttpException::class, DavException::class) fun copy(destination:HttpUrl, forceOverride:Boolean, callback: (response: Response) -> Unit) { fun copy(destination:HttpUrl, forceOverride:Boolean, callback: (response: Response) -> Unit) { Loading Loading @@ -179,6 +180,7 @@ open class DavResource @JvmOverloads constructor( * * * @throws IOException on I/O error * @throws IOException on I/O error * @throws HttpException on HTTP error * @throws HttpException on HTTP error * @throws DavException on HTTPS -> HTTP redirect */ */ @Throws(IOException::class, HttpException::class) @Throws(IOException::class, HttpException::class) fun mkCol(xmlBody: String?, callback: (response: Response) -> Unit) { fun mkCol(xmlBody: String?, callback: (response: Response) -> Unit) { Loading Loading @@ -206,6 +208,7 @@ open class DavResource @JvmOverloads constructor( * * * @throws IOException on I/O error * @throws IOException on I/O error * @throws HttpException on HTTP error * @throws HttpException on HTTP error * @throws DavException on HTTPS -> HTTP redirect */ */ @Throws(IOException::class, HttpException::class) @Throws(IOException::class, HttpException::class) fun get(accept: String, callback: (response: Response) -> Unit) { fun get(accept: String, callback: (response: Response) -> Unit) { Loading Loading @@ -235,6 +238,7 @@ open class DavResource @JvmOverloads constructor( * * * @throws IOException on I/O error * @throws IOException on I/O error * @throws HttpException on HTTP error * @throws HttpException on HTTP error * @throws DavException on HTTPS -> HTTP redirect */ */ @Throws(IOException::class, HttpException::class) @Throws(IOException::class, HttpException::class) fun put(body: RequestBody, ifETag: String? = null, ifScheduleTag: String? = null, ifNoneMatch: Boolean = false, callback: (Response) -> Unit) { fun put(body: RequestBody, ifETag: String? = null, ifScheduleTag: String? = null, ifNoneMatch: Boolean = false, callback: (Response) -> Unit) { Loading Loading @@ -273,6 +277,7 @@ open class DavResource @JvmOverloads constructor( * @throws IOException on I/O error * @throws IOException on I/O error * @throws HttpException on HTTP errors, or when 207 Multi-Status is returned * @throws HttpException on HTTP errors, or when 207 Multi-Status is returned * (because then there was probably a problem with a member resource) * (because then there was probably a problem with a member resource) * @throws DavException on HTTPS -> HTTP redirect */ */ @Throws(IOException::class, HttpException::class) @Throws(IOException::class, HttpException::class) fun delete(ifETag: String? = null, ifScheduleTag: String? = null, callback: (Response) -> Unit) { fun delete(ifETag: String? = null, ifScheduleTag: String? = null, callback: (Response) -> Unit) { Loading Loading @@ -310,7 +315,7 @@ open class DavResource @JvmOverloads constructor( * * * @throws IOException on I/O error * @throws IOException on I/O error * @throws HttpException on HTTP error * @throws HttpException on HTTP error * @throws DavException on WebDAV error (like no 207 Multi-Status response) * @throws DavException on WebDAV error (like no 207 Multi-Status response) or HTTPS -> HTTP redirect */ */ @Throws(IOException::class, HttpException::class, DavException::class) @Throws(IOException::class, HttpException::class, DavException::class) fun propfind(depth: Int, vararg reqProp: Property.Name, callback: DavResponseCallback) { fun propfind(depth: Int, vararg reqProp: Property.Name, callback: DavResponseCallback) { Loading Loading @@ -386,8 +391,10 @@ open class DavResource @JvmOverloads constructor( * @param sendRequest called to send the request (may be called multiple times) * @param sendRequest called to send the request (may be called multiple times) * * * @return response of the last request (whether it is a redirect or not) * @return response of the last request (whether it is a redirect or not) * * @throws DavException on HTTPS -> HTTP redirect */ */ protected fun followRedirects(sendRequest: () -> Response): Response { internal fun followRedirects(sendRequest: () -> Response): Response { lateinit var response: Response lateinit var response: Response for (attempt in 1..MAX_REDIRECTS) { for (attempt in 1..MAX_REDIRECTS) { response = sendRequest() response = sendRequest() Loading @@ -397,6 +404,10 @@ open class DavResource @JvmOverloads constructor( val target = it.header("Location")?.let { location.resolve(it) } val target = it.header("Location")?.let { location.resolve(it) } if (target != null) { if (target != null) { log.fine("Redirected, new location = $target") log.fine("Redirected, new location = $target") if (location.isHttps && !target.isHttps) throw DavException("Received redirect from HTTPS to HTTP") location = target location = target } else } else throw DavException("Redirected without new Location") throw DavException("Redirected without new Location") Loading
src/test/kotlin/at/bitfire/dav4jvm/DavResourceTest.kt +57 −0 Original line number Original line Diff line number Diff line Loading @@ -13,9 +13,13 @@ import at.bitfire.dav4jvm.property.DisplayName import at.bitfire.dav4jvm.property.GetContentType import at.bitfire.dav4jvm.property.GetContentType import at.bitfire.dav4jvm.property.GetETag import at.bitfire.dav4jvm.property.GetETag import at.bitfire.dav4jvm.property.ResourceType import at.bitfire.dav4jvm.property.ResourceType import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.OkHttpClient import okhttp3.Protocol import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.ResponseBody.Companion.toResponseBody import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer import okhttp3.mockwebserver.MockWebServer import org.junit.After import org.junit.After Loading Loading @@ -722,4 +726,57 @@ class DavResourceTest { assertTrue(called) assertTrue(called) } } @Test fun testFollowRedirects_302() { val url = sampleUrl() val dav = DavResource(httpClient, url) var i = 0 dav.followRedirects { if (i++ == 0) okhttp3.Response.Builder() .protocol(Protocol.HTTP_1_1) .code(302) .message("Found") .header("Location", "http://to.com/") .request(Request.Builder() .get() .url("http://from.com/") .build()) .body("New location!".toResponseBody()) .build() else okhttp3.Response.Builder() .protocol(Protocol.HTTP_1_1) .code(204) .message("No Content") .request(Request.Builder() .get() .url("http://to.com/") .build()) .build() }.let { response -> assertEquals(204, response.code) assertEquals("http://to.com/".toHttpUrl(), dav.location) } } @Test(expected = DavException::class) fun testFollowRedirects_HttpsToHttp() { val dav = DavResource(httpClient, "https://from.com".toHttpUrl()) dav.followRedirects { okhttp3.Response.Builder() .protocol(Protocol.HTTP_1_1) .code(302) .message("Found") .header("Location", "http://to.com/") .request(Request.Builder() .get() .url("https://from.com/") .build()) .body("New location!".toResponseBody()) .build() } } } }