Skip to content

Commit 1ea798f

Browse files
authored
Merge pull request #670 from OneSignal/fix/file-type-detection
Improving file type detection for attachments
2 parents 07508a1 + e00d0ac commit 1ea798f

File tree

7 files changed

+121
-62
lines changed

7 files changed

+121
-62
lines changed

iOS_SDK/OneSignalDevApp/OneSignalDevApp.xcodeproj/xcshareddata/xcschemes/OneSignalDevApp.xcscheme

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,7 @@
2626
buildConfiguration = "Debug"
2727
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
2828
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
29-
language = ""
3029
shouldUseLaunchSchemeArgsEnv = "YES">
31-
<Testables>
32-
</Testables>
3330
<MacroExpansion>
3431
<BuildableReference
3532
BuildableIdentifier = "primary"
@@ -46,12 +43,23 @@
4643
isEnabled = "YES">
4744
</AdditionalOption>
4845
</AdditionalOptions>
46+
<Testables>
47+
<TestableReference
48+
skipped = "NO">
49+
<BuildableReference
50+
BuildableIdentifier = "primary"
51+
BlueprintIdentifier = "911E2CB91E398AB3003112A4"
52+
BuildableName = "UnitTests.xctest"
53+
BlueprintName = "UnitTests"
54+
ReferencedContainer = "container:../OneSignalSDK/OneSignal.xcodeproj">
55+
</BuildableReference>
56+
</TestableReference>
57+
</Testables>
4958
</TestAction>
5059
<LaunchAction
5160
buildConfiguration = "Debug"
5261
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
5362
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
54-
language = ""
5563
launchStyle = "0"
5664
useCustomWorkingDirectory = "NO"
5765
ignoresPersistentStateOnLaunch = "NO"

iOS_SDK/OneSignalSDK/Source/NSURL+OneSignal.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,6 @@
3131

3232
- (NSString *)valueFromQueryParameter:(NSString *)parameter;
3333

34+
- (NSString *)supportedFileExtensionFromQueryItems;
35+
3436
@end

iOS_SDK/OneSignalSDK/Source/NSURL+OneSignal.m

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,38 @@
2626
*/
2727

2828
#import "NSURL+OneSignal.h"
29+
#import "OneSignalCommonDefines.h"
2930

3031
@implementation NSURL (OneSignal)
3132
- (NSString *)valueFromQueryParameter:(NSString *)parameter {
3233
NSURLComponents *components = [NSURLComponents componentsWithURL:self resolvingAgainstBaseURL:false];
3334

34-
for(NSURLQueryItem *item in components.queryItems)
35+
for (NSURLQueryItem *item in components.queryItems)
3536
if([item.name isEqualToString:parameter])
3637
return item.value;
3738

3839
return nil;
3940
}
41+
42+
- (NSString *)supportedFileExtensionFromQueryItems {
43+
NSURLComponents *components = [NSURLComponents componentsWithURL:self resolvingAgainstBaseURL:false];
44+
45+
for (NSURLQueryItem *item in [components.queryItems reverseObjectEnumerator]) {
46+
NSString *value = item.value;
47+
NSString *extension = [self findExtensionInParam:value];
48+
if (extension)
49+
return extension;
50+
}
51+
return nil;
52+
}
53+
54+
- (NSString*)findExtensionInParam:(NSString *)parameter {
55+
NSArray *paramComponents = [parameter componentsSeparatedByString:@"."];
56+
for (NSString *component in [paramComponents reverseObjectEnumerator]) {
57+
if ([ONESIGNAL_SUPPORTED_ATTACHMENT_TYPES containsObject:component])
58+
return component;
59+
}
60+
return nil;
61+
}
62+
4063
@end

iOS_SDK/OneSignalSDK/Source/OneSignalCommonDefines.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,4 +238,6 @@ typedef enum {GET, POST, HEAD, PUT, DELETE, OPTIONS, CONNECT, TRACE} HTTPMethod;
238238
// variance and floating-point error.
239239
#define OS_ROUGHLY_EQUAL(left, right) (fabs(left - right) < 0.03)
240240

