From 1169e15017a900d7fb3f29e42ea18d45e3c5d33a Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Mon, 12 Mar 2018 12:06:53 +0100 Subject: [PATCH] Update okhttp, tests in Kotlin, refactoring --- build.gradle | 8 +- ...avResourceTest.java => DavResourceTest.kt} | 484 +++++++++--------- .../dav4android/BasicDigestAuthHandler.kt | 4 +- .../java/at/bitfire/dav4android/Constants.kt | 1 - .../at/bitfire/dav4android/DavAddressBook.kt | 5 - .../at/bitfire/dav4android/DavCalendar.kt | 3 - .../at/bitfire/dav4android/DavCollection.kt | 10 +- .../at/bitfire/dav4android/DavResource.kt | 12 +- .../java/at/bitfire/dav4android/HttpUtils.kt | 2 - .../java/at/bitfire/dav4android/Property.kt | 10 +- .../bitfire/dav4android/PropertyCollection.kt | 17 +- .../at/bitfire/dav4android/PropertyFactory.kt | 5 +- .../bitfire/dav4android/QuotedStringUtils.kt | 15 +- .../java/at/bitfire/dav4android/UrlUtils.kt | 4 - .../java/at/bitfire/dav4android/XmlUtils.kt | 13 +- .../dav4android/exception/DavException.kt | 5 +- .../dav4android/exception/HttpException.kt | 6 +- .../exception/InvalidDavResponseException.kt | 5 +- .../exception/UnsupportedDavException.kt | 2 +- .../bitfire/dav4android/property/GetETag.kt | 2 +- .../dav4android/property/ResourceType.kt | 10 +- .../property/SupportedReportSet.kt | 4 +- .../BasicDigestAuthHandlerTest.java | 277 ---------- .../dav4android/BasicDigestAuthHandlerTest.kt | 268 ++++++++++ .../at/bitfire/dav4android/HttpUtilsTest.java | 144 ------ .../at/bitfire/dav4android/HttpUtilsTest.kt | 140 +++++ ...tilsTest.java => QuotedStringUtilsTest.kt} | 41 +- .../at/bitfire/dav4android/UrlUtilsTest.java | 61 --- .../at/bitfire/dav4android/UrlUtilsTest.kt | 56 ++ .../exception/HttpExceptionTest.java | 47 -- .../exception/HttpExceptionTest.kt | 40 ++ .../ServiceUnavailableExceptionTest.java | 65 --- .../ServiceUnavailableExceptionTest.kt | 60 +++ 33 files changed, 883 insertions(+), 943 deletions(-) rename src/androidTest/java/at/bitfire/dav4android/{DavResourceTest.java => DavResourceTest.kt} (52%) delete mode 100644 src/test/java/at/bitfire/dav4android/BasicDigestAuthHandlerTest.java create mode 100644 src/test/java/at/bitfire/dav4android/BasicDigestAuthHandlerTest.kt delete mode 100644 src/test/java/at/bitfire/dav4android/HttpUtilsTest.java create mode 100644 src/test/java/at/bitfire/dav4android/HttpUtilsTest.kt rename src/test/java/at/bitfire/dav4android/{QuotedStringUtilsTest.java => QuotedStringUtilsTest.kt} (68%) delete mode 100644 src/test/java/at/bitfire/dav4android/UrlUtilsTest.java create mode 100644 src/test/java/at/bitfire/dav4android/UrlUtilsTest.kt delete mode 100644 src/test/java/at/bitfire/dav4android/exception/HttpExceptionTest.java create mode 100644 src/test/java/at/bitfire/dav4android/exception/HttpExceptionTest.kt delete mode 100644 src/test/java/at/bitfire/dav4android/exception/ServiceUnavailableExceptionTest.java create mode 100644 src/test/java/at/bitfire/dav4android/exception/ServiceUnavailableExceptionTest.kt diff --git a/build.gradle b/build.gradle index 94c9a11..f514826 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { - ext.kotlin_version = '1.2.21' + ext.kotlin_version = '1.2.30' ext.dokka_version = '0.9.15' repositories { @@ -52,12 +52,12 @@ android { dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" - compile 'com.squareup.okhttp3:okhttp:3.9.1' + compile 'com.squareup.okhttp3:okhttp:3.10.0' - androidTestCompile 'com.squareup.okhttp3:mockwebserver:3.9.1' + androidTestCompile 'com.squareup.okhttp3:mockwebserver:3.10.0' androidTestCompile 'com.android.support.test:runner:1.0.1' testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" testCompile 'junit:junit:4.12' - testCompile 'com.squareup.okhttp3:mockwebserver:3.9.1' + testCompile 'com.squareup.okhttp3:mockwebserver:3.10.0' } diff --git a/src/androidTest/java/at/bitfire/dav4android/DavResourceTest.java b/src/androidTest/java/at/bitfire/dav4android/DavResourceTest.kt similarity index 52% rename from src/androidTest/java/at/bitfire/dav4android/DavResourceTest.java rename to src/androidTest/java/at/bitfire/dav4android/DavResourceTest.kt index 5880999..0e65931 100644 --- a/src/androidTest/java/at/bitfire/dav4android/DavResourceTest.java +++ b/src/androidTest/java/at/bitfire/dav4android/DavResourceTest.kt @@ -6,290 +6,271 @@ * http://www.gnu.org/licenses/gpl.html */ -package at.bitfire.dav4android; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.io.IOException; -import java.net.HttpURLConnection; - -import at.bitfire.dav4android.exception.DavException; -import at.bitfire.dav4android.exception.HttpException; -import at.bitfire.dav4android.exception.InvalidDavResponseException; -import at.bitfire.dav4android.exception.PreconditionFailedException; -import at.bitfire.dav4android.property.DisplayName; -import at.bitfire.dav4android.property.GetCTag; -import at.bitfire.dav4android.property.GetContentType; -import at.bitfire.dav4android.property.GetETag; -import at.bitfire.dav4android.property.ResourceType; -import okhttp3.HttpUrl; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.RequestBody; -import okhttp3.ResponseBody; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import okhttp3.mockwebserver.RecordedRequest; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -public class DavResourceTest { - - private static final String - sampleText = "SAMPLE RESPONSE"; - - private OkHttpClient httpClient = new OkHttpClient.Builder() +package at.bitfire.dav4android + +import at.bitfire.dav4android.exception.HttpException +import at.bitfire.dav4android.exception.InvalidDavResponseException +import at.bitfire.dav4android.exception.PreconditionFailedException +import at.bitfire.dav4android.property.* +import okhttp3.MediaType +import okhttp3.OkHttpClient +import okhttp3.RequestBody +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.junit.After +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test +import java.net.HttpURLConnection + +class DavResourceTest { + + private val sampleText = "SAMPLE RESPONSE" + + private val httpClient = OkHttpClient.Builder() .followRedirects(false) - .build(); - private MockWebServer mockServer = new MockWebServer(); + .build() + private val mockServer = MockWebServer() @Before - public void startServer() throws IOException { - mockServer.start(); + fun startServer() { + mockServer.start() } @After - public void stopServer() throws IOException { - mockServer.shutdown(); + fun stopServer() { + mockServer.shutdown() } - private HttpUrl sampleUrl() { - return mockServer.url("/dav/"); - } + private fun sampleUrl() = mockServer.url("/dav/") @Test - public void testOptions() throws InterruptedException, IOException, HttpException, DavException { - HttpUrl url = sampleUrl(); - DavResource dav = new DavResource(httpClient, url); + fun testOptions() { + val url = sampleUrl() + val dav = DavResource(httpClient, url) - mockServer.enqueue(new MockResponse() + mockServer.enqueue(MockResponse() .setResponseCode(HttpURLConnection.HTTP_OK) - .setHeader("DAV", "1,2,3, hyperactive-access")); - dav.options(); - assertTrue(dav.getCapabilities().contains("1")); - assertTrue(dav.getCapabilities().contains("2")); - assertTrue(dav.getCapabilities().contains("3")); - assertTrue(dav.getCapabilities().contains("hyperactive-access")); - - mockServer.enqueue(new MockResponse() - .setResponseCode(HttpURLConnection.HTTP_OK)); - dav.options(); - assertTrue(dav.getCapabilities().isEmpty()); + .setHeader("DAV", "1,2,3, hyperactive-access")) + dav.options() + assertTrue(dav.capabilities.contains("1")) + assertTrue(dav.capabilities.contains("2")) + assertTrue(dav.capabilities.contains("3")) + assertTrue(dav.capabilities.contains("hyperactive-access")) + + mockServer.enqueue(MockResponse() + .setResponseCode(HttpURLConnection.HTTP_OK)) + dav.options() + assertTrue(dav.capabilities.isEmpty()) } @Test - public void testGet() throws InterruptedException, IOException, HttpException, DavException { - HttpUrl url = sampleUrl(); - DavResource dav = new DavResource(httpClient, url); + fun testGet() { + val url = sampleUrl() + val dav = DavResource(httpClient, url) /* POSITIVE TEST CASES */ // 200 OK - mockServer.enqueue(new MockResponse() + mockServer.enqueue(MockResponse() .setResponseCode(HttpURLConnection.HTTP_OK) .setHeader("ETag", "W/\"My Weak ETag\"") .setHeader("Content-Type", "application/x-test-result") - .setBody(sampleText)); - ResponseBody body = dav.get("*/*"); - assertEquals(sampleText, body.string()); - assertEquals("My Weak ETag", dav.getProperties().get(GetETag.class).getETag()); - assertEquals("application/x-test-result", dav.getProperties().get(GetContentType.class).getType()); + .setBody(sampleText)) + var body = dav.get("*/*") + assertEquals(sampleText, body.string()) + assertEquals("My Weak ETag", dav.properties[GetETag::class.java]?.eTag) + assertEquals("application/x-test-result", dav.properties[GetContentType::class.java]?.type) - RecordedRequest rq = mockServer.takeRequest(); - assertEquals("GET", rq.getMethod()); - assertEquals(url.encodedPath(), rq.getPath()); - assertEquals("*/*", rq.getHeader("Accept")); + var rq = mockServer.takeRequest() + assertEquals("GET", rq.method) + assertEquals(url.encodedPath(), rq.path) + assertEquals("*/*", rq.getHeader("Accept")) // 302 Moved Temporarily + 200 OK - mockServer.enqueue(new MockResponse() + mockServer.enqueue(MockResponse() .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) .setHeader("Location", "/target") - .setBody("This resource was moved.")); - mockServer.enqueue(new MockResponse() + .setBody("This resource was moved.")) + mockServer.enqueue(MockResponse() .setResponseCode(HttpURLConnection.HTTP_OK) .setHeader("ETag", "\"StrongETag\"") - .setBody(sampleText)); - body = dav.get("*/*"); - assertEquals(sampleText, body.string()); - assertEquals("StrongETag", dav.getProperties().get(GetETag.class).getETag()); + .setBody(sampleText)) + body = dav.get("*/*") + assertEquals(sampleText, body.string()) + assertEquals("StrongETag", dav.properties[GetETag::class.java]?.eTag) - mockServer.takeRequest(); - rq = mockServer.takeRequest(); - assertEquals("GET", rq.getMethod()); - assertEquals("/target", rq.getPath()); + mockServer.takeRequest() + rq = mockServer.takeRequest() + assertEquals("GET", rq.method) + assertEquals("/target", rq.path) // 200 OK without ETag in response - dav.getProperties().set(GetETag.NAME, new GetETag("test")); - mockServer.enqueue(new MockResponse() + dav.properties[GetETag.NAME] = GetETag("test") + mockServer.enqueue(MockResponse() .setResponseCode(HttpURLConnection.HTTP_OK) - .setBody(sampleText)); - dav.get("*/*"); - assertNull(dav.getProperties().get(GetETag.class)); + .setBody(sampleText)) + dav.get("*/*") + assertNull(dav.properties[GetETag::class.java]) } @Test - public void testPut() throws InterruptedException, IOException, HttpException, DavException { - HttpUrl url = sampleUrl(); - DavResource dav = new DavResource(httpClient, url); + fun testPut() { + val url = sampleUrl() + val dav = DavResource(httpClient, url) /* POSITIVE TEST CASES */ // no preconditions, 201 Created - mockServer.enqueue(new MockResponse() + mockServer.enqueue(MockResponse() .setResponseCode(HttpURLConnection.HTTP_CREATED) - .setHeader("ETag", "W/\"Weak PUT ETag\"")); - assertFalse(dav.put(RequestBody.create(MediaType.parse("text/plain"), sampleText), null, false)); - assertEquals("Weak PUT ETag", dav.getProperties().get(GetETag.class).getETag()); + .setHeader("ETag", "W/\"Weak PUT ETag\"")) + assertFalse(dav.put(RequestBody.create(MediaType.parse("text/plain"), sampleText), null, false)) + assertEquals("Weak PUT ETag", dav.properties[GetETag::class.java]?.eTag) - RecordedRequest rq = mockServer.takeRequest(); - assertEquals("PUT", rq.getMethod()); - assertEquals(url.encodedPath(), rq.getPath()); - assertNull(rq.getHeader("If-Match")); - assertNull(rq.getHeader("If-None-Match")); + var rq = mockServer.takeRequest() + assertEquals("PUT", rq.method) + assertEquals(url.encodedPath(), rq.path) + assertNull(rq.getHeader("If-Match")) + assertNull(rq.getHeader("If-None-Match")) // precondition: If-None-Match, 301 Moved Permanently + 204 No Content, no ETag in response - mockServer.enqueue(new MockResponse() + mockServer.enqueue(MockResponse() .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM) - .setHeader("Location", "/target")); - mockServer.enqueue(new MockResponse() - .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT)); - assertTrue(dav.put(RequestBody.create(MediaType.parse("text/plain"), sampleText), null, true)); - assertEquals(url.resolve("/target"), dav.getLocation()); - assertNull(dav.getProperties().get(GetETag.class)); - - mockServer.takeRequest(); - rq = mockServer.takeRequest(); - assertEquals("PUT", rq.getMethod()); - assertEquals("*", rq.getHeader("If-None-Match")); + .setHeader("Location", "/target")) + mockServer.enqueue(MockResponse() + .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT)) + assertTrue(dav.put(RequestBody.create(MediaType.parse("text/plain"), sampleText), null, true)) + assertEquals(url.resolve("/target"), dav.location) + assertNull(dav.properties[GetETag::class.java]) + + mockServer.takeRequest() + rq = mockServer.takeRequest() + assertEquals("PUT", rq.method) + assertEquals("*", rq.getHeader("If-None-Match")) // precondition: If-Match, 412 Precondition Failed - mockServer.enqueue(new MockResponse() - .setResponseCode(HttpURLConnection.HTTP_PRECON_FAILED)); + mockServer.enqueue(MockResponse() + .setResponseCode(HttpURLConnection.HTTP_PRECON_FAILED)) try { - dav.put(RequestBody.create(MediaType.parse("text/plain"), sampleText), "ExistingETag", false); - fail(); - } catch(PreconditionFailedException e) { + dav.put(RequestBody.create(MediaType.parse("text/plain"), sampleText), "ExistingETag", false) + fail() + } catch(e: PreconditionFailedException) { } - rq = mockServer.takeRequest(); - assertEquals("\"ExistingETag\"", rq.getHeader("If-Match")); - assertNull(rq.getHeader("If-None-Match")); + rq = mockServer.takeRequest() + assertEquals("\"ExistingETag\"", rq.getHeader("If-Match")) + assertNull(rq.getHeader("If-None-Match")) } @Test - public void testDelete() throws InterruptedException, IOException, HttpException { - HttpUrl url = sampleUrl(); - DavResource dav = new DavResource(httpClient, url); + fun testDelete() { + val url = sampleUrl() + val dav = DavResource(httpClient, url) /* POSITIVE TEST CASES */ // no preconditions, 204 No Content - mockServer.enqueue(new MockResponse() - .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT)); - dav.delete(null); + mockServer.enqueue(MockResponse() + .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT)) + dav.delete(null) - RecordedRequest rq = mockServer.takeRequest(); - assertEquals("DELETE", rq.getMethod()); - assertEquals(url.encodedPath(), rq.getPath()); - assertNull(rq.getHeader("If-Match")); + var rq = mockServer.takeRequest() + assertEquals("DELETE", rq.method) + assertEquals(url.encodedPath(), rq.path) + assertNull(rq.getHeader("If-Match")) // precondition: If-Match, 200 OK - mockServer.enqueue(new MockResponse() + mockServer.enqueue(MockResponse() .setResponseCode(HttpURLConnection.HTTP_OK) - .setBody("Resource has been deleted.")); - dav.delete("DeleteOnlyThisETag"); + .setBody("Resource has been deleted.")) + dav.delete("DeleteOnlyThisETag") - rq = mockServer.takeRequest(); - assertEquals("\"DeleteOnlyThisETag\"", rq.getHeader("If-Match")); + rq = mockServer.takeRequest() + assertEquals("\"DeleteOnlyThisETag\"", rq.getHeader("If-Match")) // 302 Moved Temporarily - mockServer.enqueue(new MockResponse() + mockServer.enqueue(MockResponse() .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) .setHeader("Location", "/new-location") - ); - mockServer.enqueue(new MockResponse() - .setResponseCode(HttpURLConnection.HTTP_OK)); - dav.delete(null); + ) + mockServer.enqueue(MockResponse() + .setResponseCode(HttpURLConnection.HTTP_OK)) + dav.delete(null) /* NEGATIVE TEST CASES */ // 207 multi-status (e.g. single resource couldn't be deleted when DELETEing a collection) - mockServer.enqueue(new MockResponse() - .setResponseCode(207)); + mockServer.enqueue(MockResponse() + .setResponseCode(207)) try { - dav.delete(null); - fail(); - } catch(HttpException e) { + dav.delete(null) + fail() + } catch(e: HttpException) { // treat 207 as an error } } @Test - public void testPropfindAndMultiStatus() throws IOException, HttpException, DavException { - HttpUrl url = sampleUrl(); - DavResource dav = new DavResource(httpClient, url); + fun testPropfindAndMultiStatus() { + val url = sampleUrl() + val dav = DavResource(httpClient, url) /*** NEGATIVE TESTS ***/ // test for non-multi-status responses: // * 500 Internal Server Error - mockServer.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_INTERNAL_ERROR)); + mockServer.enqueue(MockResponse().setResponseCode(HttpURLConnection.HTTP_INTERNAL_ERROR)) try { - dav.propfind(0, ResourceType.NAME); - fail("Expected HttpException"); - } catch(HttpException e) { + dav.propfind(0, ResourceType.NAME) + fail("Expected HttpException") + } catch(e: HttpException) { } // * 200 OK (instead of 207 Multi-Status) - mockServer.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_OK)); + mockServer.enqueue(MockResponse().setResponseCode(HttpURLConnection.HTTP_OK)) try { - dav.propfind(0, ResourceType.NAME); - fail("Expected InvalidDavResponseException"); - } catch(InvalidDavResponseException e) { + dav.propfind(0, ResourceType.NAME) + fail("Expected InvalidDavResponseException") + } catch(e: InvalidDavResponseException) { } // test for invalid multi-status responses: // * non-XML response - mockServer.enqueue(new MockResponse() + mockServer.enqueue(MockResponse() .setResponseCode(207) .setHeader("Content-Type", "text/html") - .setBody("")); + .setBody("")) try { - dav.propfind(0, ResourceType.NAME); - fail("Expected InvalidDavResponseException"); - } catch(InvalidDavResponseException e) { + dav.propfind(0, ResourceType.NAME) + fail("Expected InvalidDavResponseException") + } catch(e: InvalidDavResponseException) { } // * malformed XML response - mockServer.enqueue(new MockResponse() + mockServer.enqueue(MockResponse() .setResponseCode(207) .setHeader("Content-Type", "application/xml; charset=utf-8") - .setBody("")); + .setBody("")) try { - dav.propfind(0, ResourceType.NAME); - fail("Expected InvalidDavResponseException"); - } catch(InvalidDavResponseException e) { + dav.propfind(0, ResourceType.NAME) + fail("Expected InvalidDavResponseException") + } catch(e: InvalidDavResponseException) { } // * response without root element - mockServer.enqueue(new MockResponse() + mockServer.enqueue(MockResponse() .setResponseCode(207) .setHeader("Content-Type", "application/xml; charset=utf-8") - .setBody("")); + .setBody("")) try { - dav.propfind(0, ResourceType.NAME); - fail("Expected InvalidDavResponseException"); - } catch(InvalidDavResponseException e) { + dav.propfind(0, ResourceType.NAME) + fail("Expected InvalidDavResponseException") + } catch(e: InvalidDavResponseException) { } // * multi-status response with invalid in - mockServer.enqueue(new MockResponse() + mockServer.enqueue(MockResponse() .setResponseCode(207) .setHeader("Content-Type", "application/xml; charset=utf-8") .setBody("" + @@ -297,15 +278,15 @@ public class DavResourceTest { " /dav" + " Invalid Status Line" + " " + - "")); + "")) try { - dav.propfind(0, ResourceType.NAME); - fail("Expected HttpException"); - } catch(HttpException e) { + dav.propfind(0, ResourceType.NAME) + fail("Expected HttpException") + } catch(e: HttpException) { } // * multi-status response with / element indicating failure - mockServer.enqueue(new MockResponse() + mockServer.enqueue(MockResponse() .setResponseCode(207) .setHeader("Content-Type", "application/xml; charset=utf-8") .setBody("" + @@ -313,15 +294,15 @@ public class DavResourceTest { " /dav" + " HTTP/1.1 403 Forbidden" + " " + - "")); + "")) try { - dav.propfind(0, ResourceType.NAME); - fail("Expected HttpException"); - } catch(HttpException e) { + dav.propfind(0, ResourceType.NAME) + fail("Expected HttpException") + } catch(e: HttpException) { } // * multi-status response with invalid in - mockServer.enqueue(new MockResponse() + mockServer.enqueue(MockResponse() .setResponseCode(207) .setHeader("Content-Type", "application/xml; charset=utf-8") .setBody("" + @@ -334,24 +315,24 @@ public class DavResourceTest { " Invalid Status Line" + " " + " " + - "")); - dav.propfind(0, ResourceType.NAME); - assertNull(dav.getProperties().get(ResourceType.class)); + "")) + dav.propfind(0, ResourceType.NAME) + assertNull(dav.properties[ResourceType::class.java]) /*** POSITIVE TESTS ***/ // multi-status response without elements - mockServer.enqueue(new MockResponse() + mockServer.enqueue(MockResponse() .setResponseCode(207) .setHeader("Content-Type", "application/xml; charset=utf-8") - .setBody("")); - dav.propfind(0, ResourceType.NAME); - assertEquals(0, dav.getProperties().size()); - assertEquals(0, dav.getMembers().size()); + .setBody("")) + dav.propfind(0, ResourceType.NAME) + assertEquals(0, dav.properties.size()) + assertEquals(0, dav.members.size) // multi-status response with / element indicating success - mockServer.enqueue(new MockResponse() + mockServer.enqueue(MockResponse() .setResponseCode(207) .setHeader("Content-Type", "application/xml; charset=utf-8") .setBody("" + @@ -359,13 +340,13 @@ public class DavResourceTest { " /dav" + " HTTP/1.1 200 OK" + " " + - "")); - dav.propfind(0, ResourceType.NAME); - assertEquals(0, dav.getProperties().size()); - assertEquals(0, dav.getMembers().size()); + "")) + dav.propfind(0, ResourceType.NAME) + assertEquals(0, dav.properties.size()) + assertEquals(0, dav.members.size) // multi-status response with / element - mockServer.enqueue(new MockResponse() + mockServer.enqueue(MockResponse() .setResponseCode(207) .setHeader("Content-Type", "application/xml; charset=utf-8") .setBody("" + @@ -380,13 +361,13 @@ public class DavResourceTest { " HTTP/1.1 200 OK" + " " + " " + - "")); - dav.propfind(0, ResourceType.NAME, DisplayName.NAME); - assertEquals("My DAV Collection", dav.getProperties().get(DisplayName.class).getDisplayName()); - assertEquals(0, dav.getMembers().size()); + "")) + dav.propfind(0, ResourceType.NAME, DisplayName.NAME) + assertEquals("My DAV Collection", dav.properties[DisplayName::class.java]?.displayName) + assertEquals(0, dav.members.size) // multi-status response for collection with several members; incomplete (not all s listed) - mockServer.enqueue(new MockResponse() + mockServer.enqueue(MockResponse() .setResponseCode(207) .setHeader("Content-Type", "application/xml; charset=utf-8") .setBody("" + @@ -446,34 +427,37 @@ public class DavResourceTest { " HTTP/1.1 200 OK" + " " + " " + - "")); - dav.propfind(1, ResourceType.NAME, DisplayName.NAME); - assertEquals(4, dav.getMembers().size()); - boolean ok[] = new boolean[4]; - for (DavResource member : dav.getMembers()) { - if (url.resolve("/dav/subcollection/").equals(member.getLocation())) { - assertTrue(member.getProperties().get(ResourceType.class).getTypes().contains(ResourceType.COLLECTION)); - assertEquals("A Subfolder", member.getProperties().get(DisplayName.class).getDisplayName()); - ok[0] = true; - } else if (url.resolve("/dav/uid@host:file").equals(member.getLocation())) { - assertEquals("Absolute path with @ and :", member.getProperties().get(DisplayName.class).getDisplayName()); - ok[1] = true; - } else if (url.resolve("/dav/relative-uid@host.file").equals(member.getLocation())) { - assertEquals("Relative path with @", member.getProperties().get(DisplayName.class).getDisplayName()); - ok[2] = true; - } else if (url.resolve("/dav/relative:colon.vcf").equals(member.getLocation())) { - assertEquals("Relative path with colon", member.getProperties().get(DisplayName.class).getDisplayName()); - ok[3] = true; + "")) + dav.propfind(1, ResourceType.NAME, DisplayName.NAME) + assertEquals(4, dav.members.size) + val ok = BooleanArray(4) + for (member in dav.members) + when (member.location) { + url.resolve("/dav/subcollection/") -> { + assertTrue(member.properties[ResourceType::class.java]!!.types.contains(ResourceType.COLLECTION)) + assertEquals("A Subfolder", member.properties[DisplayName::class.java]?.displayName) + ok[0] = true + } + url.resolve("/dav/uid@host:file") -> { + assertEquals("Absolute path with @ and :", member.properties[DisplayName::class.java]?.displayName) + ok[1] = true + } + url.resolve("/dav/relative-uid@host.file") -> { + assertEquals("Relative path with @", member.properties[DisplayName::class.java]?.displayName) + ok[2] = true + } + url.resolve("/dav/relative:colon.vcf") -> { + assertEquals("Relative path with colon", member.properties[DisplayName::class.java]?.displayName) + ok[3] = true + } } - } - for (boolean singleOK : ok) - assertTrue(singleOK); + assertTrue(ok.all { it }) /*** SPECIAL CASES ***/ // same property is sent as 200 OK and 404 Not Found in same (seen in iCloud) - mockServer.enqueue(new MockResponse() + mockServer.enqueue(MockResponse() .setResponseCode(207) .setHeader("Content-Type", "application/xml; charset=utf-8") .setBody("" + @@ -493,13 +477,13 @@ public class DavResourceTest { " HTTP/1.1 404 Not Found" + " " + " " + - "")); - dav.propfind(0, ResourceType.NAME, DisplayName.NAME); - assertTrue(dav.getProperties().get(ResourceType.class).getTypes().contains(ResourceType.COLLECTION)); - assertEquals("My DAV Collection", dav.getProperties().get(DisplayName.class).getDisplayName()); + "")) + dav.propfind(0, ResourceType.NAME, DisplayName.NAME) + assertTrue(dav.properties[ResourceType::class.java]!!.types.contains(ResourceType.COLLECTION)) + assertEquals("My DAV Collection", dav.properties[DisplayName::class.java]?.displayName) // multi-status response with that doesn't contain (=> assume 200 OK) - mockServer.enqueue(new MockResponse() + mockServer.enqueue(MockResponse() .setResponseCode(207) .setHeader("Content-Type", "application/xml; charset=utf-8") .setBody("" + @@ -511,17 +495,17 @@ public class DavResourceTest { " " + " " + " " + - "")); - dav.propfind(0, DisplayName.NAME); - assertEquals("Without Status", dav.getProperties().get(DisplayName.class).getDisplayName()); + "")) + dav.propfind(0, DisplayName.NAME) + assertEquals("Without Status", dav.properties[DisplayName::class.java]?.displayName) } @Test - public void testPropfindUpdateProperties() throws IOException, HttpException, DavException { - HttpUrl url = sampleUrl(); - DavResource dav = new DavResource(httpClient, url); + fun testPropfindUpdateProperties() { + val url = sampleUrl() + val dav = DavResource(httpClient, url) - mockServer.enqueue(new MockResponse() + mockServer.enqueue(MockResponse() .setResponseCode(207) .setHeader("Content-Type", "application/xml; charset=utf-8") .setBody("" + @@ -536,13 +520,13 @@ public class DavResourceTest { " HTTP/1.1 200 OK" + " " + " " + - "")); - dav.propfind(0, DisplayName.NAME, GetETag.NAME, GetCTag.NAME); - assertEquals("DisplayName 1", dav.getProperties().get(DisplayName.class).getDisplayName()); - assertEquals("ETag 1", dav.getProperties().get(GetETag.class).getETag()); - assertEquals("CTag 1", dav.getProperties().get(GetCTag.class).getCTag()); + "")) + dav.propfind(0, DisplayName.NAME, GetETag.NAME, GetCTag.NAME) + assertEquals("DisplayName 1", dav.properties[DisplayName::class.java]?.displayName) + assertEquals("ETag 1", dav.properties[GetETag::class.java]?.eTag) + assertEquals("CTag 1", dav.properties[GetCTag::class.java]?.cTag) - mockServer.enqueue(new MockResponse() + mockServer.enqueue(MockResponse() .setResponseCode(207) .setHeader("Content-Type", "application/xml; charset=utf-8") .setBody("" + @@ -561,11 +545,11 @@ public class DavResourceTest { " HTTP/1.1 404 Not Found" + " " + " " + - "")); - dav.propfind(0, ResourceType.NAME, DisplayName.NAME); - assertEquals("DisplayName 2", dav.getProperties().get(DisplayName.class).getDisplayName()); - assertNull(dav.getProperties().get(GetETag.class)); - assertEquals("CTag 1", dav.getProperties().get(GetCTag.class).getCTag()); + "")) + dav.propfind(0, ResourceType.NAME, DisplayName.NAME) + assertEquals("DisplayName 2", dav.properties[DisplayName::class.java]?.displayName) + assertNull(dav.properties[GetETag::class.java]) + assertEquals("CTag 1", dav.properties[GetCTag::class.java]?.cTag) } } diff --git a/src/main/java/at/bitfire/dav4android/BasicDigestAuthHandler.kt b/src/main/java/at/bitfire/dav4android/BasicDigestAuthHandler.kt index 77181fe..c92d2c8 100644 --- a/src/main/java/at/bitfire/dav4android/BasicDigestAuthHandler.kt +++ b/src/main/java/at/bitfire/dav4android/BasicDigestAuthHandler.kt @@ -40,8 +40,8 @@ class BasicDigestAuthHandler( private val HEADER_AUTHORIZATION = "Authorization" // cached digest parameters - @JvmField var clientNonce = h(UUID.randomUUID().toString()) - @JvmField var nonceCount = AtomicInteger(1) + var clientNonce = h(UUID.randomUUID().toString()) + var nonceCount = AtomicInteger(1) fun quotedString(s: String) = "\"" + s.replace("\"", "\\\"") + "\"" fun h(data: String) = ByteString.of(ByteBuffer.wrap(data.toByteArray())).md5().hex()!! diff --git a/src/main/java/at/bitfire/dav4android/Constants.kt b/src/main/java/at/bitfire/dav4android/Constants.kt index f502ce4..25c4990 100644 --- a/src/main/java/at/bitfire/dav4android/Constants.kt +++ b/src/main/java/at/bitfire/dav4android/Constants.kt @@ -12,7 +12,6 @@ import java.util.logging.Logger object Constants { - @JvmField var log = Logger.getLogger("dav4android")!! } \ No newline at end of file diff --git a/src/main/java/at/bitfire/dav4android/DavAddressBook.kt b/src/main/java/at/bitfire/dav4android/DavAddressBook.kt index 6e7a7c0..09635d6 100644 --- a/src/main/java/at/bitfire/dav4android/DavAddressBook.kt +++ b/src/main/java/at/bitfire/dav4android/DavAddressBook.kt @@ -22,10 +22,7 @@ class DavAddressBook @JvmOverloads constructor( ): DavCollection(httpClient, location, log) { companion object { - @JvmField val MIME_VCARD3_UTF8 = MediaType.parse("text/vcard;charset=utf-8") - - @JvmField val MIME_VCARD4 = MediaType.parse("text/vcard;version=4.0") } @@ -36,7 +33,6 @@ class DavAddressBook @JvmOverloads constructor( * @throws HttpException on HTTP error * @throws DavException on DAV error */ - @Throws(IOException::class, HttpException::class, DavException::class) fun addressbookQuery() { /* , vCard4: Boolean) { /* ) { /* diff --git a/src/main/java/at/bitfire/dav4android/DavResource.kt b/src/main/java/at/bitfire/dav4android/DavResource.kt index 574f403..110bd59 100644 --- a/src/main/java/at/bitfire/dav4android/DavResource.kt +++ b/src/main/java/at/bitfire/dav4android/DavResource.kt @@ -429,11 +429,11 @@ open class DavResource @JvmOverloads constructor( "prop" -> prop = parseMultiStatus_Prop() "status" -> - try { - status = StatusLine.parse(parser.nextText()) + status = try { + StatusLine.parse(parser.nextText()) } catch(e: ProtocolException) { log.warning("Invalid status line, treating as 500 Server Error") - status = StatusLine(Protocol.HTTP_1_1, 500, "Invalid status line") + StatusLine(Protocol.HTTP_1_1, 500, "Invalid status line") } } eventType = parser.next() @@ -485,11 +485,11 @@ open class DavResource @JvmOverloads constructor( href = location.resolve(sHref) } "status" -> - try { - status = StatusLine.parse(parser.nextText()) + status = try { + StatusLine.parse(parser.nextText()) } catch(e: ProtocolException) { log.warning("Invalid status line, treating as 500 Server Error") - status = StatusLine(Protocol.HTTP_1_1, 500, "Invalid status line") + StatusLine(Protocol.HTTP_1_1, 500, "Invalid status line") } "propstat" -> parseMultiStatus_PropStat()?.let { properties.merge(it, false) } diff --git a/src/main/java/at/bitfire/dav4android/HttpUtils.kt b/src/main/java/at/bitfire/dav4android/HttpUtils.kt index 6c68453..fc52efa 100644 --- a/src/main/java/at/bitfire/dav4android/HttpUtils.kt +++ b/src/main/java/at/bitfire/dav4android/HttpUtils.kt @@ -17,13 +17,11 @@ object HttpUtils { private val authSchemeWithParam = Pattern.compile("^([^ \"]+) +(.*)$") - @JvmStatic fun listHeader(response: Response, name: String): Array { val value = response.headers(name).joinToString(",") return value.split(',').filter { it.isNotEmpty() }.toTypedArray() } - @JvmStatic fun parseWwwAuthenticate(wwwAuths: List): List { /* WWW-Authenticate = "WWW-Authenticate" ":" 1#challenge diff --git a/src/main/java/at/bitfire/dav4android/Property.kt b/src/main/java/at/bitfire/dav4android/Property.kt index 15baceb..e1895ed 100644 --- a/src/main/java/at/bitfire/dav4android/Property.kt +++ b/src/main/java/at/bitfire/dav4android/Property.kt @@ -11,7 +11,7 @@ package at.bitfire.dav4android /** * A WebDAV property. * - * Every [Property] must define a static field called NAME of type Property.Name, + * Every [Property] must define a static field (use @JvmStatic) called NAME of type [Property.Name], * which will be accessed by reflection. */ interface Property { @@ -21,11 +21,11 @@ interface Property { val name: String ) { - override fun equals(o: Any?): Boolean { - return if (o is Name) - namespace == o.namespace && name == o.name + override fun equals(other: Any?): Boolean { + return if (other is Name) + namespace == other.namespace && name == other.name else - super.equals(o) + super.equals(other) } override fun hashCode() = namespace.hashCode() xor name.hashCode() diff --git a/src/main/java/at/bitfire/dav4android/PropertyCollection.kt b/src/main/java/at/bitfire/dav4android/PropertyCollection.kt index 96ba32d..b4d59ff 100644 --- a/src/main/java/at/bitfire/dav4android/PropertyCollection.kt +++ b/src/main/java/at/bitfire/dav4android/PropertyCollection.kt @@ -33,12 +33,12 @@ class PropertyCollection { if (!properties.isInitialized()) return null - try { + return try { val name = clazz.getDeclaredField("NAME").get(null) as Property.Name - return properties.value[name] as? T + properties.value[name] as? T } catch (e: NoSuchFieldException) { Constants.log.severe("$clazz does not have a static NAME field") - return null + null } } @@ -66,15 +66,14 @@ class PropertyCollection { /** - * Merges another #{@link PropertyCollection} into #{@link #properties}. + * Merges another [PropertyCollection] into [properties]. * Existing properties will be overwritten. + * * @param another property collection to take the properties from * @param removeNullValues Indicates how "another" properties with null values should be treated. - *
    - *
  • #{@code true}: If the "another" property value is #{@code null}, the property will be removed in #{@link #properties}.
  • - *
  • #{@code false}: If the "another" property value is #{@code null}, the property in #{@link #properties} will be set to null, too, - but only if it doesn't exist yet. This means values in #{@link #properties} will never be overwritten by #{@code null}.
  • - *
+ * - true: If the "another" property value is null, the property will be removed in [properties]. + * - false: If the "another" property value is null, the property in [properties] will be set to null, too, + * but only if it doesn't exist yet. This means values in [properties] will never be overwritten by null. */ fun merge(another: PropertyCollection, removeNullValues: Boolean) { val properties = another.getMap() diff --git a/src/main/java/at/bitfire/dav4android/PropertyFactory.kt b/src/main/java/at/bitfire/dav4android/PropertyFactory.kt index 59c56df..d60c9be 100644 --- a/src/main/java/at/bitfire/dav4android/PropertyFactory.kt +++ b/src/main/java/at/bitfire/dav4android/PropertyFactory.kt @@ -20,7 +20,10 @@ interface PropertyFactory { */ fun getName(): Property.Name - @Throws(IOException::class, XmlPullParserException::class) + /** + * Parses XML of a property and returns its data class. + * @throws XmlPullParserException in case of parsing errors + */ fun create(parser: XmlPullParser): Property? } diff --git a/src/main/java/at/bitfire/dav4android/QuotedStringUtils.kt b/src/main/java/at/bitfire/dav4android/QuotedStringUtils.kt index 5b161e4..5a69165 100644 --- a/src/main/java/at/bitfire/dav4android/QuotedStringUtils.kt +++ b/src/main/java/at/bitfire/dav4android/QuotedStringUtils.kt @@ -10,19 +10,10 @@ package at.bitfire.dav4android object QuotedStringUtils { - @JvmStatic - fun asQuotedString(raw: String?): String? { - return if (raw == null) - null - else + fun asQuotedString(raw: String) = "\"" + raw.replace("\\" ,"\\\\").replace("\"", "\\\"") + "\"" - } - - @JvmStatic - fun decodeQuotedString(quoted: String?): String? { - if (quoted == null) - return null + fun decodeQuotedString(quoted: String): String { /* quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) qdtext = > quoted-pair = "\" CHAR @@ -44,4 +35,4 @@ object QuotedStringUtils { return quoted } -} +} \ No newline at end of file diff --git a/src/main/java/at/bitfire/dav4android/UrlUtils.kt b/src/main/java/at/bitfire/dav4android/UrlUtils.kt index 4f23bb6..36af08f 100644 --- a/src/main/java/at/bitfire/dav4android/UrlUtils.kt +++ b/src/main/java/at/bitfire/dav4android/UrlUtils.kt @@ -14,7 +14,6 @@ import java.net.URISyntaxException object UrlUtils { - @JvmStatic fun equals(url1: HttpUrl, url2: HttpUrl): Boolean { // if okhttp thinks the two URLs are equal, they're in any case // (and it's a simple String comparison) @@ -32,7 +31,6 @@ object UrlUtils { } } - @JvmStatic fun hostToDomain(host: String?): String? { if (host == null) return null @@ -49,7 +47,6 @@ object UrlUtils { host } - @JvmStatic fun omitTrailingSlash(url: HttpUrl): HttpUrl { val idxLast = url.pathSize () - 1 val hasTrailingSlash = url.pathSegments()[idxLast] == "" @@ -60,7 +57,6 @@ object UrlUtils { url } - @JvmStatic fun withTrailingSlash(url: HttpUrl): HttpUrl { val idxLast = url.pathSize() - 1 val hasTrailingSlash = url.pathSegments()[idxLast] == "" diff --git a/src/main/java/at/bitfire/dav4android/XmlUtils.kt b/src/main/java/at/bitfire/dav4android/XmlUtils.kt index b6a823b..48051eb 100644 --- a/src/main/java/at/bitfire/dav4android/XmlUtils.kt +++ b/src/main/java/at/bitfire/dav4android/XmlUtils.kt @@ -15,11 +15,11 @@ import java.io.IOException object XmlUtils { - @JvmField val NS_WEBDAV = "DAV:" - @JvmField val NS_CALDAV = "urn:ietf:params:xml:ns:caldav" - @JvmField val NS_CARDDAV = "urn:ietf:params:xml:ns:carddav" - @JvmField val NS_APPLE_ICAL = "http://apple.com/ns/ical/" - @JvmField val NS_CALENDARSERVER = "http://calendarserver.org/ns/" + const val NS_WEBDAV = "DAV:" + const val NS_CALDAV = "urn:ietf:params:xml:ns:caldav" + const val NS_CARDDAV = "urn:ietf:params:xml:ns:carddav" + const val NS_APPLE_ICAL = "http://apple.com/ns/ical/" + const val NS_CALENDARSERVER = "http://calendarserver.org/ns/" private val factory: XmlPullParserFactory init { @@ -31,10 +31,7 @@ object XmlUtils { } } - @JvmStatic fun newPullParser() = factory.newPullParser()!! - - @JvmStatic fun newSerializer() = factory.newSerializer()!! diff --git a/src/main/java/at/bitfire/dav4android/exception/DavException.kt b/src/main/java/at/bitfire/dav4android/exception/DavException.kt index 730d4af..8becc81 100644 --- a/src/main/java/at/bitfire/dav4android/exception/DavException.kt +++ b/src/main/java/at/bitfire/dav4android/exception/DavException.kt @@ -8,4 +8,7 @@ package at.bitfire.dav4android.exception -open class DavException @JvmOverloads constructor(message: String, ex: Throwable? = null): Exception(message, ex) \ No newline at end of file +open class DavException @JvmOverloads constructor( + message: String, + ex: Throwable? = null +): Exception(message, ex) \ No newline at end of file diff --git a/src/main/java/at/bitfire/dav4android/exception/HttpException.kt b/src/main/java/at/bitfire/dav4android/exception/HttpException.kt index d599a91..4ca589a 100644 --- a/src/main/java/at/bitfire/dav4android/exception/HttpException.kt +++ b/src/main/java/at/bitfire/dav4android/exception/HttpException.kt @@ -15,8 +15,10 @@ import java.io.* open class HttpException: Exception, Serializable { - // don't dump more than 20 kB - private val MAX_DUMP_SIZE = 20*1024 + companion object { + // don't dump more than 20 kB + private const val MAX_DUMP_SIZE = 20*1024 + } val status: Int val request: String? diff --git a/src/main/java/at/bitfire/dav4android/exception/InvalidDavResponseException.kt b/src/main/java/at/bitfire/dav4android/exception/InvalidDavResponseException.kt index 5bec820..bbe7e7e 100644 --- a/src/main/java/at/bitfire/dav4android/exception/InvalidDavResponseException.kt +++ b/src/main/java/at/bitfire/dav4android/exception/InvalidDavResponseException.kt @@ -8,4 +8,7 @@ package at.bitfire.dav4android.exception -class InvalidDavResponseException(message: String, ex: Throwable? = null): DavException(message, ex) \ No newline at end of file +class InvalidDavResponseException @JvmOverloads constructor( + message: String, + ex: Throwable? = null +): DavException(message, ex) \ No newline at end of file diff --git a/src/main/java/at/bitfire/dav4android/exception/UnsupportedDavException.kt b/src/main/java/at/bitfire/dav4android/exception/UnsupportedDavException.kt index 0f80d86..0530bf6 100644 --- a/src/main/java/at/bitfire/dav4android/exception/UnsupportedDavException.kt +++ b/src/main/java/at/bitfire/dav4android/exception/UnsupportedDavException.kt @@ -8,4 +8,4 @@ package at.bitfire.dav4android.exception -class UnsupportedDavException(message: String) : DavException(message) \ No newline at end of file +class UnsupportedDavException(message: String): DavException(message) \ No newline at end of file diff --git a/src/main/java/at/bitfire/dav4android/property/GetETag.kt b/src/main/java/at/bitfire/dav4android/property/GetETag.kt index 911f451..55d2dc5 100644 --- a/src/main/java/at/bitfire/dav4android/property/GetETag.kt +++ b/src/main/java/at/bitfire/dav4android/property/GetETag.kt @@ -37,7 +37,7 @@ class GetETag( // entity tag is weak (doesn't matter for us) tag = it.substring(2) - tag = QuotedStringUtils.decodeQuotedString(tag) + tag?.let { tag = QuotedStringUtils.decodeQuotedString(it) } } eTag = tag diff --git a/src/main/java/at/bitfire/dav4android/property/ResourceType.kt b/src/main/java/at/bitfire/dav4android/property/ResourceType.kt index 7a44a19..6c2c672 100644 --- a/src/main/java/at/bitfire/dav4android/property/ResourceType.kt +++ b/src/main/java/at/bitfire/dav4android/property/ResourceType.kt @@ -19,11 +19,11 @@ class ResourceType: Property { @JvmField val NAME = Property.Name(XmlUtils.NS_WEBDAV, "resourcetype") - @JvmField val COLLECTION = Property.Name(XmlUtils.NS_WEBDAV, "collection") // WebDAV - @JvmField val PRINCIPAL = Property.Name(XmlUtils.NS_WEBDAV, "principal") // WebDAV ACL - @JvmField val ADDRESSBOOK = Property.Name(XmlUtils.NS_CARDDAV, "addressbook") // CardDAV - @JvmField val CALENDAR = Property.Name(XmlUtils.NS_CALDAV, "calendar") // CalDAV - @JvmField val SUBSCRIBED = Property.Name(XmlUtils.NS_CALENDARSERVER, "subscribed") + val COLLECTION = Property.Name(XmlUtils.NS_WEBDAV, "collection") // WebDAV + val PRINCIPAL = Property.Name(XmlUtils.NS_WEBDAV, "principal") // WebDAV ACL + val ADDRESSBOOK = Property.Name(XmlUtils.NS_CARDDAV, "addressbook") // CardDAV + val CALENDAR = Property.Name(XmlUtils.NS_CALDAV, "calendar") // CalDAV + val SUBSCRIBED = Property.Name(XmlUtils.NS_CALENDARSERVER, "subscribed") } val types = mutableSetOf() diff --git a/src/main/java/at/bitfire/dav4android/property/SupportedReportSet.kt b/src/main/java/at/bitfire/dav4android/property/SupportedReportSet.kt index 2485c3a..e8e5f28 100644 --- a/src/main/java/at/bitfire/dav4android/property/SupportedReportSet.kt +++ b/src/main/java/at/bitfire/dav4android/property/SupportedReportSet.kt @@ -16,9 +16,9 @@ import org.xmlpull.v1.XmlPullParser class SupportedReportSet: Property { companion object { - @JvmField val NAME = Property.Name(XmlUtils.NS_WEBDAV, "supported-report-set") + val NAME = Property.Name(XmlUtils.NS_WEBDAV, "supported-report-set") - @JvmField val SYNC_COLLECTION = "DAV:sync-collection" // collection synchronization (RFC 6578) + val SYNC_COLLECTION = "DAV:sync-collection" // collection synchronization (RFC 6578) } val reports = mutableSetOf() diff --git a/src/test/java/at/bitfire/dav4android/BasicDigestAuthHandlerTest.java b/src/test/java/at/bitfire/dav4android/BasicDigestAuthHandlerTest.java deleted file mode 100644 index dedb6f0..0000000 --- a/src/test/java/at/bitfire/dav4android/BasicDigestAuthHandlerTest.java +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright © Ricki Hirner (bitfire web engineering). - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Public License v3.0 - * which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/gpl.html - */ - -package at.bitfire.dav4android; - -import org.junit.Test; - -import okhttp3.MediaType; -import okhttp3.Protocol; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -public class BasicDigestAuthHandlerTest { - - @Test - public void testBasic() { - BasicDigestAuthHandler authenticator = new BasicDigestAuthHandler(null, "user", "password"); - Request original = new Request.Builder() - .url("http://example.com") - .build(); - Response response = new Response.Builder() - .request(original) - .protocol(Protocol.HTTP_1_1) - .code(401).message("Authentication required") - .header("WWW-Authenticate", "Basic realm=\"WallyWorld\"") - .build(); - Request request = authenticator.authenticateRequest(original, response); - assertEquals("Basic dXNlcjpwYXNzd29yZA==", request.header("Authorization")); - - // special characters: always use UTF-8 (and don't crash on RFC 7617 charset header) - authenticator = new BasicDigestAuthHandler(null, "username", "paßword"); - response = response.newBuilder() - .header("WWW-Authenticate", "Basic realm=\"WallyWorld\",charset=UTF-8") - .build(); - request = authenticator.authenticateRequest(original, response); - assertEquals("Basic dXNlcm5hbWU6cGHDn3dvcmQ=", request.header("Authorization")); - } - - @Test - public void testDigestRFCExample() { - // use cnonce from example - BasicDigestAuthHandler authenticator = new BasicDigestAuthHandler(null, "Mufasa", "Circle Of Life"); - BasicDigestAuthHandler.clientNonce = "0a4f113b"; - BasicDigestAuthHandler.nonceCount.set(1); - - // construct WWW-Authenticate - HttpUtils.AuthScheme authScheme = new HttpUtils.AuthScheme("Digest"); - authScheme.getParams().put("realm", "testrealm@host.com"); - authScheme.getParams().put("qop", "auth"); - authScheme.getParams().put("nonce", "dcd98b7102dd2f0e8b11d0f600bfb0c093"); - authScheme.getParams().put("opaque", "5ccc069c403ebaf9f0171e9517f40e41"); - - Request original = new Request.Builder() - .get() - .url("http://www.nowhere.org/dir/index.html") - .build(); - Request request = authenticator.digestRequest(original, authScheme); - String auth = request.header("Authorization"); - assertTrue(auth.contains("username=\"Mufasa\"")); - assertTrue(auth.contains("realm=\"testrealm@host.com\"")); - assertTrue(auth.contains("nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\"")); - assertTrue(auth.contains("uri=\"/dir/index.html\"")); - assertTrue(auth.contains("qop=auth")); - assertTrue(auth.contains("nc=00000001")); - assertTrue(auth.contains("cnonce=\"0a4f113b\"")); - assertTrue(auth.contains("response=\"6629fae49393a05397450978507c4ef1\"")); - assertTrue(auth.contains("opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"")); - } - - @Test - public void testDigestRealWorldExamples() { - BasicDigestAuthHandler authenticator = new BasicDigestAuthHandler(null, "demo", "demo"); - BasicDigestAuthHandler.clientNonce = "MDI0ZDgxYTNmZDk4MTA1ODM0NDNjNmJjNDllYjQ1ZTI="; - BasicDigestAuthHandler.nonceCount.set(1); - - // example 1 - HttpUtils.AuthScheme authScheme = new HttpUtils.AuthScheme("Digest"); - authScheme.getParams().put("realm", "Group-Office"); - authScheme.getParams().put("qop", "auth"); - authScheme.getParams().put("nonce", "56212407212c8"); - authScheme.getParams().put("opaque", "df58bdff8cf60599c939187d0b5c54de"); - - Request original = new Request.Builder() - .method("PROPFIND", null) - .url("https://demo.group-office.eu/caldav/") - .build(); - Request request = authenticator.digestRequest(original, authScheme); - String auth = request.header("Authorization"); - assertTrue(auth.contains("username=\"demo\"")); - assertTrue(auth.contains("realm=\"Group-Office\"")); - assertTrue(auth.contains("nonce=\"56212407212c8\"")); - assertTrue(auth.contains("uri=\"/caldav/\"")); - assertTrue(auth.contains("cnonce=\"MDI0ZDgxYTNmZDk4MTA1ODM0NDNjNmJjNDllYjQ1ZTI=\"")); - assertTrue(auth.contains("nc=00000001")); - assertTrue(auth.contains("qop=auth")); - assertTrue(auth.contains("response=\"de3b3b194d85ddc62537208c9c3637dc\"")); - assertTrue(auth.contains("opaque=\"df58bdff8cf60599c939187d0b5c54de\"")); - - // example 2 - authenticator = new BasicDigestAuthHandler(null, "test", "test"); - authScheme = new HttpUtils.AuthScheme("digest"); // lower case - authScheme.getParams().put("nonce", "87c4c2aceed9abf30dd68c71"); - authScheme.getParams().put("algorithm", "md5"); // note the (illegal) lower case! - authScheme.getParams().put("opaque", "571609eb7058505d35c7bf7288fbbec4-ODdjNGMyYWNlZWQ5YWJmMzBkZDY4YzcxLDAuMC4wLjAsMTQ0NTM3NzE0Nw=="); - authScheme.getParams().put("realm", "ieddy.ru"); - original = new Request.Builder() - .method("OPTIONS", null) - .url("https://ieddy.ru/") - .build(); - request = authenticator.digestRequest(original, authScheme); - auth = request.header("Authorization"); - assertTrue(auth.contains("algorithm=\"MD5\"")); // some servers require it - assertTrue(auth.contains("username=\"test\"")); - assertTrue(auth.contains("realm=\"ieddy.ru\"")); - assertTrue(auth.contains("nonce=\"87c4c2aceed9abf30dd68c71\"")); - assertTrue(auth.contains("uri=\"/\"")); - assertFalse(auth.contains("cnonce=")); - assertFalse(auth.contains("nc=00000001")); - assertFalse(auth.contains("qop=")); - assertTrue(auth.contains("response=\"d42a39f25f80b0d6907286a960ff9c7d\"")); - assertTrue(auth.contains("opaque=\"571609eb7058505d35c7bf7288fbbec4-ODdjNGMyYWNlZWQ5YWJmMzBkZDY4YzcxLDAuMC4wLjAsMTQ0NTM3NzE0Nw==\"")); - } - - @Test - public void testDigestMD5Sess() { - BasicDigestAuthHandler authenticator = new BasicDigestAuthHandler(null, "admin", "12345"); - BasicDigestAuthHandler.clientNonce = "hxk1lu63b6c7vhk"; - BasicDigestAuthHandler.nonceCount.set(1); - - HttpUtils.AuthScheme authScheme = new HttpUtils.AuthScheme("Digest"); - authScheme.getParams().put("realm", "MD5-sess Example"); - authScheme.getParams().put("qop", "auth"); - authScheme.getParams().put("algorithm", "MD5-sess"); - authScheme.getParams().put("nonce", "dcd98b7102dd2f0e8b11d0f600bfb0c093"); - authScheme.getParams().put("opaque", "5ccc069c403ebaf9f0171e9517f40e41"); - - /* A1 = h("admin:MD5-sess Example:12345"):dcd98b7102dd2f0e8b11d0f600bfb0c093:hxk1lu63b6c7vhk = - 4eaed818bc587129e73b39c8d3e8425a:dcd98b7102dd2f0e8b11d0f600bfb0c093:hxk1lu63b6c7vhk a994ee9d33e2f077d3a6e13e882f6686 - A2 = POST:/plain.txt 1b557703454e1aa1230c5523f54380ed - - h("a994ee9d33e2f077d3a6e13e882f6686:dcd98b7102dd2f0e8b11d0f600bfb0c093:00000001:hxk1lu63b6c7vhk:auth:1b557703454e1aa1230c5523f54380ed") = - af2a72145775cfd08c36ad2676e89446 - */ - - Request original = new Request.Builder() - .method("POST", RequestBody.create(MediaType.parse("text/plain"), "PLAIN TEXT")) - .url("http://example.com/plain.txt") - .build(); - Request request = authenticator.digestRequest(original, authScheme); - String auth = request.header("Authorization"); - assertTrue(auth.contains("username=\"admin\"")); - assertTrue(auth.contains("realm=\"MD5-sess Example\"")); - assertTrue(auth.contains("nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\"")); - assertTrue(auth.contains("uri=\"/plain.txt\"")); - assertTrue(auth.contains("cnonce=\"hxk1lu63b6c7vhk\"")); - assertTrue(auth.contains("nc=00000001")); - assertTrue(auth.contains("qop=auth")); - assertTrue(auth.contains("response=\"af2a72145775cfd08c36ad2676e89446\"")); - assertTrue(auth.contains("opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"")); - } - - @Test - public void testDigestMD5AuthInt() { - BasicDigestAuthHandler authenticator = new BasicDigestAuthHandler(null, "admin", "12435"); - BasicDigestAuthHandler.clientNonce = "hxk1lu63b6c7vhk"; - BasicDigestAuthHandler.nonceCount.set(1); - - HttpUtils.AuthScheme authScheme = new HttpUtils.AuthScheme("Digest"); - authScheme.getParams().put("realm", "AuthInt Example"); - authScheme.getParams().put("qop", "auth-int"); - authScheme.getParams().put("nonce", "367sj3265s5"); - authScheme.getParams().put("opaque", "87aaxcval4gba36"); - - /* A1 = admin:AuthInt Example:12345 380dc3fc1305127cd2aa81ab68ef3f34 - - h("PLAIN TEXT") = 20296edbd4c4275fb416b64e4be752f9 - A2 = POST:/plain.txt:20296edbd4c4275fb416b64e4be752f9 a71c4c86e18b3993ffc98c6e426fe4b0 - - h(380dc3fc1305127cd2aa81ab68ef3f34:367sj3265s5:00000001:hxk1lu63b6c7vhk:auth-int:a71c4c86e18b3993ffc98c6e426fe4b0) = - 81d07cb3b8d412b34144164124c970cb - */ - - Request original = new Request.Builder() - .method("POST", RequestBody.create(MediaType.parse("text/plain"), "PLAIN TEXT")) - .url("http://example.com/plain.txt") - .build(); - Request request = authenticator.digestRequest(original, authScheme); - String auth = request.header("Authorization"); - assertTrue(auth.contains("username=\"admin\"")); - assertTrue(auth.contains("realm=\"AuthInt Example\"")); - assertTrue(auth.contains("nonce=\"367sj3265s5\"")); - assertTrue(auth.contains("uri=\"/plain.txt\"")); - assertTrue(auth.contains("cnonce=\"hxk1lu63b6c7vhk\"")); - assertTrue(auth.contains("nc=00000001")); - assertTrue(auth.contains("qop=auth-int")); - assertTrue(auth.contains("response=\"5ab6822b9d906cc711760a7783b28dca\"")); - assertTrue(auth.contains("opaque=\"87aaxcval4gba36\"")); - } - - @Test - public void testDigestLegacy() { - BasicDigestAuthHandler authenticator = new BasicDigestAuthHandler(null, "Mufasa", "CircleOfLife"); - - // construct WWW-Authenticate - HttpUtils.AuthScheme authScheme = new HttpUtils.AuthScheme("Digest"); - authScheme.getParams().put("realm", "testrealm@host.com"); - authScheme.getParams().put("nonce", "dcd98b7102dd2f0e8b11d0f600bfb0c093"); - authScheme.getParams().put("opaque", "5ccc069c403ebaf9f0171e9517f40e41"); - - Request original = new Request.Builder() - .get() - .url("http://www.nowhere.org/dir/index.html") - .build(); - Request request = authenticator.digestRequest(original, authScheme); - String auth = request.header("Authorization"); - assertTrue(auth.contains("username=\"Mufasa\"")); - assertTrue(auth.contains("realm=\"testrealm@host.com\"")); - assertTrue(auth.contains("nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\"")); - assertTrue(auth.contains("uri=\"/dir/index.html\"")); - assertFalse(auth.contains("qop=")); - assertFalse(auth.contains("nc=")); - assertFalse(auth.contains("cnonce=")); - assertTrue(auth.contains("response=\"1949323746fe6a43ef61f9606e7febea\"")); - assertTrue(auth.contains("opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"")); - } - - @Test - public void testIncompleteAuthenticationRequests() { - BasicDigestAuthHandler authenticator = new BasicDigestAuthHandler(null, "demo", "demo"); - - Request original = new Request.Builder() - .get() - .url("http://www.nowhere.org/dir/index.html") - .build(); - - HttpUtils.AuthScheme authScheme = new HttpUtils.AuthScheme("Digest"); - assertNull(authenticator.digestRequest(original, authScheme)); - - authScheme.getParams().put("realm", "Group-Office"); - assertNull(authenticator.digestRequest(original, authScheme)); - - authScheme.getParams().put("qop", "auth"); - assertNull(authenticator.digestRequest(original, authScheme)); - - authScheme.getParams().put("nonce", "56212407212c8"); - assertNotNull(authenticator.digestRequest(original, authScheme)); - } - - @Test - public void testAuthenticateNull() { - BasicDigestAuthHandler authenticator = new BasicDigestAuthHandler(null, "demo", "demo"); - // must not crash (route may be null) - Request request = new Request.Builder() - .get() - .url("http://example.com") - .build(); - Response response = new Response.Builder() - .request(request) - .protocol(Protocol.HTTP_2) - .code(200).message("OK") - .build(); - authenticator.authenticate(null, response); - } - -} diff --git a/src/test/java/at/bitfire/dav4android/BasicDigestAuthHandlerTest.kt b/src/test/java/at/bitfire/dav4android/BasicDigestAuthHandlerTest.kt new file mode 100644 index 0000000..e240599 --- /dev/null +++ b/src/test/java/at/bitfire/dav4android/BasicDigestAuthHandlerTest.kt @@ -0,0 +1,268 @@ +/* + * Copyright © Ricki Hirner (bitfire web engineering). + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + */ + +package at.bitfire.dav4android + +import okhttp3.* +import okhttp3.Response.Builder +import org.junit.Assert.* +import org.junit.Test + +class BasicDigestAuthHandlerTest { + + @Test + fun testBasic() { + var authenticator = BasicDigestAuthHandler(null, "user", "password") + val original = Request.Builder() + .url("http://example.com") + .build() + var response = Builder() + .request(original) + .protocol(Protocol.HTTP_1_1) + .code(401).message("Authentication required") + .header("WWW-Authenticate", "Basic realm=\"WallyWorld\"") + .build() + var request = authenticator.authenticateRequest(original, response) + assertEquals("Basic dXNlcjpwYXNzd29yZA==", request!!.header("Authorization")) + + // special characters: always use UTF-8 (and don't crash on RFC 7617 charset header) + authenticator = BasicDigestAuthHandler(null, "username", "paßword") + response = response.newBuilder() + .header("WWW-Authenticate", "Basic realm=\"WallyWorld\",charset=UTF-8") + .build() + request = authenticator.authenticateRequest(original, response) + assertEquals("Basic dXNlcm5hbWU6cGHDn3dvcmQ=", request!!.header("Authorization")) + } + + @Test + fun testDigestRFCExample() { + // use cnonce from example + val authenticator = BasicDigestAuthHandler(null, "Mufasa", "Circle Of Life") + BasicDigestAuthHandler.clientNonce = "0a4f113b" + BasicDigestAuthHandler.nonceCount.set(1) + + // construct WWW-Authenticate + val authScheme = HttpUtils.AuthScheme("Digest") + authScheme.params["realm"] = "testrealm@host.com" + authScheme.params["qop"] = "auth" + authScheme.params["nonce"] = "dcd98b7102dd2f0e8b11d0f600bfb0c093" + authScheme.params["opaque"] = "5ccc069c403ebaf9f0171e9517f40e41" + + val original = Request.Builder() + .get() + .url("http://www.nowhere.org/dir/index.html") + .build() + val request = authenticator.digestRequest(original, authScheme) + val auth = request!!.header("Authorization") + assertTrue(auth!!.contains("username=\"Mufasa\"")) + assertTrue(auth.contains("realm=\"testrealm@host.com\"")) + assertTrue(auth.contains("nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\"")) + assertTrue(auth.contains("uri=\"/dir/index.html\"")) + assertTrue(auth.contains("qop=auth")) + assertTrue(auth.contains("nc=00000001")) + assertTrue(auth.contains("cnonce=\"0a4f113b\"")) + assertTrue(auth.contains("response=\"6629fae49393a05397450978507c4ef1\"")) + assertTrue(auth.contains("opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"")) + } + + @Test + fun testDigestRealWorldExamples() { + var authenticator = BasicDigestAuthHandler(null, "demo", "demo") + BasicDigestAuthHandler.clientNonce = "MDI0ZDgxYTNmZDk4MTA1ODM0NDNjNmJjNDllYjQ1ZTI=" + BasicDigestAuthHandler.nonceCount.set(1) + + // example 1 + var authScheme = HttpUtils.AuthScheme("Digest") + authScheme.params["realm"] = "Group-Office" + authScheme.params["qop"] = "auth" + authScheme.params["nonce"] = "56212407212c8" + authScheme.params["opaque"] = "df58bdff8cf60599c939187d0b5c54de" + + var original = Request.Builder() + .method("PROPFIND", null) + .url("https://demo.group-office.eu/caldav/") + .build() + var request = authenticator.digestRequest(original, authScheme) + var auth = request!!.header("Authorization") + assertTrue(auth!!.contains("username=\"demo\"")) + assertTrue(auth.contains("realm=\"Group-Office\"")) + assertTrue(auth.contains("nonce=\"56212407212c8\"")) + assertTrue(auth.contains("uri=\"/caldav/\"")) + assertTrue(auth.contains("cnonce=\"MDI0ZDgxYTNmZDk4MTA1ODM0NDNjNmJjNDllYjQ1ZTI=\"")) + assertTrue(auth.contains("nc=00000001")) + assertTrue(auth.contains("qop=auth")) + assertTrue(auth.contains("response=\"de3b3b194d85ddc62537208c9c3637dc\"")) + assertTrue(auth.contains("opaque=\"df58bdff8cf60599c939187d0b5c54de\"")) + + // example 2 + authenticator = BasicDigestAuthHandler(null, "test", "test") + authScheme = HttpUtils.AuthScheme("digest") // lower case + authScheme.params["nonce"] = "87c4c2aceed9abf30dd68c71" + authScheme.params["algorithm"] = "md5" // note the (illegal) lower case! + authScheme.params["opaque"] = "571609eb7058505d35c7bf7288fbbec4-ODdjNGMyYWNlZWQ5YWJmMzBkZDY4YzcxLDAuMC4wLjAsMTQ0NTM3NzE0Nw==" + authScheme.params["realm"] = "ieddy.ru" + original = Request.Builder() + .method("OPTIONS", null) + .url("https://ieddy.ru/") + .build() + request = authenticator.digestRequest(original, authScheme) + auth = request!!.header("Authorization") + assertTrue(auth!!.contains("algorithm=\"MD5\"")) // some servers require it + assertTrue(auth.contains("username=\"test\"")) + assertTrue(auth.contains("realm=\"ieddy.ru\"")) + assertTrue(auth.contains("nonce=\"87c4c2aceed9abf30dd68c71\"")) + assertTrue(auth.contains("uri=\"/\"")) + assertFalse(auth.contains("cnonce=")) + assertFalse(auth.contains("nc=00000001")) + assertFalse(auth.contains("qop=")) + assertTrue(auth.contains("response=\"d42a39f25f80b0d6907286a960ff9c7d\"")) + assertTrue(auth.contains("opaque=\"571609eb7058505d35c7bf7288fbbec4-ODdjNGMyYWNlZWQ5YWJmMzBkZDY4YzcxLDAuMC4wLjAsMTQ0NTM3NzE0Nw==\"")) + } + + @Test + fun testDigestMD5Sess() { + val authenticator = BasicDigestAuthHandler(null, "admin", "12345") + BasicDigestAuthHandler.clientNonce = "hxk1lu63b6c7vhk" + BasicDigestAuthHandler.nonceCount.set(1) + + val authScheme = HttpUtils.AuthScheme("Digest") + authScheme.params["realm"] = "MD5-sess Example" + authScheme.params["qop"] = "auth" + authScheme.params["algorithm"] = "MD5-sess" + authScheme.params["nonce"] = "dcd98b7102dd2f0e8b11d0f600bfb0c093" + authScheme.params["opaque"] = "5ccc069c403ebaf9f0171e9517f40e41" + + /* A1 = h("admin:MD5-sess Example:12345"):dcd98b7102dd2f0e8b11d0f600bfb0c093:hxk1lu63b6c7vhk = + 4eaed818bc587129e73b39c8d3e8425a:dcd98b7102dd2f0e8b11d0f600bfb0c093:hxk1lu63b6c7vhk a994ee9d33e2f077d3a6e13e882f6686 + A2 = POST:/plain.txt 1b557703454e1aa1230c5523f54380ed + + h("a994ee9d33e2f077d3a6e13e882f6686:dcd98b7102dd2f0e8b11d0f600bfb0c093:00000001:hxk1lu63b6c7vhk:auth:1b557703454e1aa1230c5523f54380ed") = + af2a72145775cfd08c36ad2676e89446 + */ + + val original = Request.Builder() + .method("POST", RequestBody.create(MediaType.parse("text/plain"), "PLAIN TEXT")) + .url("http://example.com/plain.txt") + .build() + val request = authenticator.digestRequest(original, authScheme) + val auth = request!!.header("Authorization") + assertTrue(auth!!.contains("username=\"admin\"")) + assertTrue(auth.contains("realm=\"MD5-sess Example\"")) + assertTrue(auth.contains("nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\"")) + assertTrue(auth.contains("uri=\"/plain.txt\"")) + assertTrue(auth.contains("cnonce=\"hxk1lu63b6c7vhk\"")) + assertTrue(auth.contains("nc=00000001")) + assertTrue(auth.contains("qop=auth")) + assertTrue(auth.contains("response=\"af2a72145775cfd08c36ad2676e89446\"")) + assertTrue(auth.contains("opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"")) + } + + @Test + fun testDigestMD5AuthInt() { + val authenticator = BasicDigestAuthHandler(null, "admin", "12435") + BasicDigestAuthHandler.clientNonce = "hxk1lu63b6c7vhk" + BasicDigestAuthHandler.nonceCount.set(1) + + val authScheme = HttpUtils.AuthScheme("Digest") + authScheme.params["realm"] = "AuthInt Example" + authScheme.params["qop"] = "auth-int" + authScheme.params["nonce"] = "367sj3265s5" + authScheme.params["opaque"] = "87aaxcval4gba36" + + /* A1 = admin:AuthInt Example:12345 380dc3fc1305127cd2aa81ab68ef3f34 + + h("PLAIN TEXT") = 20296edbd4c4275fb416b64e4be752f9 + A2 = POST:/plain.txt:20296edbd4c4275fb416b64e4be752f9 a71c4c86e18b3993ffc98c6e426fe4b0 + + h(380dc3fc1305127cd2aa81ab68ef3f34:367sj3265s5:00000001:hxk1lu63b6c7vhk:auth-int:a71c4c86e18b3993ffc98c6e426fe4b0) = + 81d07cb3b8d412b34144164124c970cb + */ + + val original = Request.Builder() + .method("POST", RequestBody.create(MediaType.parse("text/plain"), "PLAIN TEXT")) + .url("http://example.com/plain.txt") + .build() + val request = authenticator.digestRequest(original, authScheme) + val auth = request!!.header("Authorization") + assertTrue(auth!!.contains("username=\"admin\"")) + assertTrue(auth.contains("realm=\"AuthInt Example\"")) + assertTrue(auth.contains("nonce=\"367sj3265s5\"")) + assertTrue(auth.contains("uri=\"/plain.txt\"")) + assertTrue(auth.contains("cnonce=\"hxk1lu63b6c7vhk\"")) + assertTrue(auth.contains("nc=00000001")) + assertTrue(auth.contains("qop=auth-int")) + assertTrue(auth.contains("response=\"5ab6822b9d906cc711760a7783b28dca\"")) + assertTrue(auth.contains("opaque=\"87aaxcval4gba36\"")) + } + + @Test + fun testDigestLegacy() { + val authenticator = BasicDigestAuthHandler(null, "Mufasa", "CircleOfLife") + + // construct WWW-Authenticate + val authScheme = HttpUtils.AuthScheme("Digest") + authScheme.params["realm"] = "testrealm@host.com" + authScheme.params["nonce"] = "dcd98b7102dd2f0e8b11d0f600bfb0c093" + authScheme.params["opaque"] = "5ccc069c403ebaf9f0171e9517f40e41" + + val original = Request.Builder() + .get() + .url("http://www.nowhere.org/dir/index.html") + .build() + val request = authenticator.digestRequest(original, authScheme) + val auth = request!!.header("Authorization") + assertTrue(auth!!.contains("username=\"Mufasa\"")) + assertTrue(auth.contains("realm=\"testrealm@host.com\"")) + assertTrue(auth.contains("nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\"")) + assertTrue(auth.contains("uri=\"/dir/index.html\"")) + assertFalse(auth.contains("qop=")) + assertFalse(auth.contains("nc=")) + assertFalse(auth.contains("cnonce=")) + assertTrue(auth.contains("response=\"1949323746fe6a43ef61f9606e7febea\"")) + assertTrue(auth.contains("opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"")) + } + + @Test + fun testIncompleteAuthenticationRequests() { + val authenticator = BasicDigestAuthHandler(null, "demo", "demo") + + val original = Request.Builder() + .get() + .url("http://www.nowhere.org/dir/index.html") + .build() + + val authScheme = HttpUtils.AuthScheme("Digest") + assertNull(authenticator.digestRequest(original, authScheme)) + + authScheme.params["realm"] = "Group-Office" + assertNull(authenticator.digestRequest(original, authScheme)) + + authScheme.params["qop"] = "auth" + assertNull(authenticator.digestRequest(original, authScheme)) + + authScheme.params["nonce"] = "56212407212c8" + assertNotNull(authenticator.digestRequest(original, authScheme)) + } + + @Test + fun testAuthenticateNull() { + val authenticator = BasicDigestAuthHandler(null, "demo", "demo") + // must not crash (route may be null) + val request = Request.Builder() + .get() + .url("http://example.com") + .build() + val response = Response.Builder() + .request(request) + .protocol(Protocol.HTTP_2) + .code(200).message("OK") + .build() + authenticator.authenticate(null, response) + } + +} diff --git a/src/test/java/at/bitfire/dav4android/HttpUtilsTest.java b/src/test/java/at/bitfire/dav4android/HttpUtilsTest.java deleted file mode 100644 index 5b01108..0000000 --- a/src/test/java/at/bitfire/dav4android/HttpUtilsTest.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright © Ricki Hirner (bitfire web engineering). - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Public License v3.0 - * which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/gpl.html - */ - -package at.bitfire.dav4android; - -import org.junit.Test; - -import java.util.Arrays; -import java.util.List; - -import static org.junit.Assert.assertEquals; - -public class HttpUtilsTest { - - @Test - public void testParseWwwAuthenticate() { - // two schemes: one without param (illegal!), second with two params - List schemes = HttpUtils.parseWwwAuthenticate(Arrays.asList(new String[]{ " UnknownWithoutParam, Unknown WithParam1=\"a\", Param2 " })); - assertEquals(2, schemes.size()); - assertEquals("UnknownWithoutParam", schemes.get(0).getName()); - assertEquals(0, schemes.get(0).getParams().size()); - assertEquals(0, schemes.get(0).getUnnamedParams().size()); - - assertEquals("Unknown", schemes.get(1).getName()); - assertEquals(1, schemes.get(1).getParams().size()); - assertEquals("a", schemes.get(1).getParams().get("withparam1")); - assertEquals(1, schemes.get(1).getParams().size()); - assertEquals(1, schemes.get(1).getUnnamedParams().size()); - assertEquals("Param2", schemes.get(1).getUnnamedParams().get(0)); - - // parameters with quoted strings with commas - schemes = HttpUtils.parseWwwAuthenticate(Arrays.asList(new String[]{ "X-MyScheme param1, param2=\"a,\\\"b\\\",c\", MyOtherScheme paramA" })); - assertEquals(2, schemes.size()); - assertEquals("X-MyScheme", schemes.get(0).getName()); - assertEquals(1, schemes.get(0).getParams().size()); - assertEquals("a,\"b\",c", schemes.get(0).getParams().get("param2")); - assertEquals(1, schemes.get(0).getUnnamedParams().size()); - assertEquals("param1", schemes.get(0).getUnnamedParams().get(0)); - - assertEquals("MyOtherScheme", schemes.get(1).getName()); - assertEquals(0, schemes.get(1).getParams().size()); - assertEquals(1, schemes.get(1).getUnnamedParams().size()); - assertEquals("paramA", schemes.get(1).getUnnamedParams().get(0)); - - - /*** REAL WORLD EXAMPLES ***/ - - // Basic auth - schemes = HttpUtils.parseWwwAuthenticate(Arrays.asList(new String[]{ "Basic realm=\"test\"" })); - assertEquals(1, schemes.size()); - HttpUtils.AuthScheme scheme = schemes.get(0); - assertEquals("Basic", scheme.getName()); - assertEquals(1, scheme.getParams().size()); - assertEquals("test", scheme.getParams().get("realm")); - assertEquals(0, scheme.getUnnamedParams().size()); - - // Basic and Digest auth in one line - schemes = HttpUtils.parseWwwAuthenticate(Arrays.asList(new String[]{ "Basic realm=\"testrealm@host.com\", Digest realm=\"testrealm@host.com\", qop=\"auth,auth-int\", nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"" })); - assertEquals(2, schemes.size()); - scheme = schemes.get(0); - assertEquals("Basic", scheme.getName()); - assertEquals(1, scheme.getParams().size()); - assertEquals("testrealm@host.com", scheme.getParams().get("realm")); - assertEquals(0, scheme.getUnnamedParams().size()); - - scheme = schemes.get(1); - assertEquals("Digest", scheme.getName()); - assertEquals(4, scheme.getParams().size()); - assertEquals("testrealm@host.com", scheme.getParams().get("realm")); - assertEquals("auth,auth-int", scheme.getParams().get("qop")); - assertEquals("dcd98b7102dd2f0e8b11d0f600bfb0c093", scheme.getParams().get("nonce")); - assertEquals("5ccc069c403ebaf9f0171e9517f40e41", scheme.getParams().get("opaque")); - assertEquals(0, scheme.getUnnamedParams().size()); - - // Negotiate (RFC 4559) - schemes = HttpUtils.parseWwwAuthenticate(Arrays.asList(new String[]{ "Negotiate" })); - assertEquals(1, schemes.size()); - scheme = schemes.get(0); - assertEquals("Negotiate", scheme.getName()); - assertEquals(0, scheme.getParams().size()); - assertEquals(0, scheme.getUnnamedParams().size()); - - schemes = HttpUtils.parseWwwAuthenticate(Arrays.asList(new String[]{ "Negotiate a87421000492aa874209af8bc028" })); - assertEquals(1, schemes.size()); - scheme = schemes.get(0); - assertEquals("Negotiate", scheme.getName()); - assertEquals(0, scheme.getParams().size()); - assertEquals(1, scheme.getUnnamedParams().size()); - assertEquals("a87421000492aa874209af8bc028", scheme.getUnnamedParams().get(0)); - - // NTLM, see https://msdn.microsoft.com/en-us/library/dd944123%28v=office.12%29.aspx - schemes = HttpUtils.parseWwwAuthenticate(Arrays.asList(new String[]{ - "NTLM realm=\"SIP Communications Service\", targetname=\"server.contoso.com\", version=3", - "Kerberos realm=\"SIP Communications Service\", targetname=\"sip/server.contoso.com\", version=3" - })); - assertEquals(2, schemes.size()); - scheme = schemes.get(0); - assertEquals("NTLM", scheme.getName()); - assertEquals(3, scheme.getParams().size()); - assertEquals("SIP Communications Service", scheme.getParams().get("realm")); - assertEquals("server.contoso.com", scheme.getParams().get("targetname")); - assertEquals("3", scheme.getParams().get("version")); - assertEquals(0, scheme.getUnnamedParams().size()); - - scheme = schemes.get(1); - assertEquals("Kerberos", scheme.getName()); - assertEquals(3, scheme.getParams().size()); - assertEquals("SIP Communications Service", scheme.getParams().get("realm")); - assertEquals("sip/server.contoso.com", scheme.getParams().get("targetname")); - assertEquals("3", scheme.getParams().get("version")); - assertEquals(0, scheme.getUnnamedParams().size()); - - // https://issues.apache.org/jira/browse/HTTPCLIENT-1489 - schemes = HttpUtils.parseWwwAuthenticate(Arrays.asList(new String[]{ "X-MobileMe-AuthToken realm=\"Newcastle\", Basic realm=\"Newcastle\"" })); - assertEquals(2, schemes.size()); - scheme = schemes.get(0); - assertEquals("X-MobileMe-AuthToken", scheme.getName()); - assertEquals(1, scheme.getParams().size()); - assertEquals("Newcastle", scheme.getParams().get("realm")); - assertEquals(0, scheme.getUnnamedParams().size()); - - scheme = schemes.get(1); - assertEquals("Basic", scheme.getName()); - assertEquals(1, scheme.getParams().size()); - assertEquals("Newcastle", scheme.getParams().get("realm")); - assertEquals(0, scheme.getUnnamedParams().size()); - - // Contacts and Calendar Server example; space in second token! - schemes = HttpUtils.parseWwwAuthenticate(Arrays.asList(new String[]{ "digest nonce=\"785592012006934833760823299624355448128925071071026584347\", realm=\"Test Realm\", algorithm=\"md5\"" })); - assertEquals(1, schemes.size()); - scheme = schemes.get(0); - assertEquals("digest", scheme.getName()); - assertEquals(3, scheme.getParams().size()); - assertEquals("785592012006934833760823299624355448128925071071026584347", scheme.getParams().get("nonce")); - assertEquals("Test Realm", scheme.getParams().get("realm")); - assertEquals("md5", scheme.getParams().get("algorithm")); - } - -} diff --git a/src/test/java/at/bitfire/dav4android/HttpUtilsTest.kt b/src/test/java/at/bitfire/dav4android/HttpUtilsTest.kt new file mode 100644 index 0000000..77ee471 --- /dev/null +++ b/src/test/java/at/bitfire/dav4android/HttpUtilsTest.kt @@ -0,0 +1,140 @@ +/* + * Copyright © Ricki Hirner (bitfire web engineering). + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + */ + +package at.bitfire.dav4android + +import org.junit.Assert.assertEquals +import org.junit.Test + +class HttpUtilsTest { + + @Test + fun testParseWwwAuthenticate() { + // two schemes: one without param (illegal!), second with two params + var schemes = HttpUtils.parseWwwAuthenticate(listOf(" UnknownWithoutParam, Unknown WithParam1=\"a\", Param2 ")) + assertEquals(2, schemes.size) + assertEquals("UnknownWithoutParam", schemes.first().name) + assertEquals(0, schemes.first().params.size) + assertEquals(0, schemes.first().unnamedParams.size) + + assertEquals("Unknown", schemes[1].name) + assertEquals(1, schemes[1].params.size) + assertEquals("a", schemes[1].params["withparam1"]) + assertEquals(1, schemes[1].params.size) + assertEquals(1, schemes[1].unnamedParams.size) + assertEquals("Param2", schemes[1].unnamedParams.first) + + // parameters with quoted strings with commas + schemes = HttpUtils.parseWwwAuthenticate(listOf("X-MyScheme param1, param2=\"a,\\\"b\\\",c\", MyOtherScheme paramA")) + assertEquals(2, schemes.size) + assertEquals("X-MyScheme", schemes.first().name) + assertEquals(1, schemes.first().params.size) + assertEquals("a,\"b\",c", schemes.first().params["param2"]) + assertEquals(1, schemes.first().unnamedParams.size) + assertEquals("param1", schemes.first().unnamedParams.first) + + assertEquals("MyOtherScheme", schemes[1].name) + assertEquals(0, schemes[1].params.size) + assertEquals(1, schemes[1].unnamedParams.size) + assertEquals("paramA", schemes[1].unnamedParams.first) + + + /*** REAL WORLD EXAMPLES ***/ + + // Basic auth + schemes = HttpUtils.parseWwwAuthenticate(listOf("Basic realm=\"test\"")) + assertEquals(1, schemes.size) + var scheme = schemes.first() + assertEquals("Basic", scheme.name) + assertEquals(1, scheme.params.size) + assertEquals("test", scheme.params["realm"]) + assertEquals(0, scheme.unnamedParams.size) + + // Basic and Digest auth in one line + schemes = HttpUtils.parseWwwAuthenticate(listOf("Basic realm=\"testrealm@host.com\", Digest realm=\"testrealm@host.com\", qop=\"auth,auth-int\", nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"")) + assertEquals(2, schemes.size) + scheme = schemes.first() + assertEquals("Basic", scheme.name) + assertEquals(1, scheme.params.size) + assertEquals("testrealm@host.com", scheme.params["realm"]) + assertEquals(0, scheme.unnamedParams.size) + + scheme = schemes[1] + assertEquals("Digest", scheme.name) + assertEquals(4, scheme.params.size) + assertEquals("testrealm@host.com", scheme.params["realm"]) + assertEquals("auth,auth-int", scheme.params["qop"]) + assertEquals("dcd98b7102dd2f0e8b11d0f600bfb0c093", scheme.params["nonce"]) + assertEquals("5ccc069c403ebaf9f0171e9517f40e41", scheme.params["opaque"]) + assertEquals(0, scheme.unnamedParams.size) + + // Negotiate (RFC 4559) + schemes = HttpUtils.parseWwwAuthenticate(listOf("Negotiate")) + assertEquals(1, schemes.size) + scheme = schemes.first() + assertEquals("Negotiate", scheme.name) + assertEquals(0, scheme.params.size) + assertEquals(0, scheme.unnamedParams.size) + + schemes = HttpUtils.parseWwwAuthenticate(listOf("Negotiate a87421000492aa874209af8bc028")) + assertEquals(1, schemes.size) + scheme = schemes.first() + assertEquals("Negotiate", scheme.name) + assertEquals(0, scheme.params.size) + assertEquals(1, scheme.unnamedParams.size) + assertEquals("a87421000492aa874209af8bc028", scheme.unnamedParams.first) + + // NTLM, see https://msdn.microsoft.com/en-us/library/dd944123%28v=office.12%29.aspx + schemes = HttpUtils.parseWwwAuthenticate(listOf( + "NTLM realm=\"SIP Communications Service\", targetname=\"server.contoso.com\", version=3", + "Kerberos realm=\"SIP Communications Service\", targetname=\"sip/server.contoso.com\", version=3" + )) + assertEquals(2, schemes.size) + scheme = schemes.first() + assertEquals("NTLM", scheme.name) + assertEquals(3, scheme.params.size) + assertEquals("SIP Communications Service", scheme.params["realm"]) + assertEquals("server.contoso.com", scheme.params["targetname"]) + assertEquals("3", scheme.params["version"]) + assertEquals(0, scheme.unnamedParams.size) + + scheme = schemes[1] + assertEquals("Kerberos", scheme.name) + assertEquals(3, scheme.params.size) + assertEquals("SIP Communications Service", scheme.params["realm"]) + assertEquals("sip/server.contoso.com", scheme.params["targetname"]) + assertEquals("3", scheme.params["version"]) + assertEquals(0, scheme.unnamedParams.size) + + // https://issues.apache.org/jira/browse/HTTPCLIENT-1489 + schemes = HttpUtils.parseWwwAuthenticate(listOf("X-MobileMe-AuthToken realm=\"Newcastle\", Basic realm=\"Newcastle\"")) + assertEquals(2, schemes.size) + scheme = schemes.first() + assertEquals("X-MobileMe-AuthToken", scheme.name) + assertEquals(1, scheme.params.size) + assertEquals("Newcastle", scheme.params["realm"]) + assertEquals(0, scheme.unnamedParams.size) + + scheme = schemes[1] + assertEquals("Basic", scheme.name) + assertEquals(1, scheme.params.size) + assertEquals("Newcastle", scheme.params["realm"]) + assertEquals(0, scheme.unnamedParams.size) + + // Contacts and Calendar Server example; space in second token! + schemes = HttpUtils.parseWwwAuthenticate(listOf("digest nonce=\"785592012006934833760823299624355448128925071071026584347\", realm=\"Test Realm\", algorithm=\"md5\"")) + assertEquals(1, schemes.size) + scheme = schemes.first() + assertEquals("digest", scheme.name) + assertEquals(3, scheme.params.size) + assertEquals("785592012006934833760823299624355448128925071071026584347", scheme.params["nonce"]) + assertEquals("Test Realm", scheme.params["realm"]) + assertEquals("md5", scheme.params["algorithm"]) + } + +} diff --git a/src/test/java/at/bitfire/dav4android/QuotedStringUtilsTest.java b/src/test/java/at/bitfire/dav4android/QuotedStringUtilsTest.kt similarity index 68% rename from src/test/java/at/bitfire/dav4android/QuotedStringUtilsTest.java rename to src/test/java/at/bitfire/dav4android/QuotedStringUtilsTest.kt index b6e966e..03cdd5f 100644 --- a/src/test/java/at/bitfire/dav4android/QuotedStringUtilsTest.java +++ b/src/test/java/at/bitfire/dav4android/QuotedStringUtilsTest.kt @@ -6,34 +6,31 @@ * http://www.gnu.org/licenses/gpl.html */ -package at.bitfire.dav4android; +package at.bitfire.dav4android -import org.junit.Test; +import org.junit.Assert.assertEquals +import org.junit.Test -import static org.junit.Assert.assertEquals; - -public class QuotedStringUtilsTest { +class QuotedStringUtilsTest { @Test - public void testAsQuotedString() { - assertEquals(null, QuotedStringUtils.asQuotedString(null)); - assertEquals("\"\"", QuotedStringUtils.asQuotedString("")); - assertEquals("\"\\\"\"", QuotedStringUtils.asQuotedString("\"")); - assertEquals("\"\\\\\"", QuotedStringUtils.asQuotedString("\\")); + fun testAsQuotedString() { + assertEquals("\"\"", QuotedStringUtils.asQuotedString("")) + assertEquals("\"\\\"\"", QuotedStringUtils.asQuotedString("\"")) + assertEquals("\"\\\\\"", QuotedStringUtils.asQuotedString("\\")) } - public void testDecodeQuotedString() { - assertEquals(null, QuotedStringUtils.decodeQuotedString(null)); - assertEquals("\"", QuotedStringUtils.decodeQuotedString("\"")); - assertEquals("\\", QuotedStringUtils.decodeQuotedString("\"\\\"")); - assertEquals("\"test", QuotedStringUtils.decodeQuotedString("\"test")); - assertEquals("test", QuotedStringUtils.decodeQuotedString("test")); - assertEquals("", QuotedStringUtils.decodeQuotedString("\"\"")); - assertEquals("test", QuotedStringUtils.decodeQuotedString("\"test\"")); - assertEquals("test\\", QuotedStringUtils.decodeQuotedString("\"test\\\"")); - assertEquals("test", QuotedStringUtils.decodeQuotedString("\"t\\e\\st\"")); - assertEquals("12\"34", QuotedStringUtils.decodeQuotedString("\"12\\\"34\"")); - assertEquals("1234\"", QuotedStringUtils.decodeQuotedString("\"1234\\\"\"")); + fun testDecodeQuotedString() { + assertEquals("\"", QuotedStringUtils.decodeQuotedString("\"")) + assertEquals("\\", QuotedStringUtils.decodeQuotedString("\"\\\"")) + assertEquals("\"test", QuotedStringUtils.decodeQuotedString("\"test")) + assertEquals("test", QuotedStringUtils.decodeQuotedString("test")) + assertEquals("", QuotedStringUtils.decodeQuotedString("\"\"")) + assertEquals("test", QuotedStringUtils.decodeQuotedString("\"test\"")) + assertEquals("test\\", QuotedStringUtils.decodeQuotedString("\"test\\\"")) + assertEquals("test", QuotedStringUtils.decodeQuotedString("\"t\\e\\st\"")) + assertEquals("12\"34", QuotedStringUtils.decodeQuotedString("\"12\\\"34\"")) + assertEquals("1234\"", QuotedStringUtils.decodeQuotedString("\"1234\\\"\"")) } } diff --git a/src/test/java/at/bitfire/dav4android/UrlUtilsTest.java b/src/test/java/at/bitfire/dav4android/UrlUtilsTest.java deleted file mode 100644 index 2089550..0000000 --- a/src/test/java/at/bitfire/dav4android/UrlUtilsTest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright © Ricki Hirner (bitfire web engineering). - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Public License v3.0 - * which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/gpl.html - */ - -package at.bitfire.dav4android; - -import org.junit.Test; - -import okhttp3.HttpUrl; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -public class UrlUtilsTest { - - @Test - public void testEquals() { - assertTrue(UrlUtils.equals(HttpUrl.parse("http://host/resource"), HttpUrl.parse("http://host/resource"))); - assertTrue(UrlUtils.equals(HttpUrl.parse("http://host:80/resource"), HttpUrl.parse("http://host/resource"))); - assertTrue(UrlUtils.equals(HttpUrl.parse("https://HOST:443/resource"), HttpUrl.parse("https://host/resource"))); - assertTrue(UrlUtils.equals(HttpUrl.parse("https://host:443/my@dav/"), HttpUrl.parse("https://host/my%40dav/"))); - - assertFalse(UrlUtils.equals(HttpUrl.parse("http://host/resource"), HttpUrl.parse("http://host/resource/"))); - assertFalse(UrlUtils.equals(HttpUrl.parse("http://host/resource"), HttpUrl.parse("http://host:81/resource"))); - } - - @Test - public void testHostToDomain() { - assertNull(UrlUtils.hostToDomain(null)); - assertEquals("", UrlUtils.hostToDomain(".")); - assertEquals("com", UrlUtils.hostToDomain("com")); - assertEquals("com", UrlUtils.hostToDomain("com.")); - assertEquals("example.com", UrlUtils.hostToDomain("example.com")); - assertEquals("example.com", UrlUtils.hostToDomain("example.com.")); - assertEquals("example.com", UrlUtils.hostToDomain(".example.com")); - assertEquals("example.com", UrlUtils.hostToDomain(".example.com.")); - assertEquals("example.com", UrlUtils.hostToDomain("host.example.com")); - assertEquals("example.com", UrlUtils.hostToDomain("host.example.com.")); - assertEquals("example.com", UrlUtils.hostToDomain("sub.host.example.com")); - assertEquals("example.com", UrlUtils.hostToDomain("sub.host.example.com.")); - } - - @Test - public void testOmitTrailingSlash() { - assertEquals(HttpUrl.parse("http://host/resource"), UrlUtils.omitTrailingSlash(HttpUrl.parse("http://host/resource"))); - assertEquals(HttpUrl.parse("http://host/resource"), UrlUtils.omitTrailingSlash(HttpUrl.parse("http://host/resource/"))); - } - - @Test - public void testWithTrailingSlash() { - assertEquals(HttpUrl.parse("http://host/resource/"), UrlUtils.withTrailingSlash(HttpUrl.parse("http://host/resource"))); - assertEquals(HttpUrl.parse("http://host/resource/"), UrlUtils.withTrailingSlash(HttpUrl.parse("http://host/resource/"))); - } - -} diff --git a/src/test/java/at/bitfire/dav4android/UrlUtilsTest.kt b/src/test/java/at/bitfire/dav4android/UrlUtilsTest.kt new file mode 100644 index 0000000..0c08993 --- /dev/null +++ b/src/test/java/at/bitfire/dav4android/UrlUtilsTest.kt @@ -0,0 +1,56 @@ +/* + * Copyright © Ricki Hirner (bitfire web engineering). + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + */ + +package at.bitfire.dav4android + +import okhttp3.HttpUrl +import org.junit.Assert.* +import org.junit.Test + +class UrlUtilsTest { + + @Test + fun testEquals() { + assertTrue(UrlUtils.equals(HttpUrl.parse("http://host/resource")!!, HttpUrl.parse("http://host/resource")!!)) + assertTrue(UrlUtils.equals(HttpUrl.parse("http://host:80/resource")!!, HttpUrl.parse("http://host/resource")!!)) + assertTrue(UrlUtils.equals(HttpUrl.parse("https://HOST:443/resource")!!, HttpUrl.parse("https://host/resource")!!)) + assertTrue(UrlUtils.equals(HttpUrl.parse("https://host:443/my@dav/")!!, HttpUrl.parse("https://host/my%40dav/")!!)) + + assertFalse(UrlUtils.equals(HttpUrl.parse("http://host/resource")!!, HttpUrl.parse("http://host/resource/")!!)) + assertFalse(UrlUtils.equals(HttpUrl.parse("http://host/resource")!!, HttpUrl.parse("http://host:81/resource")!!)) + } + + @Test + fun testHostToDomain() { + assertNull(UrlUtils.hostToDomain(null)) + assertEquals("", UrlUtils.hostToDomain(".")) + assertEquals("com", UrlUtils.hostToDomain("com")) + assertEquals("com", UrlUtils.hostToDomain("com.")) + assertEquals("example.com", UrlUtils.hostToDomain("example.com")) + assertEquals("example.com", UrlUtils.hostToDomain("example.com.")) + assertEquals("example.com", UrlUtils.hostToDomain(".example.com")) + assertEquals("example.com", UrlUtils.hostToDomain(".example.com.")) + assertEquals("example.com", UrlUtils.hostToDomain("host.example.com")) + assertEquals("example.com", UrlUtils.hostToDomain("host.example.com.")) + assertEquals("example.com", UrlUtils.hostToDomain("sub.host.example.com")) + assertEquals("example.com", UrlUtils.hostToDomain("sub.host.example.com.")) + } + + @Test + fun testOmitTrailingSlash() { + assertEquals(HttpUrl.parse("http://host/resource")!!, UrlUtils.omitTrailingSlash(HttpUrl.parse("http://host/resource")!!)) + assertEquals(HttpUrl.parse("http://host/resource")!!, UrlUtils.omitTrailingSlash(HttpUrl.parse("http://host/resource/")!!)) + } + + @Test + fun testWithTrailingSlash() { + assertEquals(HttpUrl.parse("http://host/resource/")!!, UrlUtils.withTrailingSlash(HttpUrl.parse("http://host/resource")!!)) + assertEquals(HttpUrl.parse("http://host/resource/")!!, UrlUtils.withTrailingSlash(HttpUrl.parse("http://host/resource/")!!)) + } + +} diff --git a/src/test/java/at/bitfire/dav4android/exception/HttpExceptionTest.java b/src/test/java/at/bitfire/dav4android/exception/HttpExceptionTest.java deleted file mode 100644 index ae7855c..0000000 --- a/src/test/java/at/bitfire/dav4android/exception/HttpExceptionTest.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright © Ricki Hirner (bitfire web engineering). - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Public License v3.0 - * which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/gpl.html - */ - -package at.bitfire.dav4android.exception; - -import org.junit.Test; - -import okhttp3.Protocol; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import okhttp3.ResponseBody; - -import static junit.framework.Assert.assertTrue; - -@SuppressWarnings("ThrowableInstanceNeverThrown") -public class HttpExceptionTest { - - final static String responseMessage = "Unknown error"; - - @Test - public void testHttpFormatting() { - Request request = new Request.Builder() - .post(RequestBody.create(null, "REQUEST\nBODY" + (char)5)) - .url("http://example.com") - .build(); - - Response response = new Response.Builder() - .request(request) - .protocol(Protocol.HTTP_2) - .code(500) - .message(responseMessage) - .body(ResponseBody.create(null, (char)0x99 + "SERVER\r\nRESPONSE")) - .build(); - HttpException e = new HttpException(response); - assertTrue(e.getMessage().contains("500")); - assertTrue(e.getMessage().contains(responseMessage)); - assertTrue(e.getRequest().contains("REQUEST\nBODY[05]")); - assertTrue(e.getResponse().contains("[99]SERVER↵\nRESPONSE")); - } - -} diff --git a/src/test/java/at/bitfire/dav4android/exception/HttpExceptionTest.kt b/src/test/java/at/bitfire/dav4android/exception/HttpExceptionTest.kt new file mode 100644 index 0000000..460f9e4 --- /dev/null +++ b/src/test/java/at/bitfire/dav4android/exception/HttpExceptionTest.kt @@ -0,0 +1,40 @@ +/* + * Copyright © Ricki Hirner (bitfire web engineering). + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + */ + +package at.bitfire.dav4android.exception + +import okhttp3.* +import org.junit.Assert.assertTrue +import org.junit.Test + +class HttpExceptionTest { + + private val responseMessage = "Unknown error" + + @Test + fun testHttpFormatting() { + val request = Request.Builder() + .post(RequestBody.create(null, "REQUEST\nBODY" + 5.toChar())) + .url("http://example.com") + .build() + + val response = Response.Builder() + .request(request) + .protocol(Protocol.HTTP_2) + .code(500) + .message(responseMessage) + .body(ResponseBody.create(null, 0x99.toChar() + "SERVER\r\nRESPONSE")) + .build() + val e = HttpException(response) + assertTrue(e.message!!.contains("500")) + assertTrue(e.message!!.contains(responseMessage)) + assertTrue(e.request!!.contains("REQUEST\nBODY[05]")) + assertTrue(e.response!!.contains("[99]SERVER↵\nRESPONSE")) + } + +} diff --git a/src/test/java/at/bitfire/dav4android/exception/ServiceUnavailableExceptionTest.java b/src/test/java/at/bitfire/dav4android/exception/ServiceUnavailableExceptionTest.java deleted file mode 100644 index 675fd82..0000000 --- a/src/test/java/at/bitfire/dav4android/exception/ServiceUnavailableExceptionTest.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright © Ricki Hirner (bitfire web engineering). - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Public License v3.0 - * which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/gpl.html - */ - -package at.bitfire.dav4android.exception; - -import org.junit.Test; - -import java.util.Calendar; -import java.util.Date; - -import okhttp3.Protocol; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.internal.http.HttpDate; - -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -public class ServiceUnavailableExceptionTest { - - @Test - public void testRetryAfter() { - Response response = new Response.Builder() - .request(new Request.Builder() - .url("http://www.example.com") - .get() - .build()) - .protocol(Protocol.HTTP_1_1) - .code(503).message("Try later") - .build(); - - ServiceUnavailableException e = new ServiceUnavailableException(response); - assertNull(e.getRetryAfter()); - - response = response.newBuilder() - .header("Retry-After", "120") - .build(); - e = new ServiceUnavailableException(response); - assertNotNull(e.getRetryAfter()); - assertTrue(withinTimeRange(e.getRetryAfter(), 120)); - - Calendar cal = Calendar.getInstance(); - cal.add(Calendar.MINUTE, 30); - response = response.newBuilder() - .header("Retry-After", HttpDate.format(cal.getTime())) - .build(); - e = new ServiceUnavailableException(response); - assertNotNull(e.getRetryAfter()); - assertTrue(withinTimeRange(e.getRetryAfter(), 30*60)); - } - - - private boolean withinTimeRange(Date d, int seconds) { - final long msCheck = d.getTime(), msShouldBe = new Date().getTime() + seconds*1000; - // assume max. 5 seconds difference for test running - return Math.abs(msCheck - msShouldBe) < 5000; - } - -} diff --git a/src/test/java/at/bitfire/dav4android/exception/ServiceUnavailableExceptionTest.kt b/src/test/java/at/bitfire/dav4android/exception/ServiceUnavailableExceptionTest.kt new file mode 100644 index 0000000..8383fe6 --- /dev/null +++ b/src/test/java/at/bitfire/dav4android/exception/ServiceUnavailableExceptionTest.kt @@ -0,0 +1,60 @@ +/* + * Copyright © Ricki Hirner (bitfire web engineering). + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + */ + +package at.bitfire.dav4android.exception + +import okhttp3.Protocol +import okhttp3.Request +import okhttp3.Response +import okhttp3.internal.http.HttpDate +import org.junit.Assert.* +import org.junit.Test +import java.util.* + +class ServiceUnavailableExceptionTest { + + @Test + fun testRetryAfter() { + var response = Response.Builder() + .request(Request.Builder() + .url("http://www.example.com") + .get() + .build()) + .protocol(Protocol.HTTP_1_1) + .code(503).message("Try later") + .build() + + var e = ServiceUnavailableException(response) + assertNull(e.retryAfter) + + response = response.newBuilder() + .header("Retry-After", "120") + .build() + e = ServiceUnavailableException(response) + assertNotNull(e.retryAfter) + assertTrue(withinTimeRange(e.retryAfter!!, 120)) + + val cal = Calendar.getInstance() + cal.add(Calendar.MINUTE, 30) + response = response.newBuilder() + .header("Retry-After", HttpDate.format(cal.time)) + .build() + e = ServiceUnavailableException(response) + assertNotNull(e.retryAfter) + assertTrue(withinTimeRange(e.retryAfter!!, 30*60)) + } + + + private fun withinTimeRange(d: Date, seconds: Int): Boolean { + val msCheck = d.time + val msShouldBe = Date().time + seconds*1000 + // assume max. 5 seconds difference for test running + return Math.abs(msCheck - msShouldBe) < 5000 + } + +} \ No newline at end of file -- GitLab