diff --git a/r2-shared/build.gradle b/r2-shared/build.gradle index 204b1114..78849088 100644 --- a/r2-shared/build.gradle +++ b/r2-shared/build.gradle @@ -53,6 +53,7 @@ dependencies { testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" testImplementation 'junit:junit:4.12' + testImplementation 'org.assertj:assertj-core:3.14.0' testImplementation 'org.mockito:mockito-core:2.19.0' testImplementation 'xmlpull:xmlpull:1.1.3.1' testImplementation 'net.sf.kxml:kxml2:2.3.0' diff --git a/r2-shared/src/main/java/org/readium/r2/shared/URLHelper.kt b/r2-shared/src/main/java/org/readium/r2/shared/URLHelper.kt index a3153b5b..1aac1542 100644 --- a/r2-shared/src/main/java/org/readium/r2/shared/URLHelper.kt +++ b/r2-shared/src/main/java/org/readium/r2/shared/URLHelper.kt @@ -11,13 +11,14 @@ package org.readium.r2.shared import android.net.Uri import java.net.URI +import java.net.URLDecoder fun getAbsolute(href: String, base: String): String { return try { val baseURI = URI.create(base) val relative = baseURI.resolve(href) relative.toString() - }catch (e:IllegalArgumentException){ + } catch (e:IllegalArgumentException){ val hrefUri = Uri.parse(href) if (hrefUri.isAbsolute){ href @@ -27,26 +28,24 @@ fun getAbsolute(href: String, base: String): String { } } - -internal fun normalize(base: String, in_href: String?) : String { - if (in_href == null || in_href.isEmpty()) { - return "" - } - val hrefComponents = in_href.split( "/").filter { it.isNotEmpty() } - val baseComponents = base.split( "/").filter { it.isNotEmpty() } - baseComponents.dropLast(1) - - val replacementsNumber = hrefComponents.filter { it == ".." }.count() - var normalizedComponents = hrefComponents.filter { it != ".." } - for (e in 0 until replacementsNumber) { - baseComponents.dropLast(1) - } - normalizedComponents = baseComponents + normalizedComponents - var normalizedString = "" - for (component in normalizedComponents) { - normalizedString += "/$component" +fun normalize(base: String, href: String?): String { + val resolved = if (href.isNullOrEmpty()) "" + else try { // href is returned by resolve if it is absolute + val absoluteUri = URI.create(base).resolve(href) + val absoluteString = absoluteUri.toString() // this is a percent-decoded + val addSlash = absoluteUri.scheme == null && !absoluteString.startsWith("/") + (if (addSlash) "/" else "") + absoluteString + } catch (e: IllegalArgumentException){ // one of the URIs is ill-formed + val hrefUri = Uri.parse(href) // Android Uri is more forgiving + // Let's try to return something + if (hrefUri.isAbsolute) { + href + } else if (base.startsWith("/")) { + base + href + } else + "/" + base + href } - return normalizedString + return URLDecoder.decode(resolved, "UTF-8") } diff --git a/r2-shared/src/test/java/org/readium/r2/shared/URLHelperTest.kt b/r2-shared/src/test/java/org/readium/r2/shared/URLHelperTest.kt new file mode 100644 index 00000000..e3b31ad0 --- /dev/null +++ b/r2-shared/src/test/java/org/readium/r2/shared/URLHelperTest.kt @@ -0,0 +1,44 @@ +/* + * Module: r2-streamer-kotlin + * Developers: Quentin Gliosca + * + * Copyright (c) 2018. Readium Foundation. All rights reserved. + * Use of this source code is governed by a BSD-style license which is detailed in the + * LICENSE file present in the project repository where this source code is maintained. + */ + +package org.readium.r2.shared + +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test + +class NormalizeTest { + @Test + fun `Anchors are accepted as href`() { + assertThat(normalize("OEBPS/xhtml/nav.xhtml", "#toc")).isEqualTo("/OEBPS/xhtml/nav.xhtml#toc") + } + + @Test + fun `Directories are accepted as base`() { + assertThat(normalize("OEBPS/xhtml/", "nav.xhtml")).isEqualTo("/OEBPS/xhtml/nav.xhtml") + } + + @Test + fun `href is returned unchanged if it is an absolute path`() { + assertThat(normalize("OEBPS/content.opf", "/OEBPS/xhtml/index.xhtml")).isEqualTo("/OEBPS/xhtml/index.xhtml") + } + + @Test + fun `href is returned unchanged if it is an absolute URI`() { + assertThat(normalize("OEBPS/content.opf", "http://example.org/index.xhtml")) + } + + @Test + fun `Result is percent-decoded`() { + val base = "OEBPS/xhtml/%E4%B8%8A%E6%B5%B7%2B%E4%B8%AD%E5%9C%8B/base.xhtml" + val href = "%E4%B8%8A%E6%B5%B7%2B%E4%B8%AD%E5%9C%8B.xhtml" + assertThat(normalize(base, href)).isEqualTo("/OEBPS/xhtml/上海+中國/上海+中國.xhtml") + assertThat(normalize("OEBPS/xhtml%20files/nav.xhtml", "chapter1.xhtml")).isEqualTo("/OEBPS/xhtml files/chapter1.xhtml") + + } +} \ No newline at end of file