@onekeyfe/react-native-split-bundle-loader 1.1.50 → 1.1.51
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/android/src/main/java/com/splitbundleloader/SplitBundleLoaderModule.kt +40 -1
- package/ios/SplitBundleLoader.h +4 -0
- package/ios/SplitBundleLoader.mm +68 -17
- package/lib/module/NativeSplitBundleLoader.js.map +1 -1
- package/lib/typescript/src/NativeSplitBundleLoader.d.ts +1 -0
- package/lib/typescript/src/NativeSplitBundleLoader.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/NativeSplitBundleLoader.ts +1 -0
|
@@ -85,6 +85,26 @@ class SplitBundleLoaderModule(reactContext: ReactApplicationContext) :
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
// -----------------------------------------------------------------------
|
|
89
|
+
// resolveSegmentPath (Phase 3)
|
|
90
|
+
// -----------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
override fun resolveSegmentPath(relativePath: String, sha256: String, promise: Promise) {
|
|
93
|
+
try {
|
|
94
|
+
val absolutePath = resolveSegmentPath(relativePath, sha256)
|
|
95
|
+
if (absolutePath != null) {
|
|
96
|
+
promise.resolve(absolutePath)
|
|
97
|
+
} else {
|
|
98
|
+
promise.reject(
|
|
99
|
+
"SPLIT_BUNDLE_NOT_FOUND",
|
|
100
|
+
"Segment file not found: $relativePath"
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
} catch (e: Exception) {
|
|
104
|
+
promise.reject("SPLIT_BUNDLE_RESOLVE_ERROR", e.message, e)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
88
108
|
// -----------------------------------------------------------------------
|
|
89
109
|
// loadSegment
|
|
90
110
|
// -----------------------------------------------------------------------
|
|
@@ -96,6 +116,10 @@ class SplitBundleLoaderModule(reactContext: ReactApplicationContext) :
|
|
|
96
116
|
sha256: String,
|
|
97
117
|
promise: Promise
|
|
98
118
|
) {
|
|
119
|
+
// NOTE (#44): sha256 param is not verified at load time by design.
|
|
120
|
+
// Per §6.4.1, runtime trusts that OTA install has already verified
|
|
121
|
+
// segment integrity. Builtin segments are signed as part of the APK/IPA.
|
|
122
|
+
// If runtime SHA-256 verification is needed, add it here.
|
|
99
123
|
try {
|
|
100
124
|
val segId = segmentId.toInt()
|
|
101
125
|
|
|
@@ -157,14 +181,29 @@ class SplitBundleLoaderModule(reactContext: ReactApplicationContext) :
|
|
|
157
181
|
// Path resolution helpers
|
|
158
182
|
// -----------------------------------------------------------------------
|
|
159
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Verify resolved path stays within the expected root directory (#45).
|
|
186
|
+
* Prevents path traversal via ".." components in relativePath.
|
|
187
|
+
*/
|
|
188
|
+
private fun isPathWithinRoot(root: File, resolved: File): Boolean {
|
|
189
|
+
return resolved.canonicalPath.startsWith(root.canonicalPath + File.separator) ||
|
|
190
|
+
resolved.canonicalPath == root.canonicalPath
|
|
191
|
+
}
|
|
192
|
+
|
|
160
193
|
private fun resolveSegmentPath(relativePath: String, expectedSha256: String): String? {
|
|
194
|
+
// Path traversal guard (#45)
|
|
195
|
+
if (relativePath.contains("..")) {
|
|
196
|
+
SBLLogger.warn("Path traversal rejected: $relativePath")
|
|
197
|
+
return null
|
|
198
|
+
}
|
|
199
|
+
|
|
161
200
|
// 1. Try OTA bundle directory first
|
|
162
201
|
val otaBundlePath = getOtaBundlePath()
|
|
163
202
|
if (!otaBundlePath.isNullOrEmpty()) {
|
|
164
203
|
val otaRoot = File(otaBundlePath).parentFile
|
|
165
204
|
if (otaRoot != null) {
|
|
166
205
|
val candidate = File(otaRoot, relativePath)
|
|
167
|
-
if (candidate.exists()) {
|
|
206
|
+
if (candidate.exists() && isPathWithinRoot(otaRoot, candidate)) {
|
|
168
207
|
return candidate.absolutePath
|
|
169
208
|
}
|
|
170
209
|
}
|
package/ios/SplitBundleLoader.h
CHANGED
|
@@ -10,5 +10,9 @@
|
|
|
10
10
|
sha256:(NSString *)sha256
|
|
11
11
|
resolve:(RCTPromiseResolveBlock)resolve
|
|
12
12
|
reject:(RCTPromiseRejectBlock)reject;
|
|
13
|
+
- (void)resolveSegmentPath:(NSString *)relativePath
|
|
14
|
+
sha256:(NSString *)sha256
|
|
15
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
16
|
+
reject:(RCTPromiseRejectBlock)reject;
|
|
13
17
|
|
|
14
18
|
@end
|
package/ios/SplitBundleLoader.mm
CHANGED
|
@@ -66,6 +66,11 @@
|
|
|
66
66
|
|
|
67
67
|
/// Registers a segment with the current runtime, supporting both legacy bridge
|
|
68
68
|
/// and bridgeless (RCTHost) architectures (#13).
|
|
69
|
+
///
|
|
70
|
+
/// Thread safety (#57): This method is called from the TurboModule (JS thread).
|
|
71
|
+
/// RCTBridge.registerSegmentWithId:path: internally registers the segment with
|
|
72
|
+
/// the Hermes runtime on the JS thread, which is the correct calling context.
|
|
73
|
+
/// No queue dispatch is needed.
|
|
69
74
|
+ (BOOL)registerSegment:(int)segmentId path:(NSString *)path error:(NSError **)outError
|
|
70
75
|
{
|
|
71
76
|
// Try legacy bridge first
|
|
@@ -127,6 +132,62 @@
|
|
|
127
132
|
}
|
|
128
133
|
}
|
|
129
134
|
|
|
135
|
+
// MARK: - Path resolution helper
|
|
136
|
+
|
|
137
|
+
/// Resolves a relative segment path to an absolute path, checking OTA then builtin.
|
|
138
|
+
/// Returns nil if the segment file is not found.
|
|
139
|
+
+ (nullable NSString *)resolveAbsolutePath:(NSString *)relativePath
|
|
140
|
+
{
|
|
141
|
+
// 1. Try OTA bundle root first
|
|
142
|
+
NSString *otaPath = [SplitBundleLoader otaBundlePath];
|
|
143
|
+
if (otaPath) {
|
|
144
|
+
NSString *otaRoot = [otaPath stringByDeletingLastPathComponent];
|
|
145
|
+
NSString *candidate = [[otaRoot stringByAppendingPathComponent:relativePath] stringByStandardizingPath];
|
|
146
|
+
if ([candidate hasPrefix:otaRoot] &&
|
|
147
|
+
[[NSFileManager defaultManager] fileExistsAtPath:candidate]) {
|
|
148
|
+
return candidate;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 2. Fallback to builtin resource path
|
|
153
|
+
NSString *builtinRoot = [[NSBundle mainBundle] resourcePath];
|
|
154
|
+
NSString *candidate = [[builtinRoot stringByAppendingPathComponent:relativePath] stringByStandardizingPath];
|
|
155
|
+
if ([candidate hasPrefix:builtinRoot] &&
|
|
156
|
+
[[NSFileManager defaultManager] fileExistsAtPath:candidate]) {
|
|
157
|
+
return candidate;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return nil;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// MARK: - resolveSegmentPath (Phase 3)
|
|
164
|
+
|
|
165
|
+
- (void)resolveSegmentPath:(NSString *)relativePath
|
|
166
|
+
sha256:(NSString *)sha256
|
|
167
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
168
|
+
reject:(RCTPromiseRejectBlock)reject
|
|
169
|
+
{
|
|
170
|
+
@try {
|
|
171
|
+
if ([relativePath containsString:@".."]) {
|
|
172
|
+
reject(@"SPLIT_BUNDLE_INVALID_PATH",
|
|
173
|
+
[NSString stringWithFormat:@"Path traversal rejected: %@", relativePath],
|
|
174
|
+
nil);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
NSString *absolutePath = [SplitBundleLoader resolveAbsolutePath:relativePath];
|
|
179
|
+
if (absolutePath) {
|
|
180
|
+
resolve(absolutePath);
|
|
181
|
+
} else {
|
|
182
|
+
reject(@"SPLIT_BUNDLE_NOT_FOUND",
|
|
183
|
+
[NSString stringWithFormat:@"Segment file not found: %@", relativePath],
|
|
184
|
+
nil);
|
|
185
|
+
}
|
|
186
|
+
} @catch (NSException *exception) {
|
|
187
|
+
reject(@"SPLIT_BUNDLE_RESOLVE_ERROR", exception.reason, nil);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
130
191
|
// MARK: - loadSegment
|
|
131
192
|
|
|
132
193
|
- (void)loadSegment:(double)segmentId
|
|
@@ -138,26 +199,16 @@
|
|
|
138
199
|
{
|
|
139
200
|
@try {
|
|
140
201
|
int segId = (int)segmentId;
|
|
141
|
-
NSString *absolutePath = nil;
|
|
142
202
|
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
absolutePath = candidate;
|
|
150
|
-
}
|
|
203
|
+
// Path traversal guard (#45)
|
|
204
|
+
if ([relativePath containsString:@".."]) {
|
|
205
|
+
reject(@"SPLIT_BUNDLE_INVALID_PATH",
|
|
206
|
+
[NSString stringWithFormat:@"Path traversal rejected: %@", relativePath],
|
|
207
|
+
nil);
|
|
208
|
+
return;
|
|
151
209
|
}
|
|
152
210
|
|
|
153
|
-
|
|
154
|
-
if (!absolutePath) {
|
|
155
|
-
NSString *builtinRoot = [[NSBundle mainBundle] resourcePath];
|
|
156
|
-
NSString *candidate = [builtinRoot stringByAppendingPathComponent:relativePath];
|
|
157
|
-
if ([[NSFileManager defaultManager] fileExistsAtPath:candidate]) {
|
|
158
|
-
absolutePath = candidate;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
211
|
+
NSString *absolutePath = [SplitBundleLoader resolveAbsolutePath:relativePath];
|
|
161
212
|
|
|
162
213
|
if (!absolutePath) {
|
|
163
214
|
reject(@"SPLIT_BUNDLE_NOT_FOUND",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeSplitBundleLoader.ts"],"mappings":";;AAAA,SAASA,mBAAmB,QAAQ,cAAc;
|
|
1
|
+
{"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeSplitBundleLoader.ts"],"mappings":";;AAAA,SAASA,mBAAmB,QAAQ,cAAc;AAsBlD,eAAeA,mBAAmB,CAACC,YAAY,CAAO,mBAAmB,CAAC","ignoreList":[]}
|
|
@@ -9,6 +9,7 @@ export interface Spec extends TurboModule {
|
|
|
9
9
|
bundleVersion?: string;
|
|
10
10
|
}>;
|
|
11
11
|
loadSegment(segmentId: number, segmentKey: string, relativePath: string, sha256: string): Promise<void>;
|
|
12
|
+
resolveSegmentPath(relativePath: string, sha256: string): Promise<string>;
|
|
12
13
|
}
|
|
13
14
|
declare const _default: Spec;
|
|
14
15
|
export default _default;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NativeSplitBundleLoader.d.ts","sourceRoot":"","sources":["../../../src/NativeSplitBundleLoader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,uBAAuB,IAAI,OAAO,CAAC;QACjC,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,aAAa,EAAE,MAAM,CAAC;QACtB,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC,CAAC;IACH,WAAW,CACT,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"NativeSplitBundleLoader.d.ts","sourceRoot":"","sources":["../../../src/NativeSplitBundleLoader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,uBAAuB,IAAI,OAAO,CAAC;QACjC,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,aAAa,EAAE,MAAM,CAAC;QACtB,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC,CAAC;IACH,WAAW,CACT,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,kBAAkB,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC3E;;AAED,wBAA2E"}
|
package/package.json
CHANGED
|
@@ -17,6 +17,7 @@ export interface Spec extends TurboModule {
|
|
|
17
17
|
relativePath: string,
|
|
18
18
|
sha256: string,
|
|
19
19
|
): Promise<void>;
|
|
20
|
+
resolveSegmentPath(relativePath: string, sha256: string): Promise<string>;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export default TurboModuleRegistry.getEnforcing<Spec>('SplitBundleLoader');
|