okhttp

Форк
0
267 строк · 6.8 Кб
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
 */
16
package okhttp3.internal.idn
17

18
import java.io.IOException
19
import okio.Buffer
20
import okio.BufferedSink
21
import okio.BufferedSource
22
import okio.ByteString
23
import okio.ByteString.Companion.encodeUtf8
24
import okio.Options
25

26
/**
27
 * A decoded [mapping table] that can perform the [mapping step] of IDNA processing.
28
 *
29
 * This implementation is optimized for readability over efficiency.
30
 *
31
 * This implements non-transitional processing by preserving deviation characters.
32
 *
33
 * This implementation's STD3 rules are configured to `UseSTD3ASCIIRules=false`. This is
34
 * permissive and permits the `_` character.
35
 *
36
 * [mapping table]: https://www.unicode.org/reports/tr46/#IDNA_Mapping_Table
37
 * [mapping step]: https://www.unicode.org/reports/tr46/#ProcessingStepMap
38
 */
39
class SimpleIdnaMappingTable internal constructor(
40
  internal val mappings: List<Mapping>,
41
) {
42
  /**
43
   * Returns true if the [codePoint] was applied successfully. Returns false if it was disallowed.
44
   */
45
  fun map(
46
    codePoint: Int,
47
    sink: BufferedSink,
48
  ): Boolean {
49
    val index =
50
      mappings.binarySearch {
51
        when {
52
          it.sourceCodePoint1 < codePoint -> -1
53
          it.sourceCodePoint0 > codePoint -> 1
54
          else -> 0
55
        }
56
      }
57

58
    // Code points must be in 0..0x10ffff.
59
    require(index in mappings.indices) { "unexpected code point: $codePoint" }
60

61
    val mapping = mappings[index]
62
    var result = true
63

64
    when (mapping.type) {
65
      TYPE_IGNORED -> Unit
66
      TYPE_MAPPED, TYPE_DISALLOWED_STD3_MAPPED -> {
67
        sink.write(mapping.mappedTo)
68
      }
69

70
      TYPE_DEVIATION, TYPE_DISALLOWED_STD3_VALID, TYPE_VALID -> {
71
        sink.writeUtf8CodePoint(codePoint)
72
      }
73

74
      TYPE_DISALLOWED -> {
75
        sink.writeUtf8CodePoint(codePoint)
76
        result = false
77
      }
78
    }
79

80
    return result
81
  }
82
}
83

84
private val optionsDelimiter =
85
  Options.of(
86
    // 0.
87
    ".".encodeUtf8(),
88
    // 1.
89
    " ".encodeUtf8(),
90
    // 2.
91
    ";".encodeUtf8(),
92
    // 3.
93
    "#".encodeUtf8(),
94
    // 4.
95
    "\n".encodeUtf8(),
96
  )
97

98
private val optionsDot =
99
  Options.of(
100
    // 0.
101
    ".".encodeUtf8(),
102
  )
103

104
private const val DELIMITER_DOT = 0
105
private const val DELIMITER_SPACE = 1
106
private const val DELIMITER_SEMICOLON = 2
107
private const val DELIMITER_HASH = 3
108
private const val DELIMITER_NEWLINE = 4
109

110
private val optionsType =
111
  Options.of(
112
    // 0.
113
    "deviation ".encodeUtf8(),
114
    // 1.
115
    "disallowed ".encodeUtf8(),
116
    // 2.
117
    "disallowed_STD3_mapped ".encodeUtf8(),
118
    // 3.
119
    "disallowed_STD3_valid ".encodeUtf8(),
120
    // 4.
121
    "ignored ".encodeUtf8(),
122
    // 5.
123
    "mapped ".encodeUtf8(),
124
    // 6.
125
    "valid ".encodeUtf8(),
126
  )
127

128
internal const val TYPE_DEVIATION = 0
129
internal const val TYPE_DISALLOWED = 1
130
internal const val TYPE_DISALLOWED_STD3_MAPPED = 2
131
internal const val TYPE_DISALLOWED_STD3_VALID = 3
132
internal const val TYPE_IGNORED = 4
133
internal const val TYPE_MAPPED = 5
134
internal const val TYPE_VALID = 6
135

136
private fun BufferedSource.skipWhitespace() {
137
  while (!exhausted()) {
138
    if (buffer[0] != ' '.code.toByte()) return
139
    skip(1L)
140
  }
141
}
142

