From 0e8bbabeb0b9e414dd5619b77d2c7baafdd9d40f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Menu?= Date: Wed, 12 Jun 2024 14:58:30 +0200 Subject: [PATCH 1/6] Normalize URLs --- .../navigator/epub/EpubNavigatorFragment.kt | 2 +- .../r2/navigator/extensions/Publication.kt | 4 +- .../org/readium/r2/shared/publication/Href.kt | 2 +- .../org/readium/r2/shared/publication/Link.kt | 2 +- .../readium/r2/shared/publication/Manifest.kt | 5 ++- .../java/org/readium/r2/shared/util/Url.kt | 29 +++++++++++++- .../org/readium/r2/shared/util/UrlTest.kt | 39 +++++++++++++++++++ 7 files changed, 74 insertions(+), 9 deletions(-) diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorFragment.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorFragment.kt index f14dfa12c4..9d9428266d 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorFragment.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorFragment.kt @@ -615,7 +615,7 @@ public class EpubNavigatorFragment internal constructor( listener?.onJumpToLocator(locator) - val href = locator.href.removeFragment() + val href = locator.href.removeFragment().normalize() fun setCurrent(resources: List) { val page = resources.withIndex().firstOrNull { (_, res) -> diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/extensions/Publication.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/extensions/Publication.kt index 70aa053e6a..0d678b617a 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/extensions/Publication.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/extensions/Publication.kt @@ -36,12 +36,12 @@ public fun Publication.normalizeLocator(locator: Locator): Locator { return if (self == null) { // Packaged publication locator.copy( - href = Url(locator.href.toString().removePrefix("/")) + href = Url(locator.href.toString().removePrefix("/"))?.normalize() ?: return locator ) } else { // Remote publication // Check that the locator HREF relative to `self` exists int he manifest. - val relativeHref = self.relativize(locator.href) + val relativeHref = self.relativize(locator.href).normalize() if (linkWithHref(relativeHref) != null) { locator.copy(href = relativeHref) } else { diff --git a/readium/shared/src/main/java/org/readium/r2/shared/publication/Href.kt b/readium/shared/src/main/java/org/readium/r2/shared/publication/Href.kt index 1287ac2b50..7183bb37ca 100644 --- a/readium/shared/src/main/java/org/readium/r2/shared/publication/Href.kt +++ b/readium/shared/src/main/java/org/readium/r2/shared/publication/Href.kt @@ -48,7 +48,7 @@ public class Href private constructor(private val href: Url) : Parcelable { public fun resolve( base: SharedUrl? = null, parameters: Map = emptyMap() - ): SharedUrl = href.resolve(base, parameters) + ): SharedUrl = href.resolve(base, parameters).normalize() /** * Indicates whether this HREF is templated. diff --git a/readium/shared/src/main/java/org/readium/r2/shared/publication/Link.kt b/readium/shared/src/main/java/org/readium/r2/shared/publication/Link.kt index 864cbc3c6b..c016899f46 100644 --- a/readium/shared/src/main/java/org/readium/r2/shared/publication/Link.kt +++ b/readium/shared/src/main/java/org/readium/r2/shared/publication/Link.kt @@ -92,7 +92,7 @@ public data class Link( public fun url( base: Url? = null, parameters: Map = emptyMap() - ): Url = href.resolve(base, parameters) + ): Url = href.resolve(base, parameters).normalize() /** * List of URI template parameter keys, if the [Link] is templated. diff --git a/readium/shared/src/main/java/org/readium/r2/shared/publication/Manifest.kt b/readium/shared/src/main/java/org/readium/r2/shared/publication/Manifest.kt index 96afbd08f2..d3b11e19e9 100644 --- a/readium/shared/src/main/java/org/readium/r2/shared/publication/Manifest.kt +++ b/readium/shared/src/main/java/org/readium/r2/shared/publication/Manifest.kt @@ -85,8 +85,9 @@ public data class Manifest( ?: links.deepLinkWithHref(href) } - return find(href) - ?: find(href.removeFragment().removeQuery()) + val normalizedHref = href.normalize() + return find(normalizedHref) + ?: find(normalizedHref.removeFragment().removeQuery()) } /** diff --git a/readium/shared/src/main/java/org/readium/r2/shared/util/Url.kt b/readium/shared/src/main/java/org/readium/r2/shared/util/Url.kt index d810da031e..c109505646 100644 --- a/readium/shared/src/main/java/org/readium/r2/shared/util/Url.kt +++ b/readium/shared/src/main/java/org/readium/r2/shared/util/Url.kt @@ -173,6 +173,25 @@ public sealed class Url : Parcelable { public open fun relativize(url: Url): Url = checkNotNull(toURI().relativize(url.toURI()).toUrl()) + /** + * Normalizes the URL using a subset of the RFC-3986 rules. + * + * https://datatracker.ietf.org/doc/html/rfc3986#section-6 + */ + public open fun normalize(): Url = + uri.buildUpon() + .apply { + path?.let { + path(File(it).normalize().path) + } + + if (this@Url is AbsoluteUrl) { + scheme(scheme.value) + } + } + .build() + .toUrl()!! + override fun toString(): String = uri.toString() @@ -241,6 +260,9 @@ public class AbsoluteUrl private constructor(override val uri: Uri) : Url() { public override fun resolve(url: Url): AbsoluteUrl = super.resolve(url) as AbsoluteUrl + public override fun normalize(): AbsoluteUrl = + super.normalize() as AbsoluteUrl + /** * Identifies the type of URL. */ @@ -294,6 +316,9 @@ public class RelativeUrl private constructor(override val uri: Uri) : Url() { RelativeUrl(uri) } } + + public override fun normalize(): RelativeUrl = + super.normalize() as RelativeUrl } /** @@ -307,7 +332,7 @@ public class RelativeUrl private constructor(override val uri: Uri) : Url() { */ @DelicateReadiumApi public fun Url.Companion.fromLegacyHref(href: String): Url? = - AbsoluteUrl(href) ?: Url.fromDecodedPath(href.removePrefix("/")) + AbsoluteUrl(href) ?: fromDecodedPath(href.removePrefix("/")) /** * According to the EPUB specification, the HREFs in the EPUB package must be valid URLs (so @@ -319,7 +344,7 @@ public fun Url.Companion.fromLegacyHref(href: String): Url? = */ @InternalReadiumApi public fun Url.Companion.fromEpubHref(href: String): Url? { - return (Url(href) ?: Url.fromDecodedPath(href)) + return (Url(href) ?: fromDecodedPath(href))?.normalize() } public fun File.toUrl(): AbsoluteUrl = diff --git a/readium/shared/src/test/java/org/readium/r2/shared/util/UrlTest.kt b/readium/shared/src/test/java/org/readium/r2/shared/util/UrlTest.kt index 28aa142b0d..f7ddd54e40 100644 --- a/readium/shared/src/test/java/org/readium/r2/shared/util/UrlTest.kt +++ b/readium/shared/src/test/java/org/readium/r2/shared/util/UrlTest.kt @@ -399,4 +399,43 @@ class UrlTest { assertEquals(params.allNamed("empty"), emptyList()) assertEquals(params.allNamed("not-found"), emptyList()) } + + @Test + fun normalize() { + // Scheme is lower case. + assertEquals( + "http://example.com", + Url("HTTP://example.com")!!.normalize().toString() + ) + + // Path is percent-decoded. + assertEquals( + "http://example.com/c'est%20valide", + Url("HTTP://example.com/c%27est%20valide")!!.normalize().toString() + ) + assertEquals( + "c'est%20valide", + Url("c%27est%20valide")!!.normalize().toString() + ) + + // Relative paths are resolved. + assertEquals( + "http://example.com/foo/baz", + Url("http://example.com/foo/./bar//../baz")!!.normalize().toString() + ) + assertEquals( + "foo/baz", + Url("foo/./bar//../baz")!!.normalize().toString() + ) + assertEquals( + "../baz", + Url("foo/./bar/../../../baz")!!.normalize().toString() + ) + + // The other components are left as-is. + assertEquals( + "http://user:password@example.com:443/foo?b=b&a=a#fragment", + Url("http://user:password@example.com:443/foo?b=b&a=a#fragment")!!.normalize().toString() + ) + } } From 96963bd425bfe1daac9c026a2d32678b5d0799c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Menu?= Date: Wed, 12 Jun 2024 16:09:18 +0200 Subject: [PATCH 2/6] Keep trailing slashes --- .../src/main/java/org/readium/r2/shared/util/Url.kt | 6 +++++- .../test/java/org/readium/r2/shared/util/UrlTest.kt | 10 ++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/readium/shared/src/main/java/org/readium/r2/shared/util/Url.kt b/readium/shared/src/main/java/org/readium/r2/shared/util/Url.kt index c109505646..ac00af82db 100644 --- a/readium/shared/src/main/java/org/readium/r2/shared/util/Url.kt +++ b/readium/shared/src/main/java/org/readium/r2/shared/util/Url.kt @@ -182,7 +182,11 @@ public sealed class Url : Parcelable { uri.buildUpon() .apply { path?.let { - path(File(it).normalize().path) + var normalizedPath = File(it).normalize().path + if (it.endsWith("/")) { + normalizedPath += "/" + } + path(normalizedPath) } if (this@Url is AbsoluteUrl) { diff --git a/readium/shared/src/test/java/org/readium/r2/shared/util/UrlTest.kt b/readium/shared/src/test/java/org/readium/r2/shared/util/UrlTest.kt index f7ddd54e40..f117f35e92 100644 --- a/readium/shared/src/test/java/org/readium/r2/shared/util/UrlTest.kt +++ b/readium/shared/src/test/java/org/readium/r2/shared/util/UrlTest.kt @@ -432,6 +432,16 @@ class UrlTest { Url("foo/./bar/../../../baz")!!.normalize().toString() ) + // Trailing slash is kept. + assertEquals( + "http://example.com/foo/", + Url("http://example.com/foo/")!!.normalize().toString() + ) + assertEquals( + "foo/", + Url("foo/")!!.normalize().toString() + ) + // The other components are left as-is. assertEquals( "http://user:password@example.com:443/foo?b=b&a=a#fragment", From 4b31d95b03e16c52e8c0469790ecaa9483323c95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Menu?= Date: Thu, 13 Jun 2024 15:37:00 +0200 Subject: [PATCH 3/6] Only normalize when comparing --- .../java/org/readium/r2/lcp/LcpDecryptor.kt | 3 ++- .../navigator/epub/EpubNavigatorFragment.kt | 4 ++-- .../r2/navigator/extensions/Publication.kt | 6 +++--- .../org/readium/r2/shared/publication/Href.kt | 2 +- .../org/readium/r2/shared/publication/Link.kt | 2 +- .../readium/r2/shared/publication/Manifest.kt | 2 +- .../java/org/readium/r2/shared/util/Url.kt | 21 ++++++++++++++++--- .../readium/r2/shared/util/data/Caching.kt | 3 ++- .../util/resource/SingleResourceContainer.kt | 2 +- .../r2/shared/util/format/TestContainer.kt | 3 ++- .../r2/streamer/parser/audio/AudioParser.kt | 3 ++- .../streamer/parser/epub/EpubDeobfuscator.kt | 3 ++- .../r2/streamer/parser/image/ImageParser.kt | 3 ++- 13 files changed, 39 insertions(+), 18 deletions(-) diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/LcpDecryptor.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/LcpDecryptor.kt index 0032b014fe..0f7d70c0c7 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/LcpDecryptor.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/LcpDecryptor.kt @@ -22,6 +22,7 @@ import org.readium.r2.shared.util.Try import org.readium.r2.shared.util.Url import org.readium.r2.shared.util.data.ReadError import org.readium.r2.shared.util.flatMap +import org.readium.r2.shared.util.getEquivalent import org.readium.r2.shared.util.getOrElse import org.readium.r2.shared.util.resource.FailureResource import org.readium.r2.shared.util.resource.Resource @@ -38,7 +39,7 @@ internal class LcpDecryptor( fun transform(url: Url, resource: Resource): Resource { return resource.flatMap { - val encryption = encryptionData[url] + val encryption = encryptionData.getEquivalent(url) // Checks if the resource is encrypted and whether the encryption schemes of the resource // and the DRM license are the same. diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorFragment.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorFragment.kt index 9d9428266d..ebeb6965f2 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorFragment.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorFragment.kt @@ -615,13 +615,13 @@ public class EpubNavigatorFragment internal constructor( listener?.onJumpToLocator(locator) - val href = locator.href.removeFragment().normalize() + val href = locator.href.removeFragment() fun setCurrent(resources: List) { val page = resources.withIndex().firstOrNull { (_, res) -> when (res) { is PageResource.EpubReflowable -> - res.link.url() == href + res.link.url().isEquivalent(href) is PageResource.EpubFxl -> res.leftUrl?.toString()?.endsWith(href.toString()) == true || res.rightUrl?.toString()?.endsWith( href.toString() diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/extensions/Publication.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/extensions/Publication.kt index 0d678b617a..c6bb63a78f 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/extensions/Publication.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/extensions/Publication.kt @@ -36,12 +36,12 @@ public fun Publication.normalizeLocator(locator: Locator): Locator { return if (self == null) { // Packaged publication locator.copy( - href = Url(locator.href.toString().removePrefix("/"))?.normalize() + href = Url(locator.href.toString().removePrefix("/")) ?: return locator ) } else { // Remote publication - // Check that the locator HREF relative to `self` exists int he manifest. - val relativeHref = self.relativize(locator.href).normalize() + // Check that the locator HREF relative to `self` exists in the manifest. + val relativeHref = self.relativize(locator.href) if (linkWithHref(relativeHref) != null) { locator.copy(href = relativeHref) } else { diff --git a/readium/shared/src/main/java/org/readium/r2/shared/publication/Href.kt b/readium/shared/src/main/java/org/readium/r2/shared/publication/Href.kt index 7183bb37ca..1287ac2b50 100644 --- a/readium/shared/src/main/java/org/readium/r2/shared/publication/Href.kt +++ b/readium/shared/src/main/java/org/readium/r2/shared/publication/Href.kt @@ -48,7 +48,7 @@ public class Href private constructor(private val href: Url) : Parcelable { public fun resolve( base: SharedUrl? = null, parameters: Map = emptyMap() - ): SharedUrl = href.resolve(base, parameters).normalize() + ): SharedUrl = href.resolve(base, parameters) /** * Indicates whether this HREF is templated. diff --git a/readium/shared/src/main/java/org/readium/r2/shared/publication/Link.kt b/readium/shared/src/main/java/org/readium/r2/shared/publication/Link.kt index c016899f46..864cbc3c6b 100644 --- a/readium/shared/src/main/java/org/readium/r2/shared/publication/Link.kt +++ b/readium/shared/src/main/java/org/readium/r2/shared/publication/Link.kt @@ -92,7 +92,7 @@ public data class Link( public fun url( base: Url? = null, parameters: Map = emptyMap() - ): Url = href.resolve(base, parameters).normalize() + ): Url = href.resolve(base, parameters) /** * List of URI template parameter keys, if the [Link] is templated. diff --git a/readium/shared/src/main/java/org/readium/r2/shared/publication/Manifest.kt b/readium/shared/src/main/java/org/readium/r2/shared/publication/Manifest.kt index d3b11e19e9..6237fa21dc 100644 --- a/readium/shared/src/main/java/org/readium/r2/shared/publication/Manifest.kt +++ b/readium/shared/src/main/java/org/readium/r2/shared/publication/Manifest.kt @@ -69,7 +69,7 @@ public data class Manifest( public fun linkWithHref(href: Url): Link? { fun List.deepLinkWithHref(href: Url): Link? { for (l in this) { - if (l.url() == href) { + if (l.url().normalize() == href) { return l } else { l.alternates.deepLinkWithHref(href)?.let { return it } diff --git a/readium/shared/src/main/java/org/readium/r2/shared/util/Url.kt b/readium/shared/src/main/java/org/readium/r2/shared/util/Url.kt index ac00af82db..3c4dda8518 100644 --- a/readium/shared/src/main/java/org/readium/r2/shared/util/Url.kt +++ b/readium/shared/src/main/java/org/readium/r2/shared/util/Url.kt @@ -210,6 +210,12 @@ public sealed class Url : Parcelable { return true } + /** + * Returns whether the receiver is equivalent to the given `url` after normalization. + */ + public fun isEquivalent(url: Url): Boolean = + normalize() == url.normalize() + override fun hashCode(): Int = uri.toString().hashCode() @@ -347,9 +353,8 @@ public fun Url.Companion.fromLegacyHref(href: String): Url? = * if we can't parse the URL. */ @InternalReadiumApi -public fun Url.Companion.fromEpubHref(href: String): Url? { - return (Url(href) ?: fromDecodedPath(href))?.normalize() -} +public fun Url.Companion.fromEpubHref(href: String): Url? = + Url(href) ?: fromDecodedPath(href) public fun File.toUrl(): AbsoluteUrl = checkNotNull(AbsoluteUrl(Uri.fromFile(this))) @@ -414,3 +419,13 @@ public value class FileExtension( */ public fun FileExtension?.appendToFilename(filename: String): String = this?.let { "$filename.$value" } ?: filename + +/** + * Returns the value of the first key matching `key` after normalization. + */ +public fun Map.getEquivalent(key: Url): T? = + get(key) ?: run { + val url = key.normalize() + keys.firstOrNull { it.normalize() == url } + ?.let { get(it) } + } diff --git a/readium/shared/src/main/java/org/readium/r2/shared/util/data/Caching.kt b/readium/shared/src/main/java/org/readium/r2/shared/util/data/Caching.kt index 04a15bbddd..85918b8840 100644 --- a/readium/shared/src/main/java/org/readium/r2/shared/util/data/Caching.kt +++ b/readium/shared/src/main/java/org/readium/r2/shared/util/data/Caching.kt @@ -8,6 +8,7 @@ package org.readium.r2.shared.util.data import org.readium.r2.shared.util.Try import org.readium.r2.shared.util.Url +import org.readium.r2.shared.util.getEquivalent internal class CachingReadable( private val source: Readable @@ -69,7 +70,7 @@ internal class CachingContainer( mutableMapOf() override fun get(url: Url): Readable? { - cache[url]?.let { return it } + cache.getEquivalent(url)?.let { return it } val entry = container[url] ?: return null diff --git a/readium/shared/src/main/java/org/readium/r2/shared/util/resource/SingleResourceContainer.kt b/readium/shared/src/main/java/org/readium/r2/shared/util/resource/SingleResourceContainer.kt index bdee552b5c..200a712ac2 100644 --- a/readium/shared/src/main/java/org/readium/r2/shared/util/resource/SingleResourceContainer.kt +++ b/readium/shared/src/main/java/org/readium/r2/shared/util/resource/SingleResourceContainer.kt @@ -18,7 +18,7 @@ public class SingleResourceContainer( override val entries: Set = setOf(entryUrl) override fun get(url: Url): Resource? { - if (url.removeFragment().removeQuery() != entryUrl) { + if (!url.removeFragment().removeQuery().isEquivalent(entryUrl)) { return null } diff --git a/readium/shared/src/test/java/org/readium/r2/shared/util/format/TestContainer.kt b/readium/shared/src/test/java/org/readium/r2/shared/util/format/TestContainer.kt index 2f39757888..539a1a807f 100644 --- a/readium/shared/src/test/java/org/readium/r2/shared/util/format/TestContainer.kt +++ b/readium/shared/src/test/java/org/readium/r2/shared/util/format/TestContainer.kt @@ -8,6 +8,7 @@ package org.readium.r2.shared.util.format import org.readium.r2.shared.util.Url import org.readium.r2.shared.util.data.Container +import org.readium.r2.shared.util.getEquivalent import org.readium.r2.shared.util.resource.Resource import org.readium.r2.shared.util.resource.StringResource @@ -25,7 +26,7 @@ class TestContainer( resources.keys override fun get(url: Url): Resource? = - resources[url]?.let { StringResource(it) } + resources.getEquivalent(url)?.let { StringResource(it) } override fun close() {} } diff --git a/readium/streamer/src/main/java/org/readium/r2/streamer/parser/audio/AudioParser.kt b/readium/streamer/src/main/java/org/readium/r2/streamer/parser/audio/AudioParser.kt index 59d16e642a..20990cb8dc 100644 --- a/readium/streamer/src/main/java/org/readium/r2/streamer/parser/audio/AudioParser.kt +++ b/readium/streamer/src/main/java/org/readium/r2/streamer/parser/audio/AudioParser.kt @@ -22,6 +22,7 @@ import org.readium.r2.shared.util.data.Container import org.readium.r2.shared.util.data.ReadError import org.readium.r2.shared.util.format.Format import org.readium.r2.shared.util.format.Specification +import org.readium.r2.shared.util.getEquivalent import org.readium.r2.shared.util.getOrElse import org.readium.r2.shared.util.logging.WarningLogger import org.readium.r2.shared.util.resource.Resource @@ -79,7 +80,7 @@ public class AudioParser( val readingOrderWithFormat = asset.container - .mapNotNull { url -> entryFormats[url]?.let { url to it } } + .mapNotNull { url -> entryFormats.getEquivalent(url)?.let { url to it } } .filter { (_, format) -> format.specification.specifications.any { it in audioSpecifications } } .sortedBy { it.first.toString() } diff --git a/readium/streamer/src/main/java/org/readium/r2/streamer/parser/epub/EpubDeobfuscator.kt b/readium/streamer/src/main/java/org/readium/r2/streamer/parser/epub/EpubDeobfuscator.kt index a1e5783505..e52fef1835 100644 --- a/readium/streamer/src/main/java/org/readium/r2/streamer/parser/epub/EpubDeobfuscator.kt +++ b/readium/streamer/src/main/java/org/readium/r2/streamer/parser/epub/EpubDeobfuscator.kt @@ -12,6 +12,7 @@ import org.readium.r2.shared.util.Try import org.readium.r2.shared.util.Url import org.readium.r2.shared.util.data.ReadError import org.readium.r2.shared.util.data.ReadTry +import org.readium.r2.shared.util.getEquivalent import org.readium.r2.shared.util.resource.Resource import org.readium.r2.shared.util.resource.TransformingResource import org.readium.r2.shared.util.resource.flatMap @@ -29,7 +30,7 @@ internal class EpubDeobfuscator( @Suppress("Unused_parameter") fun transform(url: Url, resource: Resource): Resource = resource.flatMap { - val algorithm = encryptionData[url]?.algorithm + val algorithm = encryptionData.getEquivalent(url)?.algorithm if (algorithm != null && algorithm2length.containsKey(algorithm)) { DeobfuscatingResource(resource, algorithm) } else { diff --git a/readium/streamer/src/main/java/org/readium/r2/streamer/parser/image/ImageParser.kt b/readium/streamer/src/main/java/org/readium/r2/streamer/parser/image/ImageParser.kt index a65efef755..536f7e4eab 100644 --- a/readium/streamer/src/main/java/org/readium/r2/streamer/parser/image/ImageParser.kt +++ b/readium/streamer/src/main/java/org/readium/r2/streamer/parser/image/ImageParser.kt @@ -23,6 +23,7 @@ import org.readium.r2.shared.util.data.Container import org.readium.r2.shared.util.data.ReadError import org.readium.r2.shared.util.format.Format import org.readium.r2.shared.util.format.Specification +import org.readium.r2.shared.util.getEquivalent import org.readium.r2.shared.util.getOrElse import org.readium.r2.shared.util.logging.WarningLogger import org.readium.r2.shared.util.mediatype.MediaType @@ -81,7 +82,7 @@ public class ImageParser( val readingOrderWithFormat = asset.container - .mapNotNull { url -> entryFormats[url]?.let { url to it } } + .mapNotNull { url -> entryFormats.getEquivalent(url)?.let { url to it } } .filter { (_, format) -> format.specification.specifications.any { it in bitmapSpecifications } } .sortedBy { it.first.toString() } From 613ec87a80b1e8e6a230498196344349e54ef4d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Menu?= Date: Thu, 13 Jun 2024 16:23:21 +0200 Subject: [PATCH 4/6] Warn against using URL equality --- .../org/readium/r2/lcp/license/License.kt | 2 ++ .../readium/r2/navigator/R2BasicWebView.kt | 2 +- .../navigator/epub/EpubNavigatorFragment.kt | 5 +++- .../r2/navigator/media/MediaService.kt | 4 +++- .../java/org/readium/r2/opds/OPDS1Parser.kt | 3 +++ .../org/readium/r2/shared/MediaOverlays.kt | 5 ++-- .../org/readium/r2/shared/publication/Link.kt | 4 ++-- .../readium/r2/shared/publication/Manifest.kt | 2 ++ .../iterators/HtmlResourceContentIterator.kt | 2 ++ .../services/search/StringSearchService.kt | 2 +- .../java/org/readium/r2/shared/util/Url.kt | 24 +++++++++++++++++-- .../r2/shared/util/http/HttpContainer.kt | 2 ++ .../parser/epub/NavigationDocumentParser.kt | 2 ++ .../r2/streamer/parser/epub/NcxParser.kt | 3 +++ .../r2/streamer/parser/epub/SmilParser.kt | 2 ++ .../parser/readium/ReadiumWebPubParser.kt | 2 ++ .../r2/testapp/bookshelf/BookshelfFragment.kt | 2 ++ .../r2/testapp/outline/BookmarksFragment.kt | 4 ++-- 18 files changed, 60 insertions(+), 12 deletions(-) diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/license/License.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/license/License.kt index b46daa7134..63031728c1 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/license/License.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/license/License.kt @@ -31,6 +31,7 @@ import org.readium.r2.lcp.service.DeviceService import org.readium.r2.lcp.service.LcpClient import org.readium.r2.lcp.service.LicensesRepository import org.readium.r2.lcp.service.NetworkService +import org.readium.r2.shared.DelicateReadiumApi import org.readium.r2.shared.InternalReadiumApi import org.readium.r2.shared.extensions.tryOrNull import org.readium.r2.shared.util.Instant @@ -246,6 +247,7 @@ internal class License private constructor( override val canReturnPublication: Boolean get() = status?.link(StatusDocument.Rel.Return) != null + @OptIn(DelicateReadiumApi::class) override suspend fun returnPublication(): Try { try { val status = this.documents.status diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/R2BasicWebView.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/R2BasicWebView.kt index 20b8a209ad..4f3e747418 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/R2BasicWebView.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/R2BasicWebView.kt @@ -587,7 +587,7 @@ internal open class R2BasicWebView(context: Context, attrs: AttributeSet) : WebV val requestUrl = request.url.toUrl() ?: return false // FIXME: I doubt this can work well. hasGesture considers itself unreliable. - return if (urlNotToOverrideLoading == requestUrl && request.hasGesture()) { + return if (urlNotToOverrideLoading?.isEquivalent(requestUrl) == true && request.hasGesture()) { urlNotToOverrideLoading = null false } else { diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorFragment.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorFragment.kt index ebeb6965f2..3fac31a779 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorFragment.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorFragment.kt @@ -785,7 +785,10 @@ public class EpubNavigatorFragment internal constructor( paginationListener?.onPageLoaded() val href = link.url() - if (state is State.Initializing || (state as? State.Loading)?.initialResourceHref == href) { + if (state is State.Initializing || (state as? State.Loading)?.initialResourceHref?.isEquivalent( + href + ) == true + ) { state = State.Ready } diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/media/MediaService.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/media/MediaService.kt index 9a110d442c..a6a4c533f4 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/media/MediaService.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/media/MediaService.kt @@ -29,6 +29,7 @@ import kotlinx.coroutines.flow.* import org.readium.r2.navigator.extensions.let import org.readium.r2.navigator.extensions.splitAt import org.readium.r2.navigator.media.extensions.publicationId +import org.readium.r2.shared.DelicateReadiumApi import org.readium.r2.shared.InternalReadiumApi import org.readium.r2.shared.publication.Link import org.readium.r2.shared.publication.Locator @@ -141,6 +142,7 @@ public open class MediaService : MediaBrowserServiceCompat(), CoroutineScope by private var notificationId: Int? = null private var notification: Notification? = null + @OptIn(DelicateReadiumApi::class) private val mediaPlayerListener = object : MediaPlayer.Listener { /** @@ -168,7 +170,7 @@ public open class MediaService : MediaBrowserServiceCompat(), CoroutineScope by ?.let { navigator.publication.linkWithHref(it) } ?.let { navigator.publication.locatorFromLink(it) } - if (locator != null && href != null && locator.href != href) { + if (locator != null && href != null && locator.href.isEquivalent(href)) { Timber.e( "Ambiguous playback location provided. HREF `$href` doesn't match locator $locator." ) diff --git a/readium/opds/src/main/java/org/readium/r2/opds/OPDS1Parser.kt b/readium/opds/src/main/java/org/readium/r2/opds/OPDS1Parser.kt index 9245fdb9b0..9197ab2a58 100644 --- a/readium/opds/src/main/java/org/readium/r2/opds/OPDS1Parser.kt +++ b/readium/opds/src/main/java/org/readium/r2/opds/OPDS1Parser.kt @@ -12,6 +12,7 @@ package org.readium.r2.opds import java.net.URL +import org.readium.r2.shared.DelicateReadiumApi import org.readium.r2.shared.InternalReadiumApi import org.readium.r2.shared.extensions.toList import org.readium.r2.shared.extensions.toMap @@ -101,6 +102,7 @@ public class OPDS1Parser { public fun parse(xmlData: ByteArray, url: URL): ParseData = throw NotImplementedError() + @OptIn(DelicateReadiumApi::class) private fun parseFeed(root: ElementNode, url: Url): Feed { val feedTitle = root.getFirst("title", Namespaces.Atom)?.text ?: throw Exception(OPDSParserError.MissingTitle.name) @@ -273,6 +275,7 @@ public class OPDS1Parser { }.mapFailure { ErrorException(it) } } + @OptIn(DelicateReadiumApi::class) private fun parseEntry(entry: ElementNode, baseUrl: Url): Publication? { // A title is mandatory val title = entry.getFirst("title", Namespaces.Atom)?.text diff --git a/readium/shared/src/main/java/org/readium/r2/shared/MediaOverlays.kt b/readium/shared/src/main/java/org/readium/r2/shared/MediaOverlays.kt index 1cd4af6df9..261969e4e1 100644 --- a/readium/shared/src/main/java/org/readium/r2/shared/MediaOverlays.kt +++ b/readium/shared/src/main/java/org/readium/r2/shared/MediaOverlays.kt @@ -11,6 +11,7 @@ package org.readium.r2.shared import java.io.Serializable import org.readium.r2.shared.util.Url +import org.readium.r2.shared.util.isEquivalent @InternalReadiumApi public data class MediaOverlays(private val nodes: List = listOf()) : Serializable { @@ -25,7 +26,7 @@ public data class MediaOverlays(private val nodes: List = list for (node in inNodes) { if (node.role.contains("section")) { return findNode(ref, node.children) - } else if (ref == null || node.text == ref) { + } else if (ref.isEquivalent(null) || node.text.isEquivalent(ref)) { return node } } @@ -55,7 +56,7 @@ public data class MediaOverlays(private val nodes: List = list prevNodeFoundFlag = ret.prevFound } // If the node text refer to filename or that filename is null, return node - else if (fragment == null || node.text == fragment) { + else if (fragment.isEquivalent(null) || node.text.isEquivalent(fragment)) { prevNodeFoundFlag = true } } diff --git a/readium/shared/src/main/java/org/readium/r2/shared/publication/Link.kt b/readium/shared/src/main/java/org/readium/r2/shared/publication/Link.kt index 864cbc3c6b..7396ac5060 100644 --- a/readium/shared/src/main/java/org/readium/r2/shared/publication/Link.kt +++ b/readium/shared/src/main/java/org/readium/r2/shared/publication/Link.kt @@ -261,13 +261,13 @@ public data class Link( * Returns the first [Link] with the given [href], or null if not found. */ public fun List.indexOfFirstWithHref(href: Url): Int? = - indexOfFirst { it.url() == href } + indexOfFirst { it.url().isEquivalent(href) } .takeUnless { it == -1 } /** * Finds the first link matching the given HREF. */ -public fun List.firstWithHref(href: Url): Link? = firstOrNull { it.url() == href } +public fun List.firstWithHref(href: Url): Link? = firstOrNull { it.url().isEquivalent(href) } /** * Finds the first link with the given relation. diff --git a/readium/shared/src/main/java/org/readium/r2/shared/publication/Manifest.kt b/readium/shared/src/main/java/org/readium/r2/shared/publication/Manifest.kt index 6237fa21dc..957f90402f 100644 --- a/readium/shared/src/main/java/org/readium/r2/shared/publication/Manifest.kt +++ b/readium/shared/src/main/java/org/readium/r2/shared/publication/Manifest.kt @@ -13,6 +13,7 @@ package org.readium.r2.shared.publication import org.json.JSONArray import org.json.JSONObject +import org.readium.r2.shared.DelicateReadiumApi import org.readium.r2.shared.InternalReadiumApi import org.readium.r2.shared.JSONable import org.readium.r2.shared.extensions.optStringsFromArrayOrSingle @@ -66,6 +67,7 @@ public data class Manifest( * If there's no match, tries again after removing any query parameter and anchor from the * given [href]. */ + @OptIn(DelicateReadiumApi::class) public fun linkWithHref(href: Url): Link? { fun List.deepLinkWithHref(href: Url): Link? { for (l in this) { diff --git a/readium/shared/src/main/java/org/readium/r2/shared/publication/services/content/iterators/HtmlResourceContentIterator.kt b/readium/shared/src/main/java/org/readium/r2/shared/publication/services/content/iterators/HtmlResourceContentIterator.kt index b2c53cad58..e8eea0687f 100644 --- a/readium/shared/src/main/java/org/readium/r2/shared/publication/services/content/iterators/HtmlResourceContentIterator.kt +++ b/readium/shared/src/main/java/org/readium/r2/shared/publication/services/content/iterators/HtmlResourceContentIterator.kt @@ -17,6 +17,7 @@ import org.jsoup.nodes.TextNode import org.jsoup.parser.Parser import org.jsoup.select.NodeTraversor import org.jsoup.select.NodeVisitor +import org.readium.r2.shared.DelicateReadiumApi import org.readium.r2.shared.ExperimentalReadiumApi import org.readium.r2.shared.InternalReadiumApi import org.readium.r2.shared.extensions.tryOrLog @@ -279,6 +280,7 @@ public class HtmlResourceContentIterator internal constructor( ) } + @OptIn(DelicateReadiumApi::class) override fun head(node: Node, depth: Int) { if (node is Element) { val parent = ParentElement(node) diff --git a/readium/shared/src/main/java/org/readium/r2/shared/publication/services/search/StringSearchService.kt b/readium/shared/src/main/java/org/readium/r2/shared/publication/services/search/StringSearchService.kt index 1db499bc6c..f088bc20f9 100644 --- a/readium/shared/src/main/java/org/readium/r2/shared/publication/services/search/StringSearchService.kt +++ b/readium/shared/src/main/java/org/readium/r2/shared/publication/services/search/StringSearchService.kt @@ -356,7 +356,7 @@ private fun List.titleMatching(href: Url): String? { } private fun Link.titleMatching(targetHref: Url): String? { - if (url().removeFragment() == targetHref) { + if (url().removeFragment().isEquivalent(targetHref)) { return title } return children.titleMatching(targetHref) diff --git a/readium/shared/src/main/java/org/readium/r2/shared/util/Url.kt b/readium/shared/src/main/java/org/readium/r2/shared/util/Url.kt index 3c4dda8518..b404d6bf05 100644 --- a/readium/shared/src/main/java/org/readium/r2/shared/util/Url.kt +++ b/readium/shared/src/main/java/org/readium/r2/shared/util/Url.kt @@ -199,6 +199,13 @@ public sealed class Url : Parcelable { override fun toString(): String = uri.toString() + /** + * Returns whether two URLs are strictly equal, by comparing their string representation. + * + * WARNING: Strict URL comparisons can be a source of bug, if the URLs are not normalized. + * In most cases, you should compare using [Url.isEquivalent]. + */ + @DelicateReadiumApi override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false @@ -213,8 +220,11 @@ public sealed class Url : Parcelable { /** * Returns whether the receiver is equivalent to the given `url` after normalization. */ - public fun isEquivalent(url: Url): Boolean = - normalize() == url.normalize() + @OptIn(DelicateReadiumApi::class) + public fun isEquivalent(url: Url?): Boolean { + url ?: return false + return normalize() == url.normalize() + } override fun hashCode(): Int = uri.toString().hashCode() @@ -420,9 +430,19 @@ public value class FileExtension( public fun FileExtension?.appendToFilename(filename: String): String = this?.let { "$filename.$value" } ?: filename +/** + * Returns whether the receiver is equivalent to the given `url` after normalization. + */ +@OptIn(DelicateReadiumApi::class) +public fun Url?.isEquivalent(url: Url?): Boolean { + if (this == null && url == null) return true + return this?.normalize() == url?.normalize() +} + /** * Returns the value of the first key matching `key` after normalization. */ +@OptIn(DelicateReadiumApi::class) public fun Map.getEquivalent(key: Url): T? = get(key) ?: run { val url = key.normalize() diff --git a/readium/shared/src/main/java/org/readium/r2/shared/util/http/HttpContainer.kt b/readium/shared/src/main/java/org/readium/r2/shared/util/http/HttpContainer.kt index a43703e167..31a56e1e9f 100644 --- a/readium/shared/src/main/java/org/readium/r2/shared/util/http/HttpContainer.kt +++ b/readium/shared/src/main/java/org/readium/r2/shared/util/http/HttpContainer.kt @@ -6,6 +6,7 @@ package org.readium.r2.shared.util.http +import org.readium.r2.shared.DelicateReadiumApi import org.readium.r2.shared.util.AbsoluteUrl import org.readium.r2.shared.util.Url import org.readium.r2.shared.util.data.Container @@ -27,6 +28,7 @@ public class HttpContainer( private val client: HttpClient ) : Container { + @OptIn(DelicateReadiumApi::class) override fun get(url: Url): Resource? { // We don't check that url matches any entry because that might save us from edge cases. diff --git a/readium/streamer/src/main/java/org/readium/r2/streamer/parser/epub/NavigationDocumentParser.kt b/readium/streamer/src/main/java/org/readium/r2/streamer/parser/epub/NavigationDocumentParser.kt index 6fb724e5b3..320b30eec7 100644 --- a/readium/streamer/src/main/java/org/readium/r2/streamer/parser/epub/NavigationDocumentParser.kt +++ b/readium/streamer/src/main/java/org/readium/r2/streamer/parser/epub/NavigationDocumentParser.kt @@ -8,6 +8,7 @@ package org.readium.r2.streamer.parser.epub +import org.readium.r2.shared.DelicateReadiumApi import org.readium.r2.shared.InternalReadiumApi import org.readium.r2.shared.publication.Link import org.readium.r2.shared.util.Url @@ -58,6 +59,7 @@ internal object NavigationDocumentParser { private fun parseOlElement(element: ElementNode, filePath: Url): List = element.get("li", Namespaces.XHTML).mapNotNull { parseLiElement(it, filePath) } + @OptIn(DelicateReadiumApi::class) private fun parseLiElement(element: ElementNode, filePath: Url): Link? { val first = element.getAll().firstOrNull() ?: return null // should be , , or
    val title = if (first.name == "ol") { diff --git a/readium/streamer/src/main/java/org/readium/r2/streamer/parser/epub/NcxParser.kt b/readium/streamer/src/main/java/org/readium/r2/streamer/parser/epub/NcxParser.kt index 0ea495c5ac..68c24c3eba 100644 --- a/readium/streamer/src/main/java/org/readium/r2/streamer/parser/epub/NcxParser.kt +++ b/readium/streamer/src/main/java/org/readium/r2/streamer/parser/epub/NcxParser.kt @@ -8,6 +8,7 @@ package org.readium.r2.streamer.parser.epub +import org.readium.r2.shared.DelicateReadiumApi import org.readium.r2.shared.InternalReadiumApi import org.readium.r2.shared.publication.Link import org.readium.r2.shared.util.Url @@ -27,6 +28,7 @@ internal object NcxParser { private fun parseNavMapElement(element: ElementNode, filePath: Url): List = element.get("navPoint", Namespaces.NCX).mapNotNull { parseNavPointElement(it, filePath) } + @OptIn(DelicateReadiumApi::class) private fun parsePageListElement(element: ElementNode, filePath: Url): List = element.get("pageTarget", Namespaces.NCX).mapNotNull { val href = extractHref(it, filePath) @@ -38,6 +40,7 @@ internal object NcxParser { } } + @OptIn(DelicateReadiumApi::class) private fun parseNavPointElement(element: ElementNode, filePath: Url): Link? { val title = extractTitle(element) val href = extractHref(element, filePath) diff --git a/readium/streamer/src/main/java/org/readium/r2/streamer/parser/epub/SmilParser.kt b/readium/streamer/src/main/java/org/readium/r2/streamer/parser/epub/SmilParser.kt index 47aa5314d4..98404b0f68 100644 --- a/readium/streamer/src/main/java/org/readium/r2/streamer/parser/epub/SmilParser.kt +++ b/readium/streamer/src/main/java/org/readium/r2/streamer/parser/epub/SmilParser.kt @@ -8,6 +8,7 @@ package org.readium.r2.streamer.parser.epub +import org.readium.r2.shared.DelicateReadiumApi import org.readium.r2.shared.InternalReadiumApi import org.readium.r2.shared.MediaOverlayNode import org.readium.r2.shared.MediaOverlays @@ -28,6 +29,7 @@ internal object SmilParser { return parseSeq(body, filePath)?.let { MediaOverlays(it) } } + @OptIn(DelicateReadiumApi::class) private fun parseSeq(node: ElementNode, filePath: Url): List? { val children: MutableList = mutableListOf() for (child in node.getAll()) { diff --git a/readium/streamer/src/main/java/org/readium/r2/streamer/parser/readium/ReadiumWebPubParser.kt b/readium/streamer/src/main/java/org/readium/r2/streamer/parser/readium/ReadiumWebPubParser.kt index 605b2e46ec..e59fd27ee3 100644 --- a/readium/streamer/src/main/java/org/readium/r2/streamer/parser/readium/ReadiumWebPubParser.kt +++ b/readium/streamer/src/main/java/org/readium/r2/streamer/parser/readium/ReadiumWebPubParser.kt @@ -9,6 +9,7 @@ package org.readium.r2.streamer.parser.readium import android.content.Context +import org.readium.r2.shared.DelicateReadiumApi import org.readium.r2.shared.InternalReadiumApi import org.readium.r2.shared.publication.Manifest import org.readium.r2.shared.publication.Publication @@ -143,6 +144,7 @@ public class ReadiumWebPubParser( } } + @OptIn(DelicateReadiumApi::class) private suspend fun parseResourceAsset( resource: Resource, formatSpecification: FormatSpecification diff --git a/test-app/src/main/java/org/readium/r2/testapp/bookshelf/BookshelfFragment.kt b/test-app/src/main/java/org/readium/r2/testapp/bookshelf/BookshelfFragment.kt index 54c41a57c5..ed12d9a22d 100644 --- a/test-app/src/main/java/org/readium/r2/testapp/bookshelf/BookshelfFragment.kt +++ b/test-app/src/main/java/org/readium/r2/testapp/bookshelf/BookshelfFragment.kt @@ -26,6 +26,7 @@ import androidx.recyclerview.widget.RecyclerView import com.google.android.material.dialog.MaterialAlertDialogBuilder import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch +import org.readium.r2.shared.DelicateReadiumApi import org.readium.r2.shared.util.AbsoluteUrl import org.readium.r2.testapp.Application import org.readium.r2.testapp.R @@ -137,6 +138,7 @@ class BookshelfFragment : Fragment() { } } + @OptIn(DelicateReadiumApi::class) private fun askForRemoteUrl() { val urlEditText = EditText(requireContext()) MaterialAlertDialogBuilder(requireContext()) diff --git a/test-app/src/main/java/org/readium/r2/testapp/outline/BookmarksFragment.kt b/test-app/src/main/java/org/readium/r2/testapp/outline/BookmarksFragment.kt index bb86f30489..359a72d13b 100644 --- a/test-app/src/main/java/org/readium/r2/testapp/outline/BookmarksFragment.kt +++ b/test-app/src/main/java/org/readium/r2/testapp/outline/BookmarksFragment.kt @@ -161,12 +161,12 @@ class BookmarkAdapter( private fun getBookSpineItem(href: Url): String? { for (link in publication.tableOfContents) { - if (link.url() == href) { + if (link.url().isEquivalent(href)) { return link.outlineTitle } } for (link in publication.readingOrder) { - if (link.url() == href) { + if (link.url().isEquivalent(href)) { return link.outlineTitle } } From e4b77d08655bc2f73031e88d3672466f1a3a1535 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Menu?= Date: Thu, 13 Jun 2024 16:56:02 +0200 Subject: [PATCH 5/6] Fix tests --- .../readium/r2/streamer/parser/epub/EpubPositionsServiceTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readium/streamer/src/test/java/org/readium/r2/streamer/parser/epub/EpubPositionsServiceTest.kt b/readium/streamer/src/test/java/org/readium/r2/streamer/parser/epub/EpubPositionsServiceTest.kt index 090ef92419..75af21fe46 100644 --- a/readium/streamer/src/test/java/org/readium/r2/streamer/parser/epub/EpubPositionsServiceTest.kt +++ b/readium/streamer/src/test/java/org/readium/r2/streamer/parser/epub/EpubPositionsServiceTest.kt @@ -493,7 +493,7 @@ class EpubPositionsServiceTest { container = object : Container { private fun find(relativePath: Url): ReadingOrderItem? = - readingOrder.find { it.link.url() == relativePath } + readingOrder.find { it.link.url().isEquivalent(relativePath) } override val entries: Set = readingOrder.map { it.href }.toSet() From bfccab64a30643a0a75ace73a4dc1ebf216c24eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Menu?= Date: Fri, 14 Jun 2024 10:36:04 +0200 Subject: [PATCH 6/6] Fixes --- .../src/main/java/org/readium/r2/shared/MediaOverlays.kt | 6 ++++-- .../src/test/java/org/readium/r2/shared/util/UrlTest.kt | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/readium/shared/src/main/java/org/readium/r2/shared/MediaOverlays.kt b/readium/shared/src/main/java/org/readium/r2/shared/MediaOverlays.kt index 261969e4e1..8fe4f2e155 100644 --- a/readium/shared/src/main/java/org/readium/r2/shared/MediaOverlays.kt +++ b/readium/shared/src/main/java/org/readium/r2/shared/MediaOverlays.kt @@ -22,11 +22,12 @@ public data class MediaOverlays(private val nodes: List = list private fun nodeForFragment(ref: Url?): MediaOverlayNode? = findNode(ref, this.nodes) + @OptIn(DelicateReadiumApi::class) private fun findNode(ref: Url?, inNodes: List): MediaOverlayNode? { for (node in inNodes) { if (node.role.contains("section")) { return findNode(ref, node.children) - } else if (ref.isEquivalent(null) || node.text.isEquivalent(ref)) { + } else if (ref == null || node.text.isEquivalent(ref)) { return node } } @@ -37,6 +38,7 @@ public data class MediaOverlays(private val nodes: List = list private fun nodeAfterFragment(ref: Url?): MediaOverlayNode? = findNextNode(ref, this.nodes).found + @OptIn(DelicateReadiumApi::class) private fun findNextNode(fragment: Url?, inNodes: List): NextNodeResult { var prevNodeFoundFlag = false // For each node of the current scope... @@ -56,7 +58,7 @@ public data class MediaOverlays(private val nodes: List = list prevNodeFoundFlag = ret.prevFound } // If the node text refer to filename or that filename is null, return node - else if (fragment.isEquivalent(null) || node.text.isEquivalent(fragment)) { + else if (fragment == null || node.text.isEquivalent(fragment)) { prevNodeFoundFlag = true } } diff --git a/readium/shared/src/test/java/org/readium/r2/shared/util/UrlTest.kt b/readium/shared/src/test/java/org/readium/r2/shared/util/UrlTest.kt index f117f35e92..22b037fbb2 100644 --- a/readium/shared/src/test/java/org/readium/r2/shared/util/UrlTest.kt +++ b/readium/shared/src/test/java/org/readium/r2/shared/util/UrlTest.kt @@ -408,10 +408,10 @@ class UrlTest { Url("HTTP://example.com")!!.normalize().toString() ) - // Path is percent-decoded. + // Percent encoding of path is normalized. assertEquals( "http://example.com/c'est%20valide", - Url("HTTP://example.com/c%27est%20valide")!!.normalize().toString() + Url("http://example.com/c%27est%20valide")!!.normalize().toString() ) assertEquals( "c'est%20valide",