Skip to content

Commit 326e4f7

Browse files
committed
Clamp blob range in RCTBlobManager instead of raising NSRangeException
1 parent a24bd32 commit 326e4f7

1 file changed

Lines changed: 38 additions & 2 deletions

File tree

‎packages/react-native/Libraries/Blob/RCTBlobManager.mm‎

100755100644
Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
#import <FBReactNativeSpec/FBReactNativeSpec.h>
1313
#import <React/RCTConvert.h>
14+
#import <React/RCTLog.h>
1415
#import <React/RCTMockDef.h>
1516
#import <React/RCTNetworking.h>
1617
#import <React/RCTUtils.h>
@@ -111,8 +112,30 @@ - (NSData *)resolve:(NSString *)blobId offset:(NSInteger)offset size:(NSInteger)
111112
if (!data) {
112113
return nil;
113114
}
114-
if (offset != 0 || (size != -1 && size != data.length)) {
115-
data = [data subdataWithRange:NSMakeRange(offset, size)];
115+
// The offset and size come from the JS-side blob descriptor, which can disagree with the bytes that were
116+
// actually stored. Clamp the range to what is really here: an NSRangeException raised from here is fatal,
117+
// because this runs on a background queue with no JS frame on the stack to catch it.
118+
const NSInteger length = (NSInteger)data.length;
119+
if (offset < 0 || offset > length) {
120+
RCTLogWarn(
121+
@"[BlobManager] blob %@: offset %ld is out of bounds for %ld stored bytes", blobId, (long)offset, (long)length);
122+
return nil;
123+
}
124+
const NSInteger available = length - offset;
125+
// A negative size means "the rest of the blob", which is the existing contract for -1.
126+
if (size < 0 || size > available) {
127+
if (size > available) {
128+
RCTLogWarn(
129+
@"[BlobManager] blob %@: %ld bytes requested at offset %ld, but only %ld are stored; truncating",
130+
blobId,
131+
(long)size,
132+
(long)offset,
133+
(long)available);
134+
}
135+
size = available;
136+
}
137+
if (offset != 0 || size != length) {
138+
data = [data subdataWithRange:NSMakeRange((NSUInteger)offset, (NSUInteger)size)];
116139
}
117140
return data;
118141
}
@@ -194,9 +217,22 @@ - (void)createFromParts:(NSArray<NSDictionary<NSString *, id> *> *)parts withId:
194217

195218
if ([type isEqualToString:@"blob"]) {
196219
NSData *partData = [self resolve:part[@"data"]];
220+
if (partData == nil) {
221+
// A part that cannot be resolved contributes nothing, which is one way a blob ends up shorter than
222+
// the size JS recorded for it. Say so rather than failing silently.
223+
RCTLogWarn(
224+
@"[BlobManager] blob %@: blob part %@ could not be resolved and contributes 0 bytes",
225+
blobId,
226+
[RCTConvert NSString:part[@"data"][@"blobId"]]);
227+
continue;
228+
}
197229
[data appendData:partData];
198230
} else if ([type isEqualToString:@"string"]) {
199231
NSData *partData = [[RCTConvert NSString:part[@"data"]] dataUsingEncoding:NSUTF8StringEncoding];
232+
if (partData == nil) {
233+
RCTLogWarn(@"[BlobManager] blob %@: a string part could not be encoded and contributes 0 bytes", blobId);
234+
continue;
235+
}
200236
[data appendData:partData];
201237
} else {
202238
[NSException raise:@"Invalid type for blob" format:@"%@ is invalid", type];

0 commit comments

Comments
 (0)