@onekeyfe/react-native-sni-connect 3.0.82 → 3.0.86
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/android/src/main/java/com/sniconnect/SniConnectModule.kt +204 -60
- package/android/src/main/java/com/sniconnect/SniConnectRequestAdmission.kt +276 -0
- package/android/src/main/java/com/sniconnect/SniConnectValidation.kt +12 -78
- package/android/src/test/java/com/sniconnect/SniConnectRequestAdmissionTest.kt +309 -0
- package/android/src/test/java/com/sniconnect/SniConnectValidationTest.kt +23 -26
- package/ios/SniConnect.mm +27 -2
- package/ios/SniConnect.swift +34 -0
- package/ios/SniConnectClient.swift +102 -33
- package/ios/SniConnectCore.swift +49 -3
- package/ios/SniConnectValidation.swift +214 -41
- package/ios/Tests/SniConnectValidationTests/SniConnectValidationTests.swift +220 -8
- package/lib/module/index.js +3 -0
- package/lib/typescript/src/NativeSniConnect.d.ts +13 -0
- package/lib/typescript/src/index.d.ts +3 -2
- package/package.json +1 -1
- package/src/NativeSniConnect.ts +17 -0
- package/src/index.tsx +10 -0
package/README.md
CHANGED
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
cancelRequest,
|
|
39
39
|
cancelAllRequests,
|
|
40
40
|
clearDNSCache,
|
|
41
|
+
getDebugSnapshot,
|
|
41
42
|
isProxyActiveForUrl,
|
|
42
43
|
} from '@onekeyfe/react-native-sni-connect';
|
|
43
44
|
|
|
@@ -66,6 +67,12 @@ await cancelAllRequests();
|
|
|
66
67
|
|
|
67
68
|
// Drop pinned-IP connections / cached clients
|
|
68
69
|
await clearDNSCache();
|
|
70
|
+
|
|
71
|
+
// Diagnostics for one validated hostname/IP pair
|
|
72
|
+
const snapshot = await getDebugSnapshot({
|
|
73
|
+
hostname: 'example.com',
|
|
74
|
+
ip: '93.184.216.34',
|
|
75
|
+
});
|
|
69
76
|
```
|
|
70
77
|
|
|
71
78
|
`multiValueHeaders` preserves repeated response headers when the native transport
|
|
@@ -57,6 +57,15 @@ internal fun classifySniFailureCode(error: Throwable): String {
|
|
|
57
57
|
return "SNI_REQUEST_FAILED"
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
internal fun classifySniResponseFailureCode(
|
|
61
|
+
error: Throwable,
|
|
62
|
+
explicitlyCancelled: Boolean,
|
|
63
|
+
): String = when {
|
|
64
|
+
explicitlyCancelled -> "SNI_CANCELLED"
|
|
65
|
+
hasCause(error, InterruptedIOException::class.java) -> "SNI_REQUEST_TIMEOUT"
|
|
66
|
+
else -> "SNI_RESPONSE_FAILED"
|
|
67
|
+
}
|
|
68
|
+
|
|
60
69
|
private fun hasCause(error: Throwable, type: Class<out Throwable>): Boolean {
|
|
61
70
|
var current: Throwable? = error
|
|
62
71
|
while (current != null) {
|
|
@@ -77,12 +86,12 @@ class SniConnectModule(reactContext: ReactApplicationContext) :
|
|
|
77
86
|
// from JS-controlled host/IP pairs (e.g. speed-testing many endpoints).
|
|
78
87
|
private const val MAX_CLIENTS = 32
|
|
79
88
|
|
|
80
|
-
//
|
|
81
|
-
// don't spawn a thread pool / connection pool per (hostname, ip) pair.
|
|
89
|
+
// Native resources are process-shared across the main and background RN runtimes.
|
|
82
90
|
private val sharedDispatcher = Dispatcher().apply {
|
|
83
|
-
maxRequests =
|
|
84
|
-
maxRequestsPerHost =
|
|
91
|
+
maxRequests = SniConnectValidation.MAX_ACTIVE_REQUESTS
|
|
92
|
+
maxRequestsPerHost = SniConnectValidation.MAX_ACTIVE_REQUESTS
|
|
85
93
|
}
|
|
94
|
+
private val sharedAdmission = SniConnectRequestAdmission()
|
|
86
95
|
private val sharedConnectionPool = ConnectionPool()
|
|
87
96
|
}
|
|
88
97
|
|
|
@@ -120,13 +129,50 @@ class SniConnectModule(reactContext: ReactApplicationContext) :
|
|
|
120
129
|
}
|
|
121
130
|
}
|
|
122
131
|
|
|
123
|
-
private
|
|
124
|
-
|
|
132
|
+
private class ManagedRequest(
|
|
133
|
+
val call: Call,
|
|
134
|
+
val settled: AtomicBoolean,
|
|
135
|
+
) {
|
|
136
|
+
lateinit var admissionTicket: SniConnectRequestAdmission.Ticket
|
|
137
|
+
private val explicitlyCancelled = AtomicBoolean(false)
|
|
138
|
+
|
|
139
|
+
fun cancel() {
|
|
140
|
+
explicitlyCancelled.set(true)
|
|
141
|
+
if (!admissionTicket.cancelPending()) {
|
|
142
|
+
call.cancel()
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
fun release() {
|
|
147
|
+
admissionTicket.release()
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
fun wasExplicitlyCancelled(): Boolean = explicitlyCancelled.get()
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Cancellation ownership is per module, so cancelAllRequests only affects its RN runtime.
|
|
154
|
+
private val activeCalls = ConcurrentHashMap<String, ManagedRequest>()
|
|
155
|
+
private val allActiveCalls = Collections.newSetFromMap(
|
|
156
|
+
ConcurrentHashMap<ManagedRequest, Boolean>(),
|
|
157
|
+
)
|
|
125
158
|
private val activeCallsLock = Any()
|
|
126
|
-
private
|
|
159
|
+
private var invalidated = false
|
|
127
160
|
|
|
128
161
|
override fun getName(): String = NAME
|
|
129
162
|
|
|
163
|
+
override fun invalidate() {
|
|
164
|
+
val cancelledCount = cancelOwnedRequests(markInvalidated = true)
|
|
165
|
+
SniConnectLogger.info(
|
|
166
|
+
SniConnectLogger.event(
|
|
167
|
+
"sni_lifecycle",
|
|
168
|
+
"action" to "runtime_invalidate",
|
|
169
|
+
"cancelledCount" to cancelledCount,
|
|
170
|
+
"success" to true,
|
|
171
|
+
),
|
|
172
|
+
)
|
|
173
|
+
super.invalidate()
|
|
174
|
+
}
|
|
175
|
+
|
|
130
176
|
override fun request(config: ReadableMap, promise: Promise) {
|
|
131
177
|
try {
|
|
132
178
|
val requestConfig = config.toRequestConfig()
|
|
@@ -147,11 +193,11 @@ class SniConnectModule(reactContext: ReactApplicationContext) :
|
|
|
147
193
|
|
|
148
194
|
@ReactMethod
|
|
149
195
|
override fun cancelRequest(requestId: String, promise: Promise) {
|
|
150
|
-
val
|
|
196
|
+
val request = synchronized(activeCallsLock) {
|
|
151
197
|
activeCalls.remove(requestId)
|
|
152
198
|
}
|
|
153
|
-
if (
|
|
154
|
-
|
|
199
|
+
if (request != null) {
|
|
200
|
+
request.cancel()
|
|
155
201
|
SniConnectLogger.info(
|
|
156
202
|
SniConnectLogger.event(
|
|
157
203
|
"sni_cancel",
|
|
@@ -174,17 +220,11 @@ class SniConnectModule(reactContext: ReactApplicationContext) :
|
|
|
174
220
|
|
|
175
221
|
@ReactMethod
|
|
176
222
|
override fun cancelAllRequests(promise: Promise) {
|
|
177
|
-
val
|
|
178
|
-
val snapshot = allActiveCalls.toList()
|
|
179
|
-
activeCalls.clear()
|
|
180
|
-
allActiveCalls.clear()
|
|
181
|
-
snapshot
|
|
182
|
-
}
|
|
183
|
-
calls.forEach { call -> call.cancel() }
|
|
223
|
+
val cancelledCount = cancelOwnedRequests()
|
|
184
224
|
SniConnectLogger.info(
|
|
185
225
|
SniConnectLogger.event(
|
|
186
226
|
"sni_cancel_all",
|
|
187
|
-
"cancelledCount" to
|
|
227
|
+
"cancelledCount" to cancelledCount,
|
|
188
228
|
"success" to true,
|
|
189
229
|
),
|
|
190
230
|
)
|
|
@@ -208,6 +248,29 @@ class SniConnectModule(reactContext: ReactApplicationContext) :
|
|
|
208
248
|
promise.resolve(Arguments.createMap().apply { putBoolean("success", true) })
|
|
209
249
|
}
|
|
210
250
|
|
|
251
|
+
@ReactMethod
|
|
252
|
+
override fun getDebugSnapshot(target: ReadableMap, promise: Promise) {
|
|
253
|
+
try {
|
|
254
|
+
val ip = target.getString("ip") ?: throw IllegalArgumentException("ip is required")
|
|
255
|
+
val hostname = target.getString("hostname")
|
|
256
|
+
?: throw IllegalArgumentException("hostname is required")
|
|
257
|
+
val canonicalIp = SniConnectValidation.canonicalizePublicIp(ip)
|
|
258
|
+
SniConnectValidation.validateHostname(hostname)
|
|
259
|
+
val snapshot = sharedAdmission.snapshot(hostname, canonicalIp)
|
|
260
|
+
|
|
261
|
+
promise.resolve(Arguments.createMap().apply {
|
|
262
|
+
putInt("activeRequests", snapshot.activeRequests)
|
|
263
|
+
putInt("activeRequestsForPair", snapshot.activeRequestsForPair)
|
|
264
|
+
putInt("pendingRequests", snapshot.pendingRequests)
|
|
265
|
+
putInt("pendingRequestsForPair", snapshot.pendingRequestsForPair)
|
|
266
|
+
putArray("activeRequestIdsForPair", snapshot.activeRequestIdsForPair.toWritableArray())
|
|
267
|
+
putArray("pendingRequestIdsForPair", snapshot.pendingRequestIdsForPair.toWritableArray())
|
|
268
|
+
})
|
|
269
|
+
} catch (error: Exception) {
|
|
270
|
+
promise.reject("SNI_INVALID_CONFIG", error.message, error)
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
211
274
|
@ReactMethod
|
|
212
275
|
override fun isProxyActiveForUrl(url: String, promise: Promise) {
|
|
213
276
|
try {
|
|
@@ -219,19 +282,14 @@ class SniConnectModule(reactContext: ReactApplicationContext) :
|
|
|
219
282
|
|
|
220
283
|
private fun performRequest(config: RequestConfig, promise: Promise) {
|
|
221
284
|
val startedAtMs = android.os.SystemClock.elapsedRealtime()
|
|
222
|
-
|
|
223
|
-
var
|
|
285
|
+
val startedAtNanos = System.nanoTime()
|
|
286
|
+
var registeredRequest: ManagedRequest? = null
|
|
224
287
|
try {
|
|
225
|
-
requestSlot = requestLimiter.acquire(config.hostname, config.ip)
|
|
226
288
|
val client = getOrCreateClient(config)
|
|
227
289
|
val request = buildRequest(config)
|
|
228
290
|
val call = client.newCall(request)
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
call.timeout().timeout(config.timeoutMillis, TimeUnit.MILLISECONDS)
|
|
232
|
-
|
|
233
|
-
registerCall(config.requestId, call)
|
|
234
|
-
registeredCall = call
|
|
291
|
+
val settled = AtomicBoolean(false)
|
|
292
|
+
lateinit var managedRequest: ManagedRequest
|
|
235
293
|
|
|
236
294
|
SniConnectLogger.info(
|
|
237
295
|
SniConnectLogger.event(
|
|
@@ -247,16 +305,13 @@ class SniConnectModule(reactContext: ReactApplicationContext) :
|
|
|
247
305
|
),
|
|
248
306
|
)
|
|
249
307
|
|
|
250
|
-
|
|
251
|
-
val settled = AtomicBoolean(false)
|
|
252
|
-
|
|
253
|
-
call.enqueue(object : Callback {
|
|
308
|
+
val callback = object : Callback {
|
|
254
309
|
override fun onFailure(call: Call, e: IOException) {
|
|
255
|
-
|
|
256
|
-
|
|
310
|
+
unregisterRequest(config.requestId, managedRequest)
|
|
311
|
+
managedRequest.release()
|
|
257
312
|
if (!settled.compareAndSet(false, true)) return
|
|
258
313
|
|
|
259
|
-
if (
|
|
314
|
+
if (managedRequest.wasExplicitlyCancelled()) {
|
|
260
315
|
promise.reject("SNI_CANCELLED", "Request cancelled", null)
|
|
261
316
|
} else {
|
|
262
317
|
val code = classifySniFailureCode(e)
|
|
@@ -310,17 +365,29 @@ class SniConnectModule(reactContext: ReactApplicationContext) :
|
|
|
310
365
|
SniConnectLogger.info(resultLog)
|
|
311
366
|
}
|
|
312
367
|
if (settled.compareAndSet(false, true)) {
|
|
313
|
-
|
|
368
|
+
if (managedRequest.wasExplicitlyCancelled()) {
|
|
369
|
+
promise.reject("SNI_CANCELLED", "Request cancelled", null)
|
|
370
|
+
} else {
|
|
371
|
+
promise.resolve(result)
|
|
372
|
+
}
|
|
314
373
|
}
|
|
315
374
|
}
|
|
316
375
|
}
|
|
317
376
|
} catch (error: Exception) {
|
|
318
377
|
if (!settled.compareAndSet(false, true)) return
|
|
378
|
+
val code = classifySniResponseFailureCode(
|
|
379
|
+
error,
|
|
380
|
+
managedRequest.wasExplicitlyCancelled(),
|
|
381
|
+
)
|
|
382
|
+
if (code == "SNI_CANCELLED") {
|
|
383
|
+
promise.reject(code, "Request cancelled", null)
|
|
384
|
+
return
|
|
385
|
+
}
|
|
319
386
|
SniConnectLogger.error(
|
|
320
387
|
SniConnectLogger.event(
|
|
321
388
|
"sni_request_result",
|
|
322
389
|
"result" to "error",
|
|
323
|
-
"code" to
|
|
390
|
+
"code" to code,
|
|
324
391
|
"nativeErrorClass" to error.javaClass.simpleName,
|
|
325
392
|
"requestIdHash" to SniConnectLogger.shortHash(config.requestId),
|
|
326
393
|
"hostname" to config.hostname.lowercase(Locale.US),
|
|
@@ -331,22 +398,57 @@ class SniConnectModule(reactContext: ReactApplicationContext) :
|
|
|
331
398
|
"elapsedMs" to SniConnectLogger.elapsedMs(startedAtMs),
|
|
332
399
|
),
|
|
333
400
|
)
|
|
334
|
-
promise.reject(
|
|
401
|
+
promise.reject(code, error.message, error)
|
|
335
402
|
} finally {
|
|
336
|
-
|
|
337
|
-
|
|
403
|
+
unregisterRequest(config.requestId, managedRequest)
|
|
404
|
+
managedRequest.release()
|
|
338
405
|
}
|
|
339
406
|
}
|
|
340
|
-
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
val timeoutBeforeAdmission = remainingTimeoutMillis(
|
|
410
|
+
config.timeoutMillis,
|
|
411
|
+
startedAtNanos,
|
|
412
|
+
)
|
|
413
|
+
val admissionTicket = sharedAdmission.createTicket(
|
|
414
|
+
hostname = config.hostname,
|
|
415
|
+
ip = config.ip,
|
|
416
|
+
requestId = config.requestId,
|
|
417
|
+
timeoutMillis = timeoutBeforeAdmission,
|
|
418
|
+
onAdmitted = { remainingTimeoutMillis ->
|
|
419
|
+
try {
|
|
420
|
+
call.timeout().timeout(remainingTimeoutMillis, TimeUnit.MILLISECONDS)
|
|
421
|
+
call.enqueue(callback)
|
|
422
|
+
} catch (error: Exception) {
|
|
423
|
+
unregisterRequest(config.requestId, managedRequest)
|
|
424
|
+
managedRequest.release()
|
|
425
|
+
if (settled.compareAndSet(false, true)) {
|
|
426
|
+
promise.reject("SNI_REQUEST_FAILED", error.message, error)
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
},
|
|
430
|
+
onPendingFailure = { code, message ->
|
|
431
|
+
unregisterRequest(config.requestId, managedRequest)
|
|
432
|
+
if (settled.compareAndSet(false, true)) {
|
|
433
|
+
promise.reject(code, message, null)
|
|
434
|
+
}
|
|
435
|
+
},
|
|
436
|
+
)
|
|
437
|
+
managedRequest = ManagedRequest(call, settled).apply {
|
|
438
|
+
this.admissionTicket = admissionTicket
|
|
439
|
+
}
|
|
440
|
+
registerRequest(config.requestId, managedRequest)
|
|
441
|
+
registeredRequest = managedRequest
|
|
442
|
+
admissionTicket.submit()
|
|
341
443
|
} catch (error: Exception) {
|
|
342
|
-
|
|
343
|
-
|
|
444
|
+
registeredRequest?.let { request ->
|
|
445
|
+
unregisterRequest(config.requestId, request)
|
|
446
|
+
request.release()
|
|
344
447
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
"
|
|
348
|
-
|
|
349
|
-
"SNI_REQUEST_FAILED"
|
|
448
|
+
val code = when {
|
|
449
|
+
error is SniConnectValidation.ValidationException -> "SNI_RESOURCE_LIMIT"
|
|
450
|
+
hasCause(error, InterruptedIOException::class.java) -> "SNI_REQUEST_TIMEOUT"
|
|
451
|
+
else -> "SNI_REQUEST_FAILED"
|
|
350
452
|
}
|
|
351
453
|
SniConnectLogger.error(
|
|
352
454
|
SniConnectLogger.event(
|
|
@@ -363,18 +465,30 @@ class SniConnectModule(reactContext: ReactApplicationContext) :
|
|
|
363
465
|
"elapsedMs" to SniConnectLogger.elapsedMs(startedAtMs),
|
|
364
466
|
),
|
|
365
467
|
)
|
|
366
|
-
|
|
468
|
+
val shouldReject = registeredRequest?.settled?.compareAndSet(false, true) ?: true
|
|
469
|
+
if (shouldReject) {
|
|
470
|
+
promise.reject(code, error.message, error)
|
|
471
|
+
}
|
|
367
472
|
}
|
|
368
473
|
}
|
|
369
474
|
|
|
370
|
-
private fun
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
475
|
+
private fun registerRequest(requestId: String?, request: ManagedRequest) {
|
|
476
|
+
var shouldCancel = false
|
|
477
|
+
val previousRequest = synchronized(activeCallsLock) {
|
|
478
|
+
if (invalidated) {
|
|
479
|
+
shouldCancel = true
|
|
480
|
+
return@synchronized null
|
|
481
|
+
}
|
|
482
|
+
val previous = requestId?.let { activeCalls.put(it, request) }
|
|
483
|
+
allActiveCalls.add(request)
|
|
374
484
|
previous
|
|
375
485
|
}
|
|
376
|
-
if (
|
|
377
|
-
|
|
486
|
+
if (shouldCancel) {
|
|
487
|
+
request.cancel()
|
|
488
|
+
return
|
|
489
|
+
}
|
|
490
|
+
if (previousRequest != null && previousRequest != request) {
|
|
491
|
+
previousRequest.cancel()
|
|
378
492
|
SniConnectLogger.warn(
|
|
379
493
|
SniConnectLogger.event(
|
|
380
494
|
"sni_duplicate_request_id",
|
|
@@ -385,18 +499,44 @@ class SniConnectModule(reactContext: ReactApplicationContext) :
|
|
|
385
499
|
}
|
|
386
500
|
}
|
|
387
501
|
|
|
388
|
-
private fun
|
|
502
|
+
private fun unregisterRequest(requestId: String?, request: ManagedRequest) {
|
|
389
503
|
synchronized(activeCallsLock) {
|
|
390
504
|
if (requestId != null) {
|
|
391
|
-
activeCalls.remove(requestId,
|
|
505
|
+
activeCalls.remove(requestId, request)
|
|
392
506
|
}
|
|
393
|
-
allActiveCalls.remove(
|
|
507
|
+
allActiveCalls.remove(request)
|
|
394
508
|
}
|
|
395
509
|
}
|
|
396
510
|
|
|
511
|
+
private fun cancelOwnedRequests(markInvalidated: Boolean = false): Int {
|
|
512
|
+
val requests = synchronized(activeCallsLock) {
|
|
513
|
+
if (markInvalidated) {
|
|
514
|
+
invalidated = true
|
|
515
|
+
}
|
|
516
|
+
val snapshot = allActiveCalls.toList()
|
|
517
|
+
activeCalls.clear()
|
|
518
|
+
allActiveCalls.clear()
|
|
519
|
+
snapshot
|
|
520
|
+
}
|
|
521
|
+
requests.forEach { request -> request.cancel() }
|
|
522
|
+
return requests.size
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
private fun remainingTimeoutMillis(totalTimeoutMillis: Long, startedAtNanos: Long): Long {
|
|
526
|
+
val elapsedNanos = (System.nanoTime() - startedAtNanos).coerceAtLeast(0L)
|
|
527
|
+
val remainingNanos = TimeUnit.MILLISECONDS.toNanos(totalTimeoutMillis) - elapsedNanos
|
|
528
|
+
if (remainingNanos <= 0L) {
|
|
529
|
+
throw java.net.SocketTimeoutException("Request timed out before admission")
|
|
530
|
+
}
|
|
531
|
+
return ((remainingNanos + 999_999L) / 1_000_000L).coerceAtLeast(1L)
|
|
532
|
+
}
|
|
533
|
+
|
|
397
534
|
private fun getOrCreateClient(config: RequestConfig): OkHttpClient {
|
|
398
535
|
val normalizedHost = config.hostname.lowercase(Locale.US)
|
|
399
|
-
val key = ClientKey(
|
|
536
|
+
val key = ClientKey(
|
|
537
|
+
normalizedHost,
|
|
538
|
+
SniConnectValidation.canonicalizePublicIp(config.ip),
|
|
539
|
+
)
|
|
400
540
|
|
|
401
541
|
synchronized(clientCache) {
|
|
402
542
|
clientCache[key]?.let { return it }
|
|
@@ -523,6 +663,10 @@ class SniConnectModule(reactContext: ReactApplicationContext) :
|
|
|
523
663
|
}
|
|
524
664
|
}
|
|
525
665
|
|
|
666
|
+
private fun List<String>.toWritableArray() = Arguments.createArray().apply {
|
|
667
|
+
forEach { value -> pushString(value) }
|
|
668
|
+
}
|
|
669
|
+
|
|
526
670
|
private fun ReadableMap.toRequestConfig(): RequestConfig {
|
|
527
671
|
val rawHeadersMap = if (hasKey("headers") && !isNull("headers")) {
|
|
528
672
|
getMap("headers")?.toHashMap()
|
|
@@ -540,7 +684,7 @@ class SniConnectModule(reactContext: ReactApplicationContext) :
|
|
|
540
684
|
|
|
541
685
|
val requestId = if (hasKey("requestId") && !isNull("requestId")) getString("requestId") else null
|
|
542
686
|
|
|
543
|
-
val
|
|
687
|
+
val rawIp = getString("ip") ?: throw IllegalArgumentException("ip is required")
|
|
544
688
|
val hostname = getString("hostname") ?: throw IllegalArgumentException("hostname is required")
|
|
545
689
|
val method = getString("method") ?: "GET"
|
|
546
690
|
val path = getString("path") ?: "/"
|
|
@@ -548,7 +692,7 @@ class SniConnectModule(reactContext: ReactApplicationContext) :
|
|
|
548
692
|
|
|
549
693
|
// Validate every caller-controlled field at the boundary.
|
|
550
694
|
SniConnectValidation.validateRequestId(requestId)
|
|
551
|
-
SniConnectValidation.
|
|
695
|
+
val ip = SniConnectValidation.canonicalizePublicIp(rawIp)
|
|
552
696
|
SniConnectValidation.validateHostname(hostname)
|
|
553
697
|
val headersMap = SniConnectValidation.normalizeHeaders(rawHeadersMap)
|
|
554
698
|
val normalizedMethod = SniConnectValidation.normalizeMethod(method)
|