143
private fun BufferedSource.skipRestOfLine() {
144
  when (val newline = indexOf('\n'.code.toByte())) {
145
    -1L -> skip(buffer.size) // Exhaust this source.
146
    else -> skip(newline + 1)
147
  }
148
}
149

150
/**
151
 * Reads lines from `IdnaMappingTable.txt`.
152
 *
153
 * Comment lines are either blank or start with a `#` character. Lines may also end with a comment.
154
 * All comments are ignored.
155
 *
156
 * Regular lines contain fields separated by semicolons.
157
 *
158
 * The first element on each line is a single hex code point (like 0041) or a hex code point range
159
 * (like 0030..0039).
160
 *
161
 * The second element on each line is a mapping type, like `valid` or `mapped`.
162
 *
163
 * For lines that contain a mapping target, the next thing is a sequence of hex code points (like
164
 * 0031 2044 0034).
165
 *
166
 * All other data is ignored.
167
 */
168
fun BufferedSource.readPlainTextIdnaMappingTable(): SimpleIdnaMappingTable {
169
  val mappedTo = Buffer()
170
  val result = mutableListOf<Mapping>()
171

172
  while (!exhausted()) {
173
    // Skip comment and empty lines.
174
    when (select(optionsDelimiter)) {
175
      DELIMITER_HASH -> {
176
        skipRestOfLine()
177
        continue
178
      }
179

180
      DELIMITER_NEWLINE -> {
181
        continue
182
      }
183

184
      DELIMITER_DOT, DELIMITER_SPACE, DELIMITER_SEMICOLON -> {
185
        throw IOException("unexpected delimiter")
186
      }
187
    }
188

189
    // "002F" or "0000..002C"
190
    val sourceCodePoint0 = readHexadecimalUnsignedLong()
191
    val sourceCodePoint1 =
192
      when (select(optionsDot)) {
193
        DELIMITER_DOT -> {
194
          if (readByte() != '.'.code.toByte()) throw IOException("expected '..'")
195
          readHexadecimalUnsignedLong()
196
        }
197

198
        else -> sourceCodePoint0
199
      }
200

201
    skipWhitespace()
202
    if (readByte() != ';'.code.toByte()) throw IOException("expected ';'")
203

204
    // "valid" or "mapped"
205
    skipWhitespace()
206
    val type = select(optionsType)
207

208
    when (type) {
209
      TYPE_DEVIATION, TYPE_MAPPED, TYPE_DISALLOWED_STD3_MAPPED -> {
210
        skipWhitespace()
211
        if (readByte() != ';'.code.toByte()) throw IOException("expected ';'")
212

213
        // Like "0061" or "0031 2044 0034".
214
        while (true) {
215
          skipWhitespace()
216

217
          when (select(optionsDelimiter)) {
218
            DELIMITER_HASH -> {
219
              break
220
            }
221

222
            DELIMITER_DOT, DELIMITER_SEMICOLON, DELIMITER_NEWLINE -> {
223
              throw IOException("unexpected delimiter")
224
            }
225
          }
226

227
          mappedTo.writeUtf8CodePoint(readHexadecimalUnsignedLong().toInt())
228
        }
229
      }
230

231
      TYPE_DISALLOWED, TYPE_DISALLOWED_STD3_VALID, TYPE_IGNORED, TYPE_VALID -> Unit
232

233
      else -> throw IOException("unexpected type")
234
    }
235

236
    skipRestOfLine()
237

238
    result +=
239
      Mapping(
240
        sourceCodePoint0.toInt(),
241
        sourceCodePoint1.toInt(),
242
        type,
243
        mappedTo.readByteString(),
244
      )
245
  }
246

247
  return SimpleIdnaMappingTable(result)
248
}
249

250
internal data class Mapping(
251
  val sourceCodePoint0: Int,
252
  val sourceCodePoint1: Int,
253
  val type: Int,
254
  val mappedTo: ByteString,
255
) {
256
  val section: Int
257
    get() = sourceCodePoint0 and 0x1fff80
258

259
  val rangeStart: Int
260
    get() = sourceCodePoint0 and 0x7f
261

262
  val hasSingleSourceCodePoint: Boolean
263
    get() = sourceCodePoint0 == sourceCodePoint1
264

265
  val spansSections: Boolean
266
    get() = (sourceCodePoint0 and 0x1fff80) != (sourceCodePoint1 and 0x1fff80)
267
}
268

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.