okhttp3.internal.hostnames.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of impersonator Show documentation
Show all versions of impersonator Show documentation
Spoof TLS/JA3/JA4 and HTTP/2 fingerprints in Java
The newest version!
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp3.internal
import java.net.IDN
import java.net.InetAddress
import java.util.Arrays
import java.util.Locale
import okio.Buffer
/**
* If this is an IP address, this returns the IP address in canonical form.
*
* Otherwise this performs IDN ToASCII encoding and canonicalize the result to lowercase. For
* example this converts `☃.net` to `xn--n3h.net`, and `WwW.GoOgLe.cOm` to `www.google.com`.
* `null` will be returned if the host cannot be ToASCII encoded or if the result contains
* unsupported ASCII characters.
*/
fun String.toCanonicalHost(): String? {
val host: String = this
// If the input contains a :, it’s an IPv6 address.
if (":" in host) {
// If the input is encased in square braces "[...]", drop 'em.
val inetAddress = (if (host.startsWith("[") && host.endsWith("]")) {
decodeIpv6(host, 1, host.length - 1)
} else {
decodeIpv6(host, 0, host.length)
}) ?: return null
val address = inetAddress.address
if (address.size == 16) return inet6AddressToAscii(address)
if (address.size == 4) return inetAddress.hostAddress // An IPv4-mapped IPv6 address.
throw AssertionError("Invalid IPv6 address: '$host'")
}
try {
val result = IDN.toASCII(host).toLowerCase(Locale.US)
if (result.isEmpty()) return null
// Confirm that the IDN ToASCII result doesn't contain any illegal characters.
return if (result.containsInvalidHostnameAsciiCodes()) {
null
} else {
result // TODO: implement all label limits.
}
} catch (_: IllegalArgumentException) {
return null
}
}
private fun String.containsInvalidHostnameAsciiCodes(): Boolean {
for (i in 0 until length) {
val c = this[i]
// The WHATWG Host parsing rules accepts some character codes which are invalid by
// definition for OkHttp's host header checks (and the WHATWG Host syntax definition). Here
// we rule out characters that would cause problems in host headers.
if (c <= '\u001f' || c >= '\u007f') {
return true
}
// Check for the characters mentioned in the WHATWG Host parsing spec:
// U+0000, U+0009, U+000A, U+000D, U+0020, "#", "%", "/", ":", "?", "@", "[", "\", and "]"
// (excluding the characters covered above).
if (" #%/:?@[\\]".indexOf(c) != -1) {
return true
}
}
return false
}
/** Decodes an IPv6 address like 1111:2222:3333:4444:5555:6666:7777:8888 or ::1. */
private fun decodeIpv6(input: String, pos: Int, limit: Int): InetAddress? {
val address = ByteArray(16)
var b = 0
var compress = -1
var groupOffset = -1
var i = pos
while (i < limit) {
if (b == address.size) return null // Too many groups.
// Read a delimiter.
if (i + 2 <= limit && input.startsWith("::", startIndex = i)) {
// Compression "::" delimiter, which is anywhere in the input, including its prefix.
if (compress != -1) return null // Multiple "::" delimiters.
i += 2
b += 2
compress = b
if (i == limit) break
} else if (b != 0) {
// Group separator ":" delimiter.
if (input.startsWith(":", startIndex = i)) {
i++
} else if (input.startsWith(".", startIndex = i)) {
// If we see a '.', rewind to the beginning of the previous group and parse as IPv4.
if (!decodeIpv4Suffix(input, groupOffset, limit, address, b - 2)) return null
b += 2 // We rewound two bytes and then added four.
break
} else {
return null // Wrong delimiter.
}
}
// Read a group, one to four hex digits.
var value = 0
groupOffset = i
while (i < limit) {
val hexDigit = input[i].parseHexDigit()
if (hexDigit == -1) break
value = (value shl 4) + hexDigit
i++
}
val groupLength = i - groupOffset
if (groupLength == 0 || groupLength > 4) return null // Group is the wrong size.
// We've successfully read a group. Assign its value to our byte array.
address[b++] = (value.ushr(8) and 0xff).toByte()
address[b++] = (value and 0xff).toByte()
}
// All done. If compression happened, we need to move bytes to the right place in the
// address. Here's a sample:
//
// input: "1111:2222:3333::7777:8888"
// before: { 11, 11, 22, 22, 33, 33, 00, 00, 77, 77, 88, 88, 00, 00, 00, 00 }
// compress: 6
// b: 10
// after: { 11, 11, 22, 22, 33, 33, 00, 00, 00, 00, 00, 00, 77, 77, 88, 88 }
//
if (b != address.size) {
if (compress == -1) return null // Address didn't have compression or enough groups.
System.arraycopy(address, compress, address, address.size - (b - compress), b - compress)
Arrays.fill(address, compress, compress + (address.size - b), 0.toByte())
}
return InetAddress.getByAddress(address)
}
/** Decodes an IPv4 address suffix of an IPv6 address, like 1111::5555:6666:192.168.0.1. */
private fun decodeIpv4Suffix(
input: String,
pos: Int,
limit: Int,
address: ByteArray,
addressOffset: Int
): Boolean {
var b = addressOffset
var i = pos
while (i < limit) {
if (b == address.size) return false // Too many groups.
// Read a delimiter.
if (b != addressOffset) {
if (input[i] != '.') return false // Wrong delimiter.
i++
}
// Read 1 or more decimal digits for a value in 0..255.
var value = 0
val groupOffset = i
while (i < limit) {
val c = input[i]
if (c < '0' || c > '9') break
if (value == 0 && groupOffset != i) return false // Reject unnecessary leading '0's.
value = value * 10 + c.toInt() - '0'.toInt()
if (value > 255) return false // Value out of range.
i++
}
val groupLength = i - groupOffset
if (groupLength == 0) return false // No digits.
// We've successfully read a byte.
address[b++] = value.toByte()
}
// Check for too few groups. We wanted exactly four.
return b == addressOffset + 4
}
/** Encodes an IPv6 address in canonical form according to RFC 5952. */
private fun inet6AddressToAscii(address: ByteArray): String {
// Go through the address looking for the longest run of 0s. Each group is 2-bytes.
// A run must be longer than one group (section 4.2.2).
// If there are multiple equal runs, the first one must be used (section 4.2.3).
var longestRunOffset = -1
var longestRunLength = 0
run {
var i = 0
while (i < address.size) {
val currentRunOffset = i
while (i < 16 && address[i].toInt() == 0 && address[i + 1].toInt() == 0) {
i += 2
}
val currentRunLength = i - currentRunOffset
if (currentRunLength > longestRunLength && currentRunLength >= 4) {
longestRunOffset = currentRunOffset
longestRunLength = currentRunLength
}
i += 2
}
}
// Emit each 2-byte group in hex, separated by ':'. The longest run of zeroes is "::".
val result = Buffer()
var i = 0
while (i < address.size) {
if (i == longestRunOffset) {
result.writeByte(':'.toInt())
i += longestRunLength
if (i == 16) result.writeByte(':'.toInt())
} else {
if (i > 0) result.writeByte(':'.toInt())
val group = address[i] and 0xff shl 8 or (address[i + 1] and 0xff)
result.writeHexadecimalUnsignedLong(group.toLong())
i += 2
}
}
return result.readUtf8()
}