241+
#define MAX_NOTIFICATION_MEDIA_SIZE_BYTES 50000000
242+
241243
#endif /* OneSignalCommonDefines_h */

iOS_SDK/OneSignalSDK/Source/OneSignalHelper.m

Lines changed: 56 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,13 @@ -(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataT
7979
[outputHandle writeData:data];
8080
}
8181

82-
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)aResponse completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
82+
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)aResponse completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
8383
response = aResponse;
84+
long long expectedLength = response.expectedContentLength;
85+
if (expectedLength > MAX_NOTIFICATION_MEDIA_SIZE_BYTES) { //Enforcing 50 mb limit on media before downloading
86+
completionHandler(NSURLSessionResponseCancel);
87+
return;
88+
}
8489
completionHandler(NSURLSessionResponseAllow);
8590
}
8691

@@ -777,29 +782,14 @@ + (void)addNotificationRequest:(OSNotificationPayload*)payload
777782
/*
778783
Synchroneously downloads an attachment
779784
On success returns bundle resource name, otherwise returns nil
780-
The preference order for file type determination is as follows:
781-
1. File extension in the actual URL
782-
2. MIME type
783-
3. URL Query parameter called 'filename', such as test.jpg. The SDK will extract the file extension from it
784785
*/
785-
+ (NSString*)downloadMediaAndSaveInBundle:(NSString*)urlString {
786+
+ (NSString *)downloadMediaAndSaveInBundle:(NSString *)urlString {
786787

787788
let url = [NSURL URLWithString:urlString];
788-
789-
NSString* extension = url.pathExtension;
790-
791-
if ([extension isEqualToString:@""])
792-
extension = nil;
793-
794-
// Unrecognized extention
795-
if (extension != nil && ![ONESIGNAL_SUPPORTED_ATTACHMENT_TYPES containsObject:extension])
796-
return nil;
797-
789+
790+
//Download the file
798791
var name = [self randomStringWithLength:10];
799-
800-
if (extension)
801-
name = [name stringByAppendingString:[NSString stringWithFormat:@".%@", extension]];
802-
792+
803793
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
804794
NSString* filePath = [paths[0] stringByAppendingPathComponent:name];
805795

@@ -813,33 +803,24 @@ + (NSString*)downloadMediaAndSaveInBundle:(NSString*)urlString {
813803
[OneSignal onesignal_Log:ONE_S_LL_ERROR message:[NSString stringWithFormat:@"Encountered an error while attempting to download file with URL: %@", error]];
814804
return nil;
815805
}
806+
807+
NSString *extension = [OneSignalHelper getSupportedFileExtensionFromURL:url mimeType:mimeType];
808+
if (!extension || [extension isEqualToString:@""])
809+
return nil;
816810

817-
if (!extension) {
818-
NSString *newExtension;
819-
820-
if (mimeType != nil && ![mimeType isEqualToString:@""]) {
821-
newExtension = mimeType.fileExtensionForMimeType;
822-
} else {
823-
newExtension = [[[NSURL URLWithString:urlString] valueFromQueryParameter:@"filename"] supportedFileExtension];
824-
}
825-
826-
if (!newExtension || ![ONESIGNAL_SUPPORTED_ATTACHMENT_TYPES containsObject:newExtension])
827-
return nil;
828-
829-
name = [NSString stringWithFormat:@"%@.%@", name, newExtension];
830-
831-
let newPath = [paths[0] stringByAppendingPathComponent:[NSString stringWithFormat:@"%@", name]];
832-
833-
[[NSFileManager defaultManager] moveItemAtPath:filePath toPath:newPath error:&error];
834-
}
811+
name = [NSString stringWithFormat:@"%@.%@", name, extension];
812+
813+
let newPath = [paths[0] stringByAppendingPathComponent:[NSString stringWithFormat:@"%@", name]];
814+
815+
[[NSFileManager defaultManager] moveItemAtPath:filePath toPath:newPath error:&error];
835816

836817
if (error) {
837818
[OneSignal onesignal_Log:ONE_S_LL_ERROR message:[NSString stringWithFormat:@"Encountered an error while attempting to download file with URL: %@", error]];
838819
return nil;
839820
}
840-
821+
841822
let standardUserDefaults = OneSignalUserDefaults.initStandard;
842-
823+
843824
NSArray* cachedFiles = [standardUserDefaults getSavedObjectForKey:OSUD_TEMP_CACHED_NOTIFICATION_MEDIA defaultValue:nil];
844825
NSMutableArray* appendedCache;
845826
if (cachedFiles) {
@@ -848,15 +829,49 @@ + (NSString*)downloadMediaAndSaveInBundle:(NSString*)urlString {
848829
}
849830
else
850831
appendedCache = [[NSMutableArray alloc] initWithObjects:name, nil];
851-
832+
852833
[standardUserDefaults saveObjectForKey:OSUD_TEMP_CACHED_NOTIFICATION_MEDIA withValue:appendedCache];
853834
return name;
854835
} @catch (NSException *exception) {
855836
[OneSignal onesignal_Log:ONE_S_LL_ERROR message:[NSString stringWithFormat:@"OneSignal encountered an exception while downloading file (%@), exception: %@", url, exception.description]];
856837

857838
return nil;
858839
}
840+
}
841+
859842

843+
/*
844+
The preference order for file type determination is as follows:
845+
1. URL Query parameter called 'filename', such as test.jpg. The SDK will extract the file extension from it
846+
2. MIME type
847+
3. File extension in the actual URL
848+
4. A file extension extracted by searching through all URL Query parameters
849+
*/
850+
+ (NSString *)getSupportedFileExtensionFromURL:(NSURL *)url mimeType:(NSString *)mimeType {
851+
//Try to get extension from the filename parameter
852+
NSString* extension = [[url valueFromQueryParameter:@"filename"]
853+
supportedFileExtension];
854+
if (extension && [ONESIGNAL_SUPPORTED_ATTACHMENT_TYPES containsObject:extension]) {
855+
return extension;
856+
}
857+
//Use the MIME type for the extension
858+
if (mimeType != nil && ![mimeType isEqualToString:@""]) {
859+
extension = mimeType.fileExtensionForMimeType;
860+
if (extension && [ONESIGNAL_SUPPORTED_ATTACHMENT_TYPES containsObject:extension]) {
861+
return extension;
862+
}
863+
}
864+
//Try using url.pathExtension
865+
extension = url.pathExtension;
866+
if (extension && [ONESIGNAL_SUPPORTED_ATTACHMENT_TYPES containsObject:extension]) {
867+
return extension;
868+
}
869+
//Try getting an extension from the query
870+
extension = url.supportedFileExtensionFromQueryItems;
871+
if (extension && [ONESIGNAL_SUPPORTED_ATTACHMENT_TYPES containsObject:extension]) {
872+
return extension;
873+
}
874+
return nil;
860875
}
861876

862877
// TODO: Add back after testing

iOS_SDK/OneSignalSDK/UnitTests/Shadows/NSURLSessionOverrider.m

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ + (NSString *)overrideDownloadItemAtURL:(NSURL*)url toFile:(NSString*)localPath
5050

5151
if ([url.absoluteString isEqualToString:@"http://domain.com/file"])
5252
return @"image/png";
53-
else if ([url.absoluteString isEqualToString:@"http://domain.com/secondFile"])
54-
return nil;
53+
else if ([url.path isEqualToString:@"/secondFile"])
54+
return @"image/heic";
5555
else
5656
return @"image/jpg";
5757
}

