okhttp
288 строк · 8.2 Кб
1/*
2* Copyright (C) 2023 Square, Inc.
3*
4* Licensed under the Apache License, Version 2.0 (the "License");
5* you may not use this file except in compliance with the License.
6* You may obtain a copy of the License at
7*
8* http://www.apache.org/licenses/LICENSE-2.0
9*
10* Unless required by applicable law or agreed to in writing, software
11* distributed under the License is distributed on an "AS IS" BASIS,
12* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13* See the License for the specific language governing permissions and
14* limitations under the License.
15*/
16package okhttp3.internal.idn17
18import kotlin.math.abs19import kotlin.streams.toList20import okio.Buffer21
22/** Index [table] for compactness as specified by `IdnaMappingTable`. */
23fun buildIdnaMappingTableData(table: SimpleIdnaMappingTable): IdnaMappingTableData {24val simplified = mergeAdjacentRanges(table.mappings)25val withoutSectionSpans = withoutSectionSpans(simplified)26val sections = sections(withoutSectionSpans)27
28val rangesBuffer = Buffer()29val mappingsBuffer = StringBuilder()30val sectionIndexBuffer = Buffer()31
32var previousMappedRanges: List<MappedRange>? = null33
34for ((section, sectionMappedRanges) in sections) {35// Skip sequential ranges when they are equal.36if (sectionMappedRanges == previousMappedRanges) continue37previousMappedRanges = sectionMappedRanges38
39val sectionOffset = rangesBuffer.size.toInt() / 440
41// Section prefix.42sectionIndexBuffer.writeByte(section and 0x1fc000 shr 14)43sectionIndexBuffer.writeByte((section and 0x3f80) shr 7)44
45// Section index.46sectionIndexBuffer.writeByte((sectionOffset and 0x3f80) shr 7)47sectionIndexBuffer.writeByte(sectionOffset and 0x7f)48
49// Ranges.50for (range in sectionMappedRanges) {51rangesBuffer.writeByte(range.rangeStart)52
53when (range) {54is MappedRange.Constant -> {55rangesBuffer.writeByte(range.b1)56rangesBuffer.writeByte('-'.code)57rangesBuffer.writeByte('-'.code)58}59is MappedRange.Inline1 -> {60rangesBuffer.writeByte(range.b1)61rangesBuffer.writeByte(range.b2)62rangesBuffer.writeByte('-'.code)63}64is MappedRange.Inline2 -> {65rangesBuffer.writeByte(range.b1)66rangesBuffer.writeByte(range.b2)67rangesBuffer.writeByte(range.b3)68}69is MappedRange.InlineDelta -> {70rangesBuffer.writeByte(range.b1)71rangesBuffer.writeByte(range.b2)72rangesBuffer.writeByte(range.b3)73}74is MappedRange.External -> {75// Write the mapping.76val mappingOffset: Int77val mappedTo = range.mappedTo.utf8()78val mappingIndex = mappingsBuffer.indexOf(mappedTo)79if (mappingIndex == -1) {80mappingOffset = mappingsBuffer.length81mappingsBuffer.append(mappedTo)82} else {83mappingOffset = mappingIndex84}85
86// Write the range bytes.87val b1 = mappedTo.length88val b2 = (mappingOffset and 0x3f80) shr 789val b3 = mappingOffset and 0x7f90rangesBuffer.writeByte(b1)91rangesBuffer.writeByte(b2)92rangesBuffer.writeByte(b3)93}94}95}96}97
98return IdnaMappingTableData(99sections = sectionIndexBuffer.readUtf8(),100ranges = rangesBuffer.readUtf8(),101mappings = mappingsBuffer.toString(),102)103}
104
105/**
106* If [mapping] qualifies to be encoded as [MappedRange.InlineDelta] return new instance, otherwise null.
107* An [MappedRange.InlineDelta] must be a mapping from a single code-point to a single code-point with a difference
108* that can be represented in 2^18-1.
109*/
110internal fun inlineDeltaOrNull(mapping: Mapping): MappedRange.InlineDelta? {111if (mapping.hasSingleSourceCodePoint) {112val sourceCodePoint = mapping.sourceCodePoint0113val mappedCodePoints = mapping.mappedTo.utf8().codePoints().toList()114if (mappedCodePoints.size == 1) {115val codePointDelta = mappedCodePoints.single() - sourceCodePoint116if (MappedRange.InlineDelta.MAX_VALUE >= abs(codePointDelta)) {117return MappedRange.InlineDelta(mapping.rangeStart, codePointDelta)118}119}120}121return null122}
123
124/**
125* Inputs must have applied [withoutSectionSpans].
126*/
127internal fun sections(mappings: List<Mapping>): Map<Int, List<MappedRange>> {128val result = mutableMapOf<Int, MutableList<MappedRange>>()129
130for (mapping in mappings) {131require(!mapping.spansSections)132
133val section = mapping.section134val rangeStart = mapping.rangeStart135
136val sectionList = result.getOrPut(section) { mutableListOf() }137
138sectionList +=139when (mapping.type) {140TYPE_MAPPED ->141run {142val deltaMapping = inlineDeltaOrNull(mapping)143if (deltaMapping != null) {144return@run deltaMapping145}146
147when (mapping.mappedTo.size) {1481 -> MappedRange.Inline1(rangeStart, mapping.mappedTo)1492 -> MappedRange.Inline2(rangeStart, mapping.mappedTo)150else -> MappedRange.External(rangeStart, mapping.mappedTo)151}152}153
154TYPE_IGNORED, TYPE_VALID, TYPE_DISALLOWED -> {155MappedRange.Constant(rangeStart, mapping.type)156}157
158else -> error("unexpected mapping type: ${mapping.type}")159}160}161
162for (sectionList in result.values) {163mergeAdjacentDeltaMappedRanges(sectionList)164}165
166return result.toMap()167}
168
169/**
170* Modifies [ranges] to combine any adjacent [MappedRange.InlineDelta] of same size to single entry.
171* @returns same instance of [ranges] for convenience
172*/
173internal fun mergeAdjacentDeltaMappedRanges(ranges: MutableList<MappedRange>): MutableList<MappedRange> {174var i = 0175while (i < ranges.size) {176val curr = ranges[i]177if (curr is MappedRange.InlineDelta) {178val j = i + 1179mergeAdjacent@ while (j < ranges.size) {180val next = ranges[j]181if (next is MappedRange.InlineDelta &&182curr.codepointDelta == next.codepointDelta183) {184ranges.removeAt(j)185} else {186break@mergeAdjacent187}188}189}190i++191}192return ranges193}
194
195/**
196* Returns a copy of [mappings], splitting to ensure that each mapping is entirely contained within
197* a single section.
198*/
199internal fun withoutSectionSpans(mappings: List<Mapping>): List<Mapping> {200val result = mutableListOf<Mapping>()201
202val i = mappings.iterator()203var current = i.next()204
205while (true) {206if (current.spansSections) {207result +=208Mapping(209current.sourceCodePoint0,210current.section + 0x7f,211current.type,212current.mappedTo,213)214current =215Mapping(216current.section + 0x80,217current.sourceCodePoint1,218current.type,219current.mappedTo,220)221} else {222result += current223current = if (i.hasNext()) i.next() else break224}225}226
227return result228}
229
230/** Returns a copy of [mappings] with adjacent ranges merged wherever possible. */
231internal fun mergeAdjacentRanges(mappings: List<Mapping>): List<Mapping> {232var index = 0233val result = mutableListOf<Mapping>()234
235while (index < mappings.size) {236val mapping = mappings[index]237val type = canonicalizeType(mapping.type)238val mappedTo = mapping.mappedTo239
240var unionWith: Mapping = mapping241index++242
243while (index < mappings.size) {244val next = mappings[index]245
246if (type != canonicalizeType(next.type)) break247if (type == TYPE_MAPPED && mappedTo != next.mappedTo) break248
249unionWith = next250index++251}252
253result +=254Mapping(255sourceCodePoint0 = mapping.sourceCodePoint0,256sourceCodePoint1 = unionWith.sourceCodePoint1,257type = type,258mappedTo = mappedTo,259)260}261
262return result263}
264
265internal fun canonicalizeType(type: Int): Int {266return when (type) {267TYPE_IGNORED -> TYPE_IGNORED268
269TYPE_MAPPED,270TYPE_DISALLOWED_STD3_MAPPED,271-> TYPE_MAPPED272
273TYPE_DEVIATION,274TYPE_DISALLOWED_STD3_VALID,275TYPE_VALID,276-> TYPE_VALID277
278TYPE_DISALLOWED -> TYPE_DISALLOWED279
280else -> error("unexpected type: $type")281}282}
283
284internal infix fun Byte.and(mask: Int): Int = toInt() and mask285
286internal infix fun Short.and(mask: Int): Int = toInt() and mask287
288internal infix fun Int.and(mask: Long): Long = toLong() and mask289