Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for IDNA 2008 (RFC 5891) when encoding URL (#819) #821

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions buildSrc/src/main/kotlin/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -238,3 +238,8 @@ object Stetho {
const val dependency = "com.facebook.stetho:stetho-urlconnection:$version"
}
}

object ICU {
const val version = "70.1"
const val dependency = "com.ibm.icu:icu4j:$version"
}
1 change: 1 addition & 0 deletions fuel/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
dependencies {
api(Result.dependency)
implementation(ICU.dependency)

testImplementation(project(Fuel.Test.name))
testImplementation(Json.dependency)
Expand Down
36 changes: 30 additions & 6 deletions fuel/src/main/kotlin/com/github/kittinunf/fuel/core/Encoding.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.github.kittinunf.fuel.core

import com.github.kittinunf.fuel.core.requests.DefaultRequest
import com.ibm.icu.text.IDNA
import java.net.MalformedURLException
import java.net.URI
import java.net.URISyntaxException
import java.net.URL

class Encoding(
Expand All @@ -13,6 +13,8 @@ class Encoding(
val parameters: Parameters? = null
) : RequestFactory.RequestConvertible {

private val idna: IDNA = IDNA.getUTS46Instance(flags)

private val encoder: (Method, String, Parameters?) -> Request = { method, path, parameters ->
DefaultRequest(
method = method,
Expand All @@ -36,13 +38,35 @@ class Encoding(
}
URL(base + if (path.startsWith('/') or path.isEmpty()) path else "/$path")
}
val uri = try {
url.toURI()
} catch (e: URISyntaxException) {
URI(url.protocol, url.userInfo, url.host, url.port, url.path, url.query, url.ref)
}

val uri = URI(
url.protocol,
url.userInfo,
// converts domain to A-Label (RFC 5891)
domainToAscii(url.host),
url.port,
url.path,
url.query,
url.ref
)

return URL(uri.toASCIIString())
}

private fun domainToAscii(domain: String): String {
val info = IDNA.Info()
val sb = StringBuilder()
val domainAscii = idna.nameToASCII(domain, sb, info).toString()
if (info.hasErrors()) {
throw MalformedURLException(info.errors.toString())
}
return domainAscii
}

private val defaultHeaders = Headers.from()

companion object {
private const val flags =
IDNA.CHECK_BIDI or IDNA.CHECK_CONTEXTJ or IDNA.CHECK_CONTEXTO or IDNA.NONTRANSITIONAL_TO_ASCII or IDNA.USE_STD3_RULES
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.github.kittinunf.fuel.core

import java.net.MalformedURLException
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Test
import org.junit.experimental.runners.Enclosed
import org.junit.runner.RunWith
import org.junit.runners.Parameterized

@RunWith(Enclosed::class)
internal class EncodingTest {

@RunWith(Parameterized::class)
internal class Valid(
private val inputUrl: String,
private val encodedUrl: String
) {

companion object {
@JvmStatic
@Parameterized.Parameters(name = "validUrl")
fun data() = listOf(
arrayOf("https://github.com/kittinunf/fuel/", "https://github.com/kittinunf/fuel/"),
arrayOf(
"https://xn----f38am99bqvcd5liy1cxsg.test",
"https://xn----f38am99bqvcd5liy1cxsg.test"
),
arrayOf("https://test.xn--rhqv96g", "https://test.xn--rhqv96g"),
arrayOf("https://test.شبك", "https://test.xn--ngbx0c"),
arrayOf("https://普遍接受-测试.top", "https://xn----f38am99bqvcd5liy1cxsg.top"),
arrayOf(
"https://मेल.डाटामेल.भारत",
"https://xn--r2bi6d.xn--c2bd4bq1db8d.xn--h2brj9c"
),
arrayOf("http://fußball.de", "http://xn--fuball-cta.de"),
arrayOf("http://fußball.de", "http://xn--fuball-cta.de"),
)
}

@Test
fun testRequestURLIDNAEncoding() {
val encoding = Encoding(
httpMethod = Method.GET,
urlString = inputUrl,
)
assertThat(encoding.request.url.toString(), equalTo(encodedUrl))
}
}


@RunWith(Parameterized::class)
internal class Invalid(
private val inputUrl: String
) {

companion object {
@JvmStatic
@Parameterized.Parameters(name = "invalidUrl")
fun data() = listOf(
"https://in--valid",
"https://.test.top",
"https://\\u0557w.test"
)
}

@Test(expected = MalformedURLException::class)
fun testRequestInvalidURL() {
Encoding(httpMethod = Method.GET, urlString = inputUrl).request
}
}
}