iOS_SDK/OneSignalSDK/UnitTests/UnitTests.m

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2136,16 +2136,7 @@ - (id)exampleNotificationJSONWithMediaURL:(NSString *)urlString {
21362136
}};
21372137
}
21382138

2139-
- (void)testExtractFileExtensionFromMimeType {
2140-
//test to make sure the MIME type parsing works correctly
2141-
//NSURLSessionOverrider returns image/png for this URL
2142-
id pngFormat = [self exampleNotificationJSONWithMediaURL:@"http://domain.com/file"];
2143-
2144-
let downloadedPngFilename = [self deliverNotificationWithJSON:pngFormat].URL.lastPathComponent;
2145-
XCTAssertTrue([downloadedPngFilename.supportedFileExtension isEqualToString:@"png"]);
2146-
}
2147-
2148-
- (void)testExtractFileExtensionFromQueryParameter {
2139+
- (void)testExtractFileExtensionFromFileNameQueryParameter {
21492140
// we allow developers to add ?filename=test.jpg (for example) to attachment URL's in cases where there is no extension & no mime type
21502141
// tests to make sure the SDK correctly extracts the file extension from the `filename` URL query parameter
21512142
// NSURLSessionOverrider returns nil for this URL
@@ -2155,14 +2146,32 @@ - (void)testExtractFileExtensionFromQueryParameter {
21552146
XCTAssertTrue([downloadedJpgFilename.supportedFileExtension isEqualToString:@"jpg"]);
21562147
}
21572148

2158-
- (void)testFileExtensionPrioritizesURLFileExtension {
2159-
//tests to make sure that the URL's file extension is prioritized above the MIME type and URL query param
2160-
//this attachment URL will have a file extension, a MIME type, and a filename query parameter. It should prioritize the URL file extension (gif)
2149+
- (void)testExtractFileExtensionFromMimeType {
2150+
//test to make sure the MIME type parsing works correctly
2151+
//NSURLSessionOverrider returns image/png for this URL
2152+
id pngFormat = [self exampleNotificationJSONWithMediaURL:@"http://domain.com/file"];
2153+
2154+
let downloadedPngFilename = [self deliverNotificationWithJSON:pngFormat].URL.lastPathComponent;
2155+
XCTAssertTrue([downloadedPngFilename.supportedFileExtension isEqualToString:@"png"]);
2156+
}
2157+
2158+
- (void)testFileExtensionPrioritizesFileNameParameter {
2159+
//tests to make sure that the filename query parameter is prioritized above the MIME type and URL extension
2160+
//this attachment URL will have a file extension, a MIME type, and a filename query parameter. It should prioritize the filename query parameter (png)
21612161
//NSURLSessionOverrider returns image/png for this URL
21622162
id gifFormat = [self exampleNotificationJSONWithMediaURL:@"http://domain.com/file.gif?filename=test.png"];
21632163

21642164
let downloadedGifFilename = [self deliverNotificationWithJSON:gifFormat].URL.lastPathComponent;
2165-
XCTAssertTrue([downloadedGifFilename.supportedFileExtension isEqualToString:@"gif"]);
2165+
XCTAssertTrue([downloadedGifFilename.supportedFileExtension isEqualToString:@"png"]);
2166+
}
2167+
2168+
- (void)testExtractFileExtensionFromAnyParameter {
2169+
//test to make sure the fallback of parsing all parameters for a file type works correctly
2170+
//NSURLSessionOverrider returns an unallowed extension (heic) for this URL to test the fallback
2171+
id pngFormat = [self exampleNotificationJSONWithMediaURL:@"http://domain.com/secondFile?file=test.png&media=image&type=.fakeextension"];
2172+
2173+
let downloadedPngFilename = [self deliverNotificationWithJSON:pngFormat].URL.lastPathComponent;
2174+
XCTAssertTrue([downloadedPngFilename.supportedFileExtension isEqualToString:@"png"]);
21662175
}
21672176

21682177
/*

0 commit comments

Comments
 (0)