@onekeyfe/react-native-image 3.0.98
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/LICENSE +21 -0
- package/OneKeyImage.podspec +32 -0
- package/README.md +88 -0
- package/android/CMakeLists.txt +12 -0
- package/android/build.gradle +85 -0
- package/android/consumer-rules.pro +10 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/cpp/cpp-adapter.cpp +6 -0
- package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImage.kt +616 -0
- package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImageCache.kt +131 -0
- package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImageDecodeDimensions.kt +169 -0
- package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImageGlideRegistry.kt +285 -0
- package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImageModel.kt +68 -0
- package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImagePackage.kt +28 -0
- package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImageSafety.kt +1120 -0
- package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyTosUrl.kt +93 -0
- package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyAnimationTimelineInspectorTest.kt +210 -0
- package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyImageCacheTypeTest.kt +16 -0
- package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyImageDecodeDimensionsTest.kt +70 -0
- package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyImageModelTest.kt +65 -0
- package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyImageRequestSignatureTest.kt +33 -0
- package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyImageSafetyTest.kt +346 -0
- package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyResourceCleanupTest.kt +43 -0
- package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyTerminalEventPairTest.kt +38 -0
- package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyTosUrlTest.kt +82 -0
- package/ios/OneKeyImage.swift +636 -0
- package/ios/OneKeyImageCache.swift +215 -0
- package/ios/OneKeyImageRequestContext.swift +781 -0
- package/ios/OneKeyTosURL.swift +69 -0
- package/ios/tests/OneKeyImageInfrastructureTests.swift +555 -0
- package/ios/tests/OneKeyTosURLTests.swift +142 -0
- package/lib/module/OneKeyImage.nitro.js +30 -0
- package/lib/module/index.js +223 -0
- package/lib/module/package.json +1 -0
- package/lib/nitrogen/generated/android/c++/JFunc_void.hpp +75 -0
- package/lib/nitrogen/generated/android/c++/JFunc_void_double_double_OneKeyImageCacheType.hpp +77 -0
- package/lib/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +76 -0
- package/lib/nitrogen/generated/android/c++/JHybridOneKeyImageCacheSpec.cpp +130 -0
- package/lib/nitrogen/generated/android/c++/JHybridOneKeyImageCacheSpec.hpp +66 -0
- package/lib/nitrogen/generated/android/c++/JHybridOneKeyImageSpec.cpp +255 -0
- package/lib/nitrogen/generated/android/c++/JHybridOneKeyImageSpec.hpp +93 -0
- package/lib/nitrogen/generated/android/c++/JOneKeyImageCachePolicy.hpp +64 -0
- package/lib/nitrogen/generated/android/c++/JOneKeyImageCacheType.hpp +61 -0
- package/lib/nitrogen/generated/android/c++/JOneKeyImageContentFit.hpp +64 -0
- package/lib/nitrogen/generated/android/c++/JOneKeyImageLoadingStrategy.hpp +61 -0
- package/lib/nitrogen/generated/android/c++/JOneKeyImagePreloadSource.hpp +88 -0
- package/lib/nitrogen/generated/android/c++/JOneKeyImageVariant.hpp +64 -0
- package/lib/nitrogen/generated/android/c++/views/JHybridOneKeyImageStateUpdater.cpp +144 -0
- package/lib/nitrogen/generated/android/c++/views/JHybridOneKeyImageStateUpdater.hpp +54 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/Func_void.kt +78 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/Func_void_double_double_OneKeyImageCacheType.kt +78 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/Func_void_std__string.kt +78 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/HybridOneKeyImageCacheSpec.kt +69 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/HybridOneKeyImageSpec.kt +189 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageCachePolicy.kt +25 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageCacheType.kt +24 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageContentFit.kt +25 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageLoadingStrategy.kt +24 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImagePreloadSource.kt +86 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageVariant.kt +25 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/onekeyimageOnLoad.kt +35 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/views/HybridOneKeyImageManager.kt +96 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/views/HybridOneKeyImageStateUpdater.kt +23 -0
- package/lib/nitrogen/generated/android/onekeyimage+autolinking.cmake +85 -0
- package/lib/nitrogen/generated/android/onekeyimage+autolinking.gradle +27 -0
- package/lib/nitrogen/generated/android/onekeyimageOnLoad.cpp +78 -0
- package/lib/nitrogen/generated/android/onekeyimageOnLoad.hpp +34 -0
- package/lib/nitrogen/generated/ios/OneKeyImage+autolinking.rb +62 -0
- package/lib/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Bridge.cpp +90 -0
- package/lib/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Bridge.hpp +405 -0
- package/lib/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Umbrella.hpp +71 -0
- package/lib/nitrogen/generated/ios/OneKeyImageAutolinking.mm +41 -0
- package/lib/nitrogen/generated/ios/OneKeyImageAutolinking.swift +38 -0
- package/lib/nitrogen/generated/ios/c++/HybridOneKeyImageCacheSpecSwift.cpp +11 -0
- package/lib/nitrogen/generated/ios/c++/HybridOneKeyImageCacheSpecSwift.hpp +114 -0
- package/lib/nitrogen/generated/ios/c++/HybridOneKeyImageSpecSwift.cpp +11 -0
- package/lib/nitrogen/generated/ios/c++/HybridOneKeyImageSpecSwift.hpp +206 -0
- package/lib/nitrogen/generated/ios/c++/views/HybridOneKeyImageComponent.mm +228 -0
- package/lib/nitrogen/generated/ios/swift/Func_void.swift +46 -0
- package/lib/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
- package/lib/nitrogen/generated/ios/swift/Func_void_double_double_OneKeyImageCacheType.swift +46 -0
- package/lib/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
- package/lib/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
- package/lib/nitrogen/generated/ios/swift/HybridOneKeyImageCacheSpec.swift +58 -0
- package/lib/nitrogen/generated/ios/swift/HybridOneKeyImageCacheSpec_cxx.swift +202 -0
- package/lib/nitrogen/generated/ios/swift/HybridOneKeyImageSpec.swift +70 -0
- package/lib/nitrogen/generated/ios/swift/HybridOneKeyImageSpec_cxx.swift +539 -0
- package/lib/nitrogen/generated/ios/swift/OneKeyImageCachePolicy.swift +48 -0
- package/lib/nitrogen/generated/ios/swift/OneKeyImageCacheType.swift +44 -0
- package/lib/nitrogen/generated/ios/swift/OneKeyImageContentFit.swift +48 -0
- package/lib/nitrogen/generated/ios/swift/OneKeyImageLoadingStrategy.swift +44 -0
- package/lib/nitrogen/generated/ios/swift/OneKeyImagePreloadSource.swift +148 -0
- package/lib/nitrogen/generated/ios/swift/OneKeyImageVariant.swift +48 -0
- package/lib/nitrogen/generated/shared/c++/HybridOneKeyImageCacheSpec.cpp +24 -0
- package/lib/nitrogen/generated/shared/c++/HybridOneKeyImageCacheSpec.hpp +68 -0
- package/lib/nitrogen/generated/shared/c++/HybridOneKeyImageSpec.cpp +52 -0
- package/lib/nitrogen/generated/shared/c++/HybridOneKeyImageSpec.hpp +108 -0
- package/lib/nitrogen/generated/shared/c++/OneKeyImageCachePolicy.hpp +84 -0
- package/lib/nitrogen/generated/shared/c++/OneKeyImageCacheType.hpp +80 -0
- package/lib/nitrogen/generated/shared/c++/OneKeyImageContentFit.hpp +84 -0
- package/lib/nitrogen/generated/shared/c++/OneKeyImageLoadingStrategy.hpp +80 -0
- package/lib/nitrogen/generated/shared/c++/OneKeyImagePreloadSource.hpp +114 -0
- package/lib/nitrogen/generated/shared/c++/OneKeyImageVariant.hpp +84 -0
- package/lib/nitrogen/generated/shared/c++/views/HybridOneKeyImageComponent.cpp +62 -0
- package/lib/nitrogen/generated/shared/c++/views/HybridOneKeyImageComponent.hpp +132 -0
- package/lib/nitrogen/generated/shared/json/OneKeyImageConfig.json +24 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/OneKeyImage.nitro.d.ts +67 -0
- package/lib/typescript/src/index.d.ts +55 -0
- package/nitro.json +31 -0
- package/nitrogen/generated/android/c++/JFunc_void.hpp +75 -0
- package/nitrogen/generated/android/c++/JFunc_void_double_double_OneKeyImageCacheType.hpp +77 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +76 -0
- package/nitrogen/generated/android/c++/JHybridOneKeyImageCacheSpec.cpp +130 -0
- package/nitrogen/generated/android/c++/JHybridOneKeyImageCacheSpec.hpp +66 -0
- package/nitrogen/generated/android/c++/JHybridOneKeyImageSpec.cpp +255 -0
- package/nitrogen/generated/android/c++/JHybridOneKeyImageSpec.hpp +93 -0
- package/nitrogen/generated/android/c++/JOneKeyImageCachePolicy.hpp +64 -0
- package/nitrogen/generated/android/c++/JOneKeyImageCacheType.hpp +61 -0
- package/nitrogen/generated/android/c++/JOneKeyImageContentFit.hpp +64 -0
- package/nitrogen/generated/android/c++/JOneKeyImageLoadingStrategy.hpp +61 -0
- package/nitrogen/generated/android/c++/JOneKeyImagePreloadSource.hpp +88 -0
- package/nitrogen/generated/android/c++/JOneKeyImageVariant.hpp +64 -0
- package/nitrogen/generated/android/c++/views/JHybridOneKeyImageStateUpdater.cpp +144 -0
- package/nitrogen/generated/android/c++/views/JHybridOneKeyImageStateUpdater.hpp +54 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/Func_void.kt +78 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/Func_void_double_double_OneKeyImageCacheType.kt +78 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/Func_void_std__string.kt +78 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/HybridOneKeyImageCacheSpec.kt +69 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/HybridOneKeyImageSpec.kt +189 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageCachePolicy.kt +25 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageCacheType.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageContentFit.kt +25 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageLoadingStrategy.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImagePreloadSource.kt +86 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageVariant.kt +25 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/onekeyimageOnLoad.kt +35 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/views/HybridOneKeyImageManager.kt +96 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/views/HybridOneKeyImageStateUpdater.kt +23 -0
- package/nitrogen/generated/android/onekeyimage+autolinking.cmake +85 -0
- package/nitrogen/generated/android/onekeyimage+autolinking.gradle +27 -0
- package/nitrogen/generated/android/onekeyimageOnLoad.cpp +78 -0
- package/nitrogen/generated/android/onekeyimageOnLoad.hpp +34 -0
- package/nitrogen/generated/ios/OneKeyImage+autolinking.rb +62 -0
- package/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Bridge.cpp +90 -0
- package/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Bridge.hpp +405 -0
- package/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Umbrella.hpp +71 -0
- package/nitrogen/generated/ios/OneKeyImageAutolinking.mm +41 -0
- package/nitrogen/generated/ios/OneKeyImageAutolinking.swift +38 -0
- package/nitrogen/generated/ios/c++/HybridOneKeyImageCacheSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridOneKeyImageCacheSpecSwift.hpp +114 -0
- package/nitrogen/generated/ios/c++/HybridOneKeyImageSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridOneKeyImageSpecSwift.hpp +206 -0
- package/nitrogen/generated/ios/c++/views/HybridOneKeyImageComponent.mm +228 -0
- package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_double_double_OneKeyImageCacheType.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridOneKeyImageCacheSpec.swift +58 -0
- package/nitrogen/generated/ios/swift/HybridOneKeyImageCacheSpec_cxx.swift +202 -0
- package/nitrogen/generated/ios/swift/HybridOneKeyImageSpec.swift +70 -0
- package/nitrogen/generated/ios/swift/HybridOneKeyImageSpec_cxx.swift +539 -0
- package/nitrogen/generated/ios/swift/OneKeyImageCachePolicy.swift +48 -0
- package/nitrogen/generated/ios/swift/OneKeyImageCacheType.swift +44 -0
- package/nitrogen/generated/ios/swift/OneKeyImageContentFit.swift +48 -0
- package/nitrogen/generated/ios/swift/OneKeyImageLoadingStrategy.swift +44 -0
- package/nitrogen/generated/ios/swift/OneKeyImagePreloadSource.swift +148 -0
- package/nitrogen/generated/ios/swift/OneKeyImageVariant.swift +48 -0
- package/nitrogen/generated/shared/c++/HybridOneKeyImageCacheSpec.cpp +24 -0
- package/nitrogen/generated/shared/c++/HybridOneKeyImageCacheSpec.hpp +68 -0
- package/nitrogen/generated/shared/c++/HybridOneKeyImageSpec.cpp +52 -0
- package/nitrogen/generated/shared/c++/HybridOneKeyImageSpec.hpp +108 -0
- package/nitrogen/generated/shared/c++/OneKeyImageCachePolicy.hpp +84 -0
- package/nitrogen/generated/shared/c++/OneKeyImageCacheType.hpp +80 -0
- package/nitrogen/generated/shared/c++/OneKeyImageContentFit.hpp +84 -0
- package/nitrogen/generated/shared/c++/OneKeyImageLoadingStrategy.hpp +80 -0
- package/nitrogen/generated/shared/c++/OneKeyImagePreloadSource.hpp +114 -0
- package/nitrogen/generated/shared/c++/OneKeyImageVariant.hpp +84 -0
- package/nitrogen/generated/shared/c++/views/HybridOneKeyImageComponent.cpp +62 -0
- package/nitrogen/generated/shared/c++/views/HybridOneKeyImageComponent.hpp +132 -0
- package/nitrogen/generated/shared/json/OneKeyImageConfig.json +24 -0
- package/package.json +128 -0
- package/src/OneKeyImage.nitro.ts +86 -0
- package/src/index.tsx +376 -0
|
@@ -0,0 +1,781 @@
|
|
|
1
|
+
import CryptoKit
|
|
2
|
+
import Foundation
|
|
3
|
+
import ImageIO
|
|
4
|
+
import SDWebImage
|
|
5
|
+
import SDWebImageSVGCoder
|
|
6
|
+
import SDWebImageWebPCoder
|
|
7
|
+
import UIKit
|
|
8
|
+
|
|
9
|
+
enum OneKeyImageSafetyViolation: LocalizedError, Equatable, Sendable {
|
|
10
|
+
case encodedDataTooLarge
|
|
11
|
+
case dataURIPayloadTooLarge
|
|
12
|
+
case staticDimensionsTooLarge
|
|
13
|
+
case animatedDimensionsTooLarge
|
|
14
|
+
case animatedEncodedDataTooLarge
|
|
15
|
+
case animatedFrameCountTooLarge
|
|
16
|
+
case animatedDurationTooLong
|
|
17
|
+
|
|
18
|
+
var errorDescription: String? {
|
|
19
|
+
switch self {
|
|
20
|
+
case .encodedDataTooLarge:
|
|
21
|
+
return "Image encoded data exceeds 32 MiB"
|
|
22
|
+
case .dataURIPayloadTooLarge:
|
|
23
|
+
return "Image data URI payload exceeds 8 MiB"
|
|
24
|
+
case .staticDimensionsTooLarge:
|
|
25
|
+
return "Static image dimensions exceed the safety limit"
|
|
26
|
+
case .animatedDimensionsTooLarge:
|
|
27
|
+
return "Animated image dimensions exceed the safety limit"
|
|
28
|
+
case .animatedEncodedDataTooLarge:
|
|
29
|
+
return "Animated image encoded data exceeds 16 MiB"
|
|
30
|
+
case .animatedFrameCountTooLarge:
|
|
31
|
+
return "Animated image frame count exceeds 1000"
|
|
32
|
+
case .animatedDurationTooLong:
|
|
33
|
+
return "Animated image duration exceeds 60 seconds"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
struct OneKeyImageMetadata: Equatable, Sendable {
|
|
39
|
+
let width: Int?
|
|
40
|
+
let height: Int?
|
|
41
|
+
let frameCount: Int?
|
|
42
|
+
let duration: TimeInterval?
|
|
43
|
+
|
|
44
|
+
var isAnimated: Bool { (frameCount ?? 1) > 1 }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
struct OneKeyImageHeadersIdentity: Hashable, Sendable {
|
|
48
|
+
private struct Field: Hashable, Sendable {
|
|
49
|
+
let name: String
|
|
50
|
+
let value: String
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private let fields: [Field]
|
|
54
|
+
|
|
55
|
+
init(headers: [String: String]?) {
|
|
56
|
+
fields = (headers ?? [:])
|
|
57
|
+
.map { Field(name: $0.key.lowercased(), value: $0.value) }
|
|
58
|
+
.sorted {
|
|
59
|
+
if $0.name == $1.name { return $0.value < $1.value }
|
|
60
|
+
return $0.name < $1.name
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
var isEmpty: Bool { fields.isEmpty }
|
|
65
|
+
|
|
66
|
+
var cacheKeyComponent: String? {
|
|
67
|
+
guard !fields.isEmpty else { return nil }
|
|
68
|
+
let canonicalValue = fields.map {
|
|
69
|
+
"\($0.name.utf8.count):\($0.name)\($0.value.utf8.count):\($0.value)"
|
|
70
|
+
}.joined()
|
|
71
|
+
let digest = SHA256.hash(data: Data(canonicalValue.utf8))
|
|
72
|
+
return digest.map { String(format: "%02x", $0) }.joined()
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private struct OneKeyImageSafetyFlightIdentity: Hashable, Sendable {
|
|
77
|
+
let url: URL
|
|
78
|
+
let headers: OneKeyImageHeadersIdentity
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
final class OneKeyImageSafetyTracker: @unchecked Sendable {
|
|
82
|
+
private let lock = NSLock()
|
|
83
|
+
private var storedViolation: OneKeyImageSafetyViolation?
|
|
84
|
+
|
|
85
|
+
var violation: OneKeyImageSafetyViolation? {
|
|
86
|
+
lock.lock()
|
|
87
|
+
defer { lock.unlock() }
|
|
88
|
+
return storedViolation
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@discardableResult
|
|
92
|
+
func record(_ violation: OneKeyImageSafetyViolation) -> Bool {
|
|
93
|
+
lock.lock()
|
|
94
|
+
defer { lock.unlock() }
|
|
95
|
+
guard storedViolation == nil else { return false }
|
|
96
|
+
storedViolation = violation
|
|
97
|
+
return true
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@discardableResult
|
|
101
|
+
func inspectReceivedByteCount(_ byteCount: Int64) -> Bool {
|
|
102
|
+
guard byteCount > Int64(OneKeyImageSafetyPolicy.maximumEncodedBytes) else { return false }
|
|
103
|
+
record(.encodedDataTooLarge)
|
|
104
|
+
return true
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
final class OneKeyImageSafetyFlightHandle: @unchecked Sendable {
|
|
109
|
+
let tracker: OneKeyImageSafetyTracker
|
|
110
|
+
var manager: SDWebImageManager { managerLease.manager }
|
|
111
|
+
|
|
112
|
+
private let coordinator: OneKeyImageSafetyFlightCoordinator
|
|
113
|
+
private let identity: OneKeyImageSafetyFlightIdentity
|
|
114
|
+
private let managerLease: OneKeyImageManagerLease
|
|
115
|
+
private let lock = NSLock()
|
|
116
|
+
private var finished = false
|
|
117
|
+
|
|
118
|
+
fileprivate init(
|
|
119
|
+
coordinator: OneKeyImageSafetyFlightCoordinator,
|
|
120
|
+
identity: OneKeyImageSafetyFlightIdentity,
|
|
121
|
+
tracker: OneKeyImageSafetyTracker,
|
|
122
|
+
managerLease: OneKeyImageManagerLease
|
|
123
|
+
) {
|
|
124
|
+
self.coordinator = coordinator
|
|
125
|
+
self.identity = identity
|
|
126
|
+
self.tracker = tracker
|
|
127
|
+
self.managerLease = managerLease
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
func finish() {
|
|
131
|
+
lock.lock()
|
|
132
|
+
guard !finished else {
|
|
133
|
+
lock.unlock()
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
finished = true
|
|
137
|
+
lock.unlock()
|
|
138
|
+
coordinator.release(identity: identity, tracker: tracker)
|
|
139
|
+
managerLease.finish()
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
deinit {
|
|
143
|
+
finish()
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
final class OneKeyImageSafetyFlightCoordinator: @unchecked Sendable {
|
|
148
|
+
static let shared = OneKeyImageSafetyFlightCoordinator()
|
|
149
|
+
|
|
150
|
+
private struct Flight {
|
|
151
|
+
let tracker: OneKeyImageSafetyTracker
|
|
152
|
+
var referenceCount: Int
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private let lock = NSLock()
|
|
156
|
+
private let managerPool: OneKeyImageManagerPool
|
|
157
|
+
private var flights: [OneKeyImageSafetyFlightIdentity: Flight] = [:]
|
|
158
|
+
|
|
159
|
+
init(managerPool: OneKeyImageManagerPool = .shared) {
|
|
160
|
+
self.managerPool = managerPool
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
func acquire(
|
|
164
|
+
url: URL,
|
|
165
|
+
headers: [String: String]? = nil
|
|
166
|
+
) -> OneKeyImageSafetyFlightHandle {
|
|
167
|
+
let headersIdentity = OneKeyImageHeadersIdentity(headers: headers)
|
|
168
|
+
let identity = OneKeyImageSafetyFlightIdentity(url: url, headers: headersIdentity)
|
|
169
|
+
let managerLease = managerPool.acquire(identity: headersIdentity)
|
|
170
|
+
lock.lock()
|
|
171
|
+
let tracker: OneKeyImageSafetyTracker
|
|
172
|
+
if var flight = flights[identity] {
|
|
173
|
+
flight.referenceCount += 1
|
|
174
|
+
flights[identity] = flight
|
|
175
|
+
tracker = flight.tracker
|
|
176
|
+
} else {
|
|
177
|
+
tracker = OneKeyImageSafetyTracker()
|
|
178
|
+
flights[identity] = Flight(tracker: tracker, referenceCount: 1)
|
|
179
|
+
}
|
|
180
|
+
lock.unlock()
|
|
181
|
+
return OneKeyImageSafetyFlightHandle(
|
|
182
|
+
coordinator: self,
|
|
183
|
+
identity: identity,
|
|
184
|
+
tracker: tracker,
|
|
185
|
+
managerLease: managerLease
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
fileprivate func release(
|
|
190
|
+
identity: OneKeyImageSafetyFlightIdentity,
|
|
191
|
+
tracker: OneKeyImageSafetyTracker
|
|
192
|
+
) {
|
|
193
|
+
lock.lock()
|
|
194
|
+
defer { lock.unlock() }
|
|
195
|
+
guard var flight = flights[identity], flight.tracker === tracker else { return }
|
|
196
|
+
if flight.referenceCount == 1 {
|
|
197
|
+
flights.removeValue(forKey: identity)
|
|
198
|
+
} else {
|
|
199
|
+
flight.referenceCount -= 1
|
|
200
|
+
flights[identity] = flight
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
var activeFlightCount: Int {
|
|
205
|
+
lock.lock()
|
|
206
|
+
defer { lock.unlock() }
|
|
207
|
+
return flights.count
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
enum OneKeyImageSafetyPolicy {
|
|
212
|
+
static let maximumEncodedBytes = 32 * 1024 * 1024
|
|
213
|
+
static let maximumDataURIDecodedBytes = 8 * 1024 * 1024
|
|
214
|
+
static let maximumAnimatedEncodedBytes = 16 * 1024 * 1024
|
|
215
|
+
static let maximumStaticPixelArea = 100_000_000
|
|
216
|
+
static let maximumStaticSide = 32_768
|
|
217
|
+
static let maximumAnimatedPixelArea = 16_000_000
|
|
218
|
+
static let maximumAnimatedSide = 8_192
|
|
219
|
+
static let maximumAnimatedFrameCount = 1_000
|
|
220
|
+
static let maximumAnimatedDuration: TimeInterval = 60
|
|
221
|
+
static let maximumAnimatedBufferBytes: UInt = 16 * 1024 * 1024
|
|
222
|
+
|
|
223
|
+
static func preflight(source: String) -> OneKeyImageSafetyViolation? {
|
|
224
|
+
guard source.range(of: "data:", options: [.anchored, .caseInsensitive]) != nil,
|
|
225
|
+
let comma = source.firstIndex(of: ",")
|
|
226
|
+
else {
|
|
227
|
+
return nil
|
|
228
|
+
}
|
|
229
|
+
let header = source[..<comma]
|
|
230
|
+
let payload = source[source.index(after: comma)...]
|
|
231
|
+
let payloadByteCount = payload.utf8.count
|
|
232
|
+
let isBase64 = header.range(of: ";base64", options: .caseInsensitive) != nil
|
|
233
|
+
let decodedByteCount =
|
|
234
|
+
isBase64 ? base64DecodedByteCount(payload) : payloadByteCount
|
|
235
|
+
guard let decodedByteCount else {
|
|
236
|
+
// Invalid base64 is a normal corrupt-data failure. The raw character
|
|
237
|
+
// ceiling still guarantees the decoder cannot allocate over 8 MiB.
|
|
238
|
+
let maximumEncodedLength = ((maximumDataURIDecodedBytes + 2) / 3) * 4
|
|
239
|
+
return payloadByteCount > maximumEncodedLength ? .dataURIPayloadTooLarge : nil
|
|
240
|
+
}
|
|
241
|
+
return decodedByteCount > maximumDataURIDecodedBytes ? .dataURIPayloadTooLarge : nil
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
static func preflight(url: URL) -> OneKeyImageSafetyViolation? {
|
|
245
|
+
guard url.isFileURL,
|
|
246
|
+
let values = try? url.resourceValues(forKeys: [.fileSizeKey, .totalFileAllocatedSizeKey])
|
|
247
|
+
else {
|
|
248
|
+
return nil
|
|
249
|
+
}
|
|
250
|
+
let byteCount = max(values.fileSize ?? 0, values.totalFileAllocatedSize ?? 0)
|
|
251
|
+
return byteCount > maximumEncodedBytes ? .encodedDataTooLarge : nil
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
static func violation(
|
|
255
|
+
encodedByteCount: Int,
|
|
256
|
+
metadata: OneKeyImageMetadata?
|
|
257
|
+
) -> OneKeyImageSafetyViolation? {
|
|
258
|
+
guard encodedByteCount <= maximumEncodedBytes else { return .encodedDataTooLarge }
|
|
259
|
+
guard let metadata else { return nil }
|
|
260
|
+
|
|
261
|
+
if metadata.isAnimated {
|
|
262
|
+
if encodedByteCount > maximumAnimatedEncodedBytes {
|
|
263
|
+
return .animatedEncodedDataTooLarge
|
|
264
|
+
}
|
|
265
|
+
if let frameCount = metadata.frameCount,
|
|
266
|
+
frameCount > maximumAnimatedFrameCount
|
|
267
|
+
{
|
|
268
|
+
return .animatedFrameCountTooLarge
|
|
269
|
+
}
|
|
270
|
+
if let duration = metadata.duration,
|
|
271
|
+
!duration.isFinite || duration > maximumAnimatedDuration
|
|
272
|
+
{
|
|
273
|
+
return .animatedDurationTooLong
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if let width = metadata.width,
|
|
277
|
+
let height = metadata.height,
|
|
278
|
+
width > 0,
|
|
279
|
+
height > 0,
|
|
280
|
+
width > maximumAnimatedSide || height > maximumAnimatedSide
|
|
281
|
+
|| Double(width) * Double(height) > Double(maximumAnimatedPixelArea)
|
|
282
|
+
{
|
|
283
|
+
return .animatedDimensionsTooLarge
|
|
284
|
+
}
|
|
285
|
+
} else if let width = metadata.width,
|
|
286
|
+
let height = metadata.height,
|
|
287
|
+
width > 0,
|
|
288
|
+
height > 0,
|
|
289
|
+
width > maximumStaticSide || height > maximumStaticSide
|
|
290
|
+
|| Double(width) * Double(height) > Double(maximumStaticPixelArea)
|
|
291
|
+
{
|
|
292
|
+
return .staticDimensionsTooLarge
|
|
293
|
+
}
|
|
294
|
+
return nil
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
static func violation(for data: Data) -> OneKeyImageSafetyViolation? {
|
|
298
|
+
violation(encodedByteCount: data.count, metadata: metadata(from: data))
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
static func violation(for image: UIImage) -> OneKeyImageSafetyViolation? {
|
|
302
|
+
let pixelWidth = image.cgImage?.width ?? Int(ceil(image.size.width * image.scale))
|
|
303
|
+
let pixelHeight = image.cgImage?.height ?? Int(ceil(image.size.height * image.scale))
|
|
304
|
+
if let animatedImage = image as? SDAnimatedImage {
|
|
305
|
+
let frameCount = Int(animatedImage.animatedImageFrameCount)
|
|
306
|
+
let encodedByteCount = animatedImage.animatedImageData?.count ?? 0
|
|
307
|
+
var duration: TimeInterval = 0
|
|
308
|
+
if frameCount <= maximumAnimatedFrameCount {
|
|
309
|
+
for index in 0..<frameCount {
|
|
310
|
+
duration += animatedImage.animatedImageDuration(at: UInt(index))
|
|
311
|
+
if duration > maximumAnimatedDuration { break }
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return violation(
|
|
315
|
+
encodedByteCount: encodedByteCount,
|
|
316
|
+
metadata: OneKeyImageMetadata(
|
|
317
|
+
width: pixelWidth,
|
|
318
|
+
height: pixelHeight,
|
|
319
|
+
frameCount: frameCount,
|
|
320
|
+
duration: duration
|
|
321
|
+
)
|
|
322
|
+
)
|
|
323
|
+
}
|
|
324
|
+
let frameCount = image.images?.count
|
|
325
|
+
return violation(
|
|
326
|
+
encodedByteCount: 0,
|
|
327
|
+
metadata: OneKeyImageMetadata(
|
|
328
|
+
width: pixelWidth,
|
|
329
|
+
height: pixelHeight,
|
|
330
|
+
frameCount: frameCount,
|
|
331
|
+
duration: frameCount == nil ? nil : image.duration
|
|
332
|
+
)
|
|
333
|
+
)
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
static func metadata(from data: Data) -> OneKeyImageMetadata? {
|
|
337
|
+
guard !data.isEmpty,
|
|
338
|
+
let source = CGImageSourceCreateWithData(
|
|
339
|
+
data as CFData,
|
|
340
|
+
[kCGImageSourceShouldCache: false] as CFDictionary
|
|
341
|
+
),
|
|
342
|
+
CGImageSourceGetCount(source) > 0,
|
|
343
|
+
let firstProperties = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as NSDictionary?
|
|
344
|
+
else {
|
|
345
|
+
return nil
|
|
346
|
+
}
|
|
347
|
+
let sourceProperties = CGImageSourceCopyProperties(source, nil) as NSDictionary?
|
|
348
|
+
var dimensions = maximumDimensions(
|
|
349
|
+
width: (sourceProperties?[kCGImagePropertyPixelWidth] as? NSNumber)?.intValue,
|
|
350
|
+
height: (sourceProperties?[kCGImagePropertyPixelHeight] as? NSNumber)?.intValue,
|
|
351
|
+
candidateWidth: (firstProperties[kCGImagePropertyPixelWidth] as? NSNumber)?.intValue,
|
|
352
|
+
candidateHeight: (firstProperties[kCGImagePropertyPixelHeight] as? NSNumber)?.intValue
|
|
353
|
+
)
|
|
354
|
+
let frameCount = CGImageSourceGetCount(source)
|
|
355
|
+
var duration: TimeInterval?
|
|
356
|
+
if frameCount <= maximumAnimatedFrameCount {
|
|
357
|
+
var total: TimeInterval = 0
|
|
358
|
+
var hasDuration = false
|
|
359
|
+
for index in 0..<frameCount {
|
|
360
|
+
guard
|
|
361
|
+
let properties =
|
|
362
|
+
CGImageSourceCopyPropertiesAtIndex(source, index, nil) as NSDictionary?
|
|
363
|
+
else {
|
|
364
|
+
continue
|
|
365
|
+
}
|
|
366
|
+
dimensions = maximumDimensions(
|
|
367
|
+
width: dimensions.width,
|
|
368
|
+
height: dimensions.height,
|
|
369
|
+
candidateWidth: (properties[kCGImagePropertyPixelWidth] as? NSNumber)?.intValue,
|
|
370
|
+
candidateHeight: (properties[kCGImagePropertyPixelHeight] as? NSNumber)?.intValue
|
|
371
|
+
)
|
|
372
|
+
if frameCount > 1, let frameDuration = frameDuration(from: properties) {
|
|
373
|
+
hasDuration = true
|
|
374
|
+
total += frameDuration
|
|
375
|
+
if !total.isFinite || total > maximumAnimatedDuration { break }
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
duration = hasDuration ? total : nil
|
|
379
|
+
}
|
|
380
|
+
return OneKeyImageMetadata(
|
|
381
|
+
width: dimensions.width,
|
|
382
|
+
height: dimensions.height,
|
|
383
|
+
frameCount: frameCount,
|
|
384
|
+
duration: duration
|
|
385
|
+
)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
static func maximumDimensions(
|
|
389
|
+
width: Int?,
|
|
390
|
+
height: Int?,
|
|
391
|
+
candidateWidth: Int?,
|
|
392
|
+
candidateHeight: Int?
|
|
393
|
+
) -> (width: Int?, height: Int?) {
|
|
394
|
+
(
|
|
395
|
+
[width, candidateWidth].compactMap { $0 }.max(),
|
|
396
|
+
[height, candidateHeight].compactMap { $0 }.max()
|
|
397
|
+
)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
private static func base64DecodedByteCount(_ payload: Substring) -> Int? {
|
|
401
|
+
var characterCount = 0
|
|
402
|
+
var paddingCount = 0
|
|
403
|
+
var sawPadding = false
|
|
404
|
+
for byte in payload.utf8 {
|
|
405
|
+
if byte == 9 || byte == 10 || byte == 13 || byte == 32 { continue }
|
|
406
|
+
if byte == 61 {
|
|
407
|
+
sawPadding = true
|
|
408
|
+
paddingCount += 1
|
|
409
|
+
guard paddingCount <= 2 else { return nil }
|
|
410
|
+
} else {
|
|
411
|
+
guard !sawPadding,
|
|
412
|
+
(byte >= 65 && byte <= 90)
|
|
413
|
+
|| (byte >= 97 && byte <= 122)
|
|
414
|
+
|| (byte >= 48 && byte <= 57)
|
|
415
|
+
|| byte == 43
|
|
416
|
+
|| byte == 47
|
|
417
|
+
else {
|
|
418
|
+
return nil
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
characterCount += 1
|
|
422
|
+
}
|
|
423
|
+
guard characterCount > 0 else { return 0 }
|
|
424
|
+
let remainder = characterCount % 4
|
|
425
|
+
guard remainder != 1, paddingCount == 0 || remainder == 0 else { return nil }
|
|
426
|
+
let unpaddedBytes =
|
|
427
|
+
(characterCount / 4) * 3
|
|
428
|
+
+ (remainder == 2 ? 1 : remainder == 3 ? 2 : 0)
|
|
429
|
+
return unpaddedBytes - paddingCount
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
private static func frameDuration(from properties: NSDictionary) -> TimeInterval? {
|
|
433
|
+
if let dictionary = properties[kCGImagePropertyGIFDictionary] as? NSDictionary {
|
|
434
|
+
return duration(
|
|
435
|
+
in: dictionary,
|
|
436
|
+
unclampedKey: kCGImagePropertyGIFUnclampedDelayTime,
|
|
437
|
+
clampedKey: kCGImagePropertyGIFDelayTime
|
|
438
|
+
)
|
|
439
|
+
}
|
|
440
|
+
if let dictionary = properties[kCGImagePropertyPNGDictionary] as? NSDictionary {
|
|
441
|
+
return duration(
|
|
442
|
+
in: dictionary,
|
|
443
|
+
unclampedKey: kCGImagePropertyAPNGUnclampedDelayTime,
|
|
444
|
+
clampedKey: kCGImagePropertyAPNGDelayTime
|
|
445
|
+
)
|
|
446
|
+
}
|
|
447
|
+
if let dictionary = properties[kCGImagePropertyWebPDictionary] as? NSDictionary {
|
|
448
|
+
return duration(
|
|
449
|
+
in: dictionary,
|
|
450
|
+
unclampedKey: kCGImagePropertyWebPUnclampedDelayTime,
|
|
451
|
+
clampedKey: kCGImagePropertyWebPDelayTime
|
|
452
|
+
)
|
|
453
|
+
}
|
|
454
|
+
return nil
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
private static func duration(
|
|
458
|
+
in dictionary: NSDictionary,
|
|
459
|
+
unclampedKey: CFString,
|
|
460
|
+
clampedKey: CFString
|
|
461
|
+
) -> TimeInterval? {
|
|
462
|
+
let unclamped = (dictionary[unclampedKey] as? NSNumber)?.doubleValue
|
|
463
|
+
if let unclamped, !unclamped.isFinite || unclamped > 0 { return unclamped }
|
|
464
|
+
let clamped = (dictionary[clampedKey] as? NSNumber)?.doubleValue
|
|
465
|
+
return clamped.flatMap { !($0.isFinite) || $0 > 0 ? $0 : nil }
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
enum OneKeyImageCoderRegistry {
|
|
470
|
+
private static let svgCoder = SDImageSVGCoder.shared
|
|
471
|
+
private static let webPCoder = SDImageWebPCoder.shared
|
|
472
|
+
|
|
473
|
+
static let coder: SDImageCodersManager = {
|
|
474
|
+
// Keep OneKey's decoder set independent from Expo's global registrations.
|
|
475
|
+
// SDImageCodersManager starts with ImageIO, GIF and APNG coders.
|
|
476
|
+
let manager = SDImageCodersManager()
|
|
477
|
+
manager.addCoder(svgCoder)
|
|
478
|
+
manager.addCoder(webPCoder)
|
|
479
|
+
return manager
|
|
480
|
+
}()
|
|
481
|
+
|
|
482
|
+
private static let globalRegistration: Void = {
|
|
483
|
+
// SDAnimatedImage resolves its animated coder through the global manager,
|
|
484
|
+
// even when a request-local coder is provided in the SDWebImage context.
|
|
485
|
+
let global = SDImageCodersManager.shared
|
|
486
|
+
let isAlreadyRegistered = (global.coders ?? []).contains {
|
|
487
|
+
($0 as AnyObject) === webPCoder
|
|
488
|
+
}
|
|
489
|
+
if !isAlreadyRegistered {
|
|
490
|
+
global.addCoder(webPCoder)
|
|
491
|
+
}
|
|
492
|
+
}()
|
|
493
|
+
|
|
494
|
+
static func ensureWebPRegistered() {
|
|
495
|
+
_ = globalRegistration
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
enum OneKeyImagePipeline {
|
|
500
|
+
static let downloader = SDWebImageDownloader(config: nil)
|
|
501
|
+
static let manager = SDWebImageManager(cache: SDImageCache.shared, loader: downloader)
|
|
502
|
+
|
|
503
|
+
static func makeIsolatedManager() -> SDWebImageManager {
|
|
504
|
+
SDWebImageManager(
|
|
505
|
+
cache: SDImageCache.shared,
|
|
506
|
+
loader: SDWebImageDownloader(config: nil)
|
|
507
|
+
)
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
final class OneKeyImageManagerLease: @unchecked Sendable {
|
|
512
|
+
let manager: SDWebImageManager
|
|
513
|
+
|
|
514
|
+
private let pool: OneKeyImageManagerPool?
|
|
515
|
+
private let identity: OneKeyImageHeadersIdentity
|
|
516
|
+
private let lock = NSLock()
|
|
517
|
+
private var finished = false
|
|
518
|
+
|
|
519
|
+
fileprivate init(
|
|
520
|
+
manager: SDWebImageManager,
|
|
521
|
+
pool: OneKeyImageManagerPool?,
|
|
522
|
+
identity: OneKeyImageHeadersIdentity
|
|
523
|
+
) {
|
|
524
|
+
self.manager = manager
|
|
525
|
+
self.pool = pool
|
|
526
|
+
self.identity = identity
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
func finish() {
|
|
530
|
+
lock.lock()
|
|
531
|
+
guard !finished else {
|
|
532
|
+
lock.unlock()
|
|
533
|
+
return
|
|
534
|
+
}
|
|
535
|
+
finished = true
|
|
536
|
+
lock.unlock()
|
|
537
|
+
pool?.release(identity: identity, manager: manager)
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
deinit {
|
|
541
|
+
finish()
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
final class OneKeyImageManagerPool: @unchecked Sendable {
|
|
546
|
+
static let shared = OneKeyImageManagerPool()
|
|
547
|
+
|
|
548
|
+
private struct Entry {
|
|
549
|
+
let manager: SDWebImageManager
|
|
550
|
+
var referenceCount: Int
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
private let defaultManager: SDWebImageManager
|
|
554
|
+
private let lock = NSLock()
|
|
555
|
+
private var entries: [OneKeyImageHeadersIdentity: Entry] = [:]
|
|
556
|
+
|
|
557
|
+
init(defaultManager: SDWebImageManager = OneKeyImagePipeline.manager) {
|
|
558
|
+
self.defaultManager = defaultManager
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
func acquire(identity: OneKeyImageHeadersIdentity) -> OneKeyImageManagerLease {
|
|
562
|
+
guard !identity.isEmpty else {
|
|
563
|
+
return OneKeyImageManagerLease(manager: defaultManager, pool: nil, identity: identity)
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
lock.lock()
|
|
567
|
+
let manager: SDWebImageManager
|
|
568
|
+
if var entry = entries[identity] {
|
|
569
|
+
entry.referenceCount += 1
|
|
570
|
+
entries[identity] = entry
|
|
571
|
+
manager = entry.manager
|
|
572
|
+
} else {
|
|
573
|
+
manager = OneKeyImagePipeline.makeIsolatedManager()
|
|
574
|
+
entries[identity] = Entry(manager: manager, referenceCount: 1)
|
|
575
|
+
}
|
|
576
|
+
lock.unlock()
|
|
577
|
+
return OneKeyImageManagerLease(manager: manager, pool: self, identity: identity)
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
fileprivate func release(
|
|
581
|
+
identity: OneKeyImageHeadersIdentity,
|
|
582
|
+
manager: SDWebImageManager
|
|
583
|
+
) {
|
|
584
|
+
lock.lock()
|
|
585
|
+
defer { lock.unlock() }
|
|
586
|
+
guard var entry = entries[identity], entry.manager === manager else { return }
|
|
587
|
+
if entry.referenceCount == 1 {
|
|
588
|
+
entries.removeValue(forKey: identity)
|
|
589
|
+
} else {
|
|
590
|
+
entry.referenceCount -= 1
|
|
591
|
+
entries[identity] = entry
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
var activeManagerCount: Int {
|
|
596
|
+
lock.lock()
|
|
597
|
+
defer { lock.unlock() }
|
|
598
|
+
return entries.count
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
enum OneKeyImageDecodeSizing {
|
|
603
|
+
static let maximumDecodedBytes = 16 * 1024 * 1024
|
|
604
|
+
static let bytesPerPixel = 4
|
|
605
|
+
static let maximumPixelArea = CGFloat(maximumDecodedBytes / bytesPerPixel)
|
|
606
|
+
static let defaultPreloadPixelSize = CGSize(width: 2_048, height: 2_048)
|
|
607
|
+
|
|
608
|
+
static func logicalViewSize(width: Double?, height: Double?) -> CGSize? {
|
|
609
|
+
let validWidth = width.flatMap { value -> CGFloat? in
|
|
610
|
+
guard value.isFinite, value > 0 else { return nil }
|
|
611
|
+
return CGFloat(value)
|
|
612
|
+
}
|
|
613
|
+
let validHeight = height.flatMap { value -> CGFloat? in
|
|
614
|
+
guard value.isFinite, value > 0 else { return nil }
|
|
615
|
+
return CGFloat(value)
|
|
616
|
+
}
|
|
617
|
+
switch (validWidth, validHeight) {
|
|
618
|
+
case (let width?, let height?): return CGSize(width: width, height: height)
|
|
619
|
+
case (let width?, nil): return CGSize(width: width, height: width)
|
|
620
|
+
case (nil, let height?): return CGSize(width: height, height: height)
|
|
621
|
+
case (nil, nil): return nil
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
static func thumbnailPixelSize(
|
|
626
|
+
viewSize: CGSize,
|
|
627
|
+
scale: CGFloat,
|
|
628
|
+
contentFit: OneKeyImageContentFit
|
|
629
|
+
) -> CGSize? {
|
|
630
|
+
guard viewSize.width.isFinite,
|
|
631
|
+
viewSize.height.isFinite,
|
|
632
|
+
viewSize.width > 0,
|
|
633
|
+
viewSize.height > 0
|
|
634
|
+
else {
|
|
635
|
+
return nil
|
|
636
|
+
}
|
|
637
|
+
let pixelScale = min(max(scale.isFinite ? scale : 1, 1), 3)
|
|
638
|
+
let requestedSize: CGSize
|
|
639
|
+
switch contentFit {
|
|
640
|
+
case .cover:
|
|
641
|
+
// The source aspect ratio is unknown before decode. A square based on the
|
|
642
|
+
// longest edge avoids under-decoding the common cover case.
|
|
643
|
+
let edge = ceil(max(viewSize.width, viewSize.height) * pixelScale)
|
|
644
|
+
requestedSize = CGSize(width: edge, height: edge)
|
|
645
|
+
case .contain, .fill, .center:
|
|
646
|
+
requestedSize = CGSize(
|
|
647
|
+
width: ceil(viewSize.width * pixelScale),
|
|
648
|
+
height: ceil(viewSize.height * pixelScale)
|
|
649
|
+
)
|
|
650
|
+
}
|
|
651
|
+
return cappedToMaximumArea(requestedSize)
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
private static func cappedToMaximumArea(_ size: CGSize) -> CGSize {
|
|
655
|
+
let area = size.width * size.height
|
|
656
|
+
guard area > maximumPixelArea else { return size }
|
|
657
|
+
let ratio = sqrt(maximumPixelArea / area)
|
|
658
|
+
return CGSize(
|
|
659
|
+
width: max(floor(size.width * ratio), 1),
|
|
660
|
+
height: max(floor(size.height * ratio), 1)
|
|
661
|
+
)
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
enum OneKeyImageRequestContext {
|
|
666
|
+
static let safetyCacheGeneration = "onekey-image-safety-v2"
|
|
667
|
+
static let renderOptions: SDWebImage.SDWebImageOptions = [.retryFailed]
|
|
668
|
+
static let preloadOptions: SDWebImage.SDWebImageOptions = [
|
|
669
|
+
.retryFailed, .lowPriority,
|
|
670
|
+
]
|
|
671
|
+
|
|
672
|
+
static var baseContext: [SDWebImageContextOption: Any] {
|
|
673
|
+
OneKeyImageCoderRegistry.ensureWebPRegistered()
|
|
674
|
+
return [
|
|
675
|
+
.imageCoder: OneKeyImageCoderRegistry.coder,
|
|
676
|
+
.animatedImageClass: SDAnimatedImage.self,
|
|
677
|
+
.customManager: OneKeyImagePipeline.manager,
|
|
678
|
+
]
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
static func headers(from json: String?) -> [String: String]? {
|
|
682
|
+
guard let json,
|
|
683
|
+
let data = json.data(using: .utf8),
|
|
684
|
+
let value = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
|
|
685
|
+
else {
|
|
686
|
+
return nil
|
|
687
|
+
}
|
|
688
|
+
var headers: [String: String] = [:]
|
|
689
|
+
for (key, rawValue) in value {
|
|
690
|
+
if let string = rawValue as? String { headers[key] = string }
|
|
691
|
+
}
|
|
692
|
+
return headers.isEmpty ? nil : headers
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
static func make(
|
|
696
|
+
headersJson: String?,
|
|
697
|
+
cachePolicy: OneKeyImageCachePolicy,
|
|
698
|
+
thumbnailPixelSize: CGSize?,
|
|
699
|
+
safetyTracker: OneKeyImageSafetyTracker?,
|
|
700
|
+
manager: SDWebImageManager
|
|
701
|
+
) -> [SDWebImageContextOption: Any] {
|
|
702
|
+
var context = baseContext
|
|
703
|
+
context[.customManager] = manager
|
|
704
|
+
if let safetyTracker {
|
|
705
|
+
for (key, value) in transportGuards(tracker: safetyTracker) {
|
|
706
|
+
context[key] = value
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
let headers = headers(from: headersJson)
|
|
710
|
+
let headersIdentity = OneKeyImageHeadersIdentity(headers: headers)
|
|
711
|
+
if let headers {
|
|
712
|
+
context[.downloadRequestModifier] = SDWebImageDownloaderRequestModifier(headers: headers)
|
|
713
|
+
}
|
|
714
|
+
context[.cacheKeyFilter] = SDWebImageCacheKeyFilter { url in
|
|
715
|
+
filteredCacheKey(
|
|
716
|
+
for: url,
|
|
717
|
+
headersIdentity: headersIdentity
|
|
718
|
+
)
|
|
719
|
+
}
|
|
720
|
+
if let thumbnailPixelSize {
|
|
721
|
+
for (key, value) in thumbnailOptions(pixelSize: thumbnailPixelSize) {
|
|
722
|
+
context[key] = value
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
let cacheType = cacheType(for: cachePolicy)
|
|
727
|
+
context[.queryCacheType] = cacheType.rawValue
|
|
728
|
+
context[.storeCacheType] = cacheType.rawValue
|
|
729
|
+
context[.originalQueryCacheType] = cacheType.rawValue
|
|
730
|
+
context[.originalStoreCacheType] = cacheType.rawValue
|
|
731
|
+
return context
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
static func transportGuards(
|
|
735
|
+
tracker: OneKeyImageSafetyTracker
|
|
736
|
+
) -> [SDWebImageContextOption: Any] {
|
|
737
|
+
[
|
|
738
|
+
.downloadResponseModifier: SDWebImageDownloaderResponseModifier { response in
|
|
739
|
+
guard !tracker.inspectReceivedByteCount(response.expectedContentLength) else {
|
|
740
|
+
return nil
|
|
741
|
+
}
|
|
742
|
+
return response
|
|
743
|
+
},
|
|
744
|
+
.downloadDecryptor: SDWebImageDownloaderDecryptor { data, _ in
|
|
745
|
+
if let violation = OneKeyImageSafetyPolicy.violation(for: data) {
|
|
746
|
+
tracker.record(violation)
|
|
747
|
+
return nil
|
|
748
|
+
}
|
|
749
|
+
return data
|
|
750
|
+
},
|
|
751
|
+
]
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
static func filteredCacheKey(
|
|
755
|
+
for url: URL,
|
|
756
|
+
headersIdentity: OneKeyImageHeadersIdentity = OneKeyImageHeadersIdentity(headers: nil)
|
|
757
|
+
) -> String {
|
|
758
|
+
var identity = url.absoluteString
|
|
759
|
+
if let headersComponent = headersIdentity.cacheKeyComponent {
|
|
760
|
+
identity += "|headers:\(headersComponent)"
|
|
761
|
+
}
|
|
762
|
+
return "\(identity)|\(safetyCacheGeneration)"
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
static func thumbnailOptions(pixelSize: CGSize) -> [SDWebImageContextOption: Any] {
|
|
766
|
+
guard pixelSize.width > 0, pixelSize.height > 0 else { return [:] }
|
|
767
|
+
return [
|
|
768
|
+
.imageThumbnailPixelSize: NSValue(cgSize: pixelSize),
|
|
769
|
+
.imagePreserveAspectRatio: true,
|
|
770
|
+
]
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
static func cacheType(for policy: OneKeyImageCachePolicy) -> SDImageCacheType {
|
|
774
|
+
switch policy {
|
|
775
|
+
case .memoryDisk: return .all
|
|
776
|
+
case .memory: return .memory
|
|
777
|
+
case .disk: return .disk
|
|
778
|
+
case .none: return .none
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|