|
11 | 11 |
|
12 | 12 | #import <FBReactNativeSpec/FBReactNativeSpec.h> |
13 | 13 | #import <React/RCTConvert.h> |
| 14 | +#import <React/RCTLog.h> |
14 | 15 | #import <React/RCTMockDef.h> |
15 | 16 | #import <React/RCTNetworking.h> |
16 | 17 | #import <React/RCTUtils.h> |
@@ -111,8 +112,30 @@ - (NSData *)resolve:(NSString *)blobId offset:(NSInteger)offset size:(NSInteger) |
111 | 112 | if (!data) { |
112 | 113 | return nil; |
113 | 114 | } |
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)]; |
116 | 139 | } |
117 | 140 | return data; |
118 | 141 | } |
@@ -194,9 +217,22 @@ - (void)createFromParts:(NSArray<NSDictionary<NSString *, id> *> *)parts withId: |
194 | 217 |
|
195 | 218 | if ([type isEqualToString:@"blob"]) { |
196 | 219 | 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 | + } |
197 | 229 | [data appendData:partData]; |
198 | 230 | } else if ([type isEqualToString:@"string"]) { |
199 | 231 | 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 | + } |
200 | 236 | [data appendData:partData]; |
201 | 237 | } else { |
202 | 238 | [NSException raise:@"Invalid type for blob" format:@"%@ is invalid", type]; |
|
0 commit comments