Skip to content

Commit c1bc143

Browse files
authored
Simplify UrlUtils.equals(), move to extension (#54)
1 parent 56d8b6c commit c1bc143

File tree

3 files changed

+71
-79
lines changed

3 files changed

+71
-79
lines changed

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

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ data class Response(
115115

116116
val depth = parser.depth
117117

118-
var href: HttpUrl? = null
118+
var hrefOrNull: HttpUrl? = null
119119
var status: StatusLine? = null
120120
val propStat = mutableListOf<PropStat>()
121121
var error: List<Error>? = null
@@ -147,7 +147,7 @@ data class Response(
147147
sHref = "./$sHref"
148148
}
149149
}
150-
href = location.resolve(sHref)
150+
hrefOrNull = location.resolve(sHref)
151151
}
152152
STATUS ->
153153
status = try {
@@ -166,10 +166,11 @@ data class Response(
166166
eventType = parser.next()
167167
}
168168

169-
if (href == null) {
169+
if (hrefOrNull == null) {
170170
logger.warning("Ignoring XML response element without valid href")
171171
return
172172
}
173+
var href: HttpUrl = hrefOrNull // guaranteed to be not null
173174

174175
// if we know this resource is a collection, make sure href has a trailing slash
175176
// (for clarity and resolving relative paths)
@@ -179,23 +180,24 @@ data class Response(
179180
.firstOrNull()
180181
?.let { type ->
181182
if (type.types.contains(ResourceType.COLLECTION))
182-
href = UrlUtils.withTrailingSlash(href!!)
183+
href = UrlUtils.withTrailingSlash(href)
183184
}
184185

185186
//log.log(Level.FINE, "Received properties for $href", if (status != null) status else propStat)
186187

187188
// Which resource does this <response> represent?
188189
val relation = when {
189-
UrlUtils.equals(UrlUtils.omitTrailingSlash(href!!), UrlUtils.omitTrailingSlash(location)) ->
190+
UrlUtils.omitTrailingSlash(href).equalsForWebDAV(UrlUtils.omitTrailingSlash(location)) ->
190191
HrefRelation.SELF
192+
191193
else -> {
192-
if (location.scheme == href!!.scheme && location.host == href!!.host && location.port == href!!.port) {
194+
if (location.scheme == href.scheme && location.host == href.host && location.port == href.port) {
193195
val locationSegments = location.pathSegments
194-
val hrefSegments = href!!.pathSegments
196+
val hrefSegments = href.pathSegments
195197

196198
// don't compare trailing slash segment ("")
197199
var nBasePathSegments = locationSegments.size
198-
if (locationSegments[nBasePathSegments-1] == "")
200+
if (locationSegments[nBasePathSegments - 1] == "")
199201
nBasePathSegments--
200202

201203
/* example: locationSegments = [ "davCollection", "" ]
@@ -217,12 +219,12 @@ data class Response(
217219

218220
callback.onResponse(
219221
Response(
220-
location,
221-
href!!,
222-
status,
223-
propStat,
224-
error,
225-
newLocation
222+
requestedUrl = location,
223+
href = href,
224+
status = status,
225+
propstat = propStat,
226+
error = error,
227+
newLocation = newLocation
226228
),
227229
relation
228230
)

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

Lines changed: 37 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6,58 +6,14 @@
66

77
package at.bitfire.dav4jvm
88

9+
import at.bitfire.dav4jvm.UrlUtils.omitTrailingSlash
10+
import at.bitfire.dav4jvm.UrlUtils.withTrailingSlash
911
import okhttp3.HttpUrl
10-
import java.net.URI
11-
import java.net.URISyntaxException
12-
import java.util.logging.Level
13-
import java.util.logging.Logger
1412

1513
object UrlUtils {
1614

17-
private val logger
18-
get() = Logger.getLogger(javaClass.name)
19-
20-
21-
/**
22-
* Compares two URLs in WebDAV context. If two URLs are considered *equal*, both
23-
* represent the same WebDAV resource (e.g. `http://host:80/folder1` and `http://HOST/folder1#somefragment`).
24-
*
25-
* It decodes %xx entities in the path, so `/my@dav` and `/my%40dav` are considered the same.
26-
* This is important to process multi-status responses: some servers serve a multi-status
27-
* response with href `/my@dav` when you request `/my%40dav` and vice versa.
28-
*
29-
* This method does not deal with trailing slashes, so if you want to compare collection URLs,
30-
* make sure they both (don't) have a trailing slash before calling this method, for instance
31-
* with [omitTrailingSlash] or [withTrailingSlash].
32-
*
33-
* @param url1 the first URL to be compared
34-
* @param url2 the second URL to be compared
35-
*
36-
* @return whether [url1] and [url2] (usually) represent the same WebDAV resource
37-
*/
38-
fun equals(url1: HttpUrl, url2: HttpUrl): Boolean {
39-
// if okhttp thinks the two URLs are equal, they're in any case
40-
// (and it's a simple String comparison)
41-
if (url1 == url2)
42-
return true
43-
44-
// convert to java.net.URI (also corrects some mistakes)
45-
val uri1 = url1.toUri()
46-
val uri2 = url2.toUri()
47-
48-
// if the URIs are the same (ignoring scheme case and fragments), they're equal for us
49-
if (uri1.scheme.equals(uri2.scheme, true) && uri1.schemeSpecificPart == uri2.schemeSpecificPart)
50-
return true
51-
52-
return try {
53-
val decoded1 = URI(url1.scheme, uri1.schemeSpecificPart, null)
54-
val decoded2 = URI(uri2.scheme, uri2.schemeSpecificPart, null)
55-
decoded1 == decoded2
56-
} catch (e: URISyntaxException) {
57-
logger.log(Level.WARNING, "Couldn't decode URI for comparison, assuming inequality", e)
58-
false
59-
}
60-
}
15+
@Deprecated("Use equalsForWebDAV instead", ReplaceWith("url1.equalsForWebDAV(url2)"))
16+
fun equals(url1: HttpUrl, url2: HttpUrl) = url1.equalsForWebDAV(url2)
6117

6218
/**
6319
* Gets the first-level domain name (without subdomains) from a host name.
@@ -117,4 +73,37 @@ object UrlUtils {
11773
url.newBuilder().addPathSegment("").build()
11874
}
11975

76+
}
77+
78+
/**
79+
* Compares two [HttpUrl]s in WebDAV context. If two URLs are considered *equal*, both
80+
* represent the same WebDAV resource.
81+
*
82+
* The fragment of an URL is ignored, e.g. `http://host:80/folder1` and `http://HOST/folder1#somefragment`
83+
* are considered to be equal.
84+
*
85+
* [HttpUrl] is less strict than [java.net.URI] and allows for instance (not encoded) square brackets in the path.
86+
* So this method tries to normalize the URI by converting it to a [java.net.URI] (encodes for instance square brackets)
87+
* and then comparing the scheme and scheme-specific part (without fragment).
88+
*
89+
* Attention: **This method does not deal with trailing slashes**, so if you want to compare collection URLs,
90+
* make sure they both (don't) have a trailing slash before calling this method, for instance
91+
* with [omitTrailingSlash] or [withTrailingSlash].
92+
*
93+
* @param other the URL to compare the current object with
94+
*
95+
* @return whether the URLs are considered to represent the same WebDAV resource
96+
*/
97+
fun HttpUrl.equalsForWebDAV(other: HttpUrl): Boolean {
98+
// if okhttp thinks the two URLs are equal, they're in any case
99+
// (and it's a simple String comparison)
100+
if (this == other)
101+
return true
102+
103+
// convert to java.net.URI (also corrects some mistakes and escapes for instance square brackets)
104+
val uri1 = toUri()
105+
val uri2 = other.toUri()
106+
107+
// if the URIs are the same (ignoring scheme case and fragments), they're equal for us
108+
return uri1.scheme.equals(uri2.scheme, true) && uri1.schemeSpecificPart == uri2.schemeSpecificPart
120109
}

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

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,6 @@ import org.junit.Test
1515

1616
class UrlUtilsTest {
1717

18-
@Test
19-
fun testEquals() {
20-
assertTrue(UrlUtils.equals("http://host/resource".toHttpUrl(), "http://host/resource".toHttpUrl()))
21-
assertTrue(UrlUtils.equals("http://host:80/resource".toHttpUrl(), "http://host/resource".toHttpUrl()))
22-
assertTrue(UrlUtils.equals("https://HOST:443/resource".toHttpUrl(), "https://host/resource".toHttpUrl()))
23-
assertTrue(UrlUtils.equals("https://host:443/my@dav/".toHttpUrl(), "https://host/my%40dav/".toHttpUrl()))
24-
assertTrue(UrlUtils.equals("http://host/resource".toHttpUrl(), "http://host/resource#frag1".toHttpUrl()))
25-
26-
assertFalse(UrlUtils.equals("http://host/resource".toHttpUrl(), "http://host/resource/".toHttpUrl()))
27-
assertFalse(UrlUtils.equals("http://host/resource".toHttpUrl(), "http://host:81/resource".toHttpUrl()))
28-
29-
assertTrue(UrlUtils.equals("https://www.example.com/folder/[X]Y!.txt".toHttpUrl(), "https://www.example.com/folder/[X]Y!.txt".toHttpUrl()))
30-
assertTrue(UrlUtils.equals("https://www.example.com/folder/%5BX%5DY!.txt".toHttpUrl(), "https://www.example.com/folder/[X]Y!.txt".toHttpUrl()))
31-
assertTrue(UrlUtils.equals("https://www.example.com/folder/%5bX%5dY%21.txt".toHttpUrl(), "https://www.example.com/folder/[X]Y!.txt".toHttpUrl()))
32-
}
33-
3418
@Test
3519
fun testHostToDomain() {
3620
assertNull(UrlUtils.hostToDomain(null))
@@ -59,4 +43,21 @@ class UrlUtilsTest {
5943
assertEquals("http://host/resource/".toHttpUrl(), UrlUtils.withTrailingSlash("http://host/resource/".toHttpUrl()))
6044
}
6145

62-
}
46+
47+
@Test
48+
fun testHttpUrl_EqualsForWebDAV() {
49+
assertTrue("http://host/resource".toHttpUrl().equalsForWebDAV("http://host/resource".toHttpUrl()))
50+
assertTrue("http://host:80/resource".toHttpUrl().equalsForWebDAV("http://host/resource".toHttpUrl()))
51+
assertTrue("https://HOST:443/resource".toHttpUrl().equalsForWebDAV("https://host/resource".toHttpUrl()))
52+
assertTrue("https://host:443/my@dav/".toHttpUrl().equalsForWebDAV("https://host/my%40dav/".toHttpUrl()))
53+
assertTrue("http://host/resource".toHttpUrl().equalsForWebDAV("http://host/resource#frag1".toHttpUrl()))
54+
55+
assertFalse("http://host/resource".toHttpUrl().equalsForWebDAV("http://host/resource/".toHttpUrl()))
56+
assertFalse("http://host/resource".toHttpUrl().equalsForWebDAV("http://host:81/resource".toHttpUrl()))
57+
58+
assertTrue("https://www.example.com/folder/[X]Y!.txt".toHttpUrl().equalsForWebDAV("https://www.example.com/folder/[X]Y!.txt".toHttpUrl()))
59+
assertTrue("https://www.example.com/folder/%5BX%5DY!.txt".toHttpUrl().equalsForWebDAV("https://www.example.com/folder/[X]Y!.txt".toHttpUrl()))
60+
assertTrue("https://www.example.com/folder/%5bX%5dY%21.txt".toHttpUrl().equalsForWebDAV("https://www.example.com/folder/[X]Y!.txt".toHttpUrl()))
61+
}
62+
63+
}

0 commit comments

Comments
 (0)