From 196f5e63f07bf1ce223b71f82c49c116b1e91095 Mon Sep 17 00:00:00 2001 From: Martin Jansen Date: Tue, 21 Feb 2012 11:01:55 +0100 Subject: [PATCH 1/6] (At least) UIWebView instances get confused when the content encoding header is part of the MIMEType property of a NSURLResponse. Splitting the MIME type and passing the conding using textEncodingName fixes this. We probably need to figure out a more solid way to parse the MIME type. --- src/shared/AFURLCache.m | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/shared/AFURLCache.m b/src/shared/AFURLCache.m index f9fec41..26c239d 100755 --- a/src/shared/AFURLCache.m +++ b/src/shared/AFURLCache.m @@ -31,9 +31,22 @@ -(NSCachedURLResponse*)cachedResponseForRequest:(NSURLRequest*)request NSURL* url = request.URL; AFCacheableItem* item = [[AFCache sharedInstance] cacheableItemFromCacheStore:url]; if (item && item.cacheStatus == kCacheStatusFresh) { + + /* Split content type and content encoding from the "MIME type". */ + + /* TODO: Figure out a better way to parse the MIME type here. */ + NSString *mime = item.info.mimeType; + NSString *encoding = nil; + NSRange range = [mime rangeOfString:@"; charset="]; + if (range.length > 0) { + encoding = [mime substringFromIndex:(range.location + 10)]; + mime = [mime substringToIndex:range.location]; + } + NSURLResponse* response = [[NSURLResponse alloc] initWithURL:item.url - MIMEType:item.info.mimeType - expectedContentLength:[item.data length] textEncodingName:nil]; + MIMEType:mime + expectedContentLength:[item.data length] + textEncodingName:encoding]; NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:item.data userInfo:nil storagePolicy:NSURLCacheStorageAllowedInMemoryOnly]; [response release]; return [cachedResponse autorelease]; From a7f4cb4eba8cb4f07a612bbe0438f5d7de2b319b Mon Sep 17 00:00:00 2001 From: Martin Jansen Date: Sat, 25 Feb 2012 10:24:55 +0100 Subject: [PATCH 2/6] The name of the manifest is actually manifest.afcache. --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index f43e8f8..a02ceb3 100644 --- a/README.markdown +++ b/README.markdown @@ -106,7 +106,7 @@ Logging is achieved via an AFLog macro which is either ## Anatomy of the manifest file -The afcache.manifest file contains an entry for every file contained in the archive. One entry looks like this: +The manifest.afcache file contains an entry for every file contained in the archive. One entry looks like this: URL ; last-modified ; expires\n ; mimetype From c0bf897b0d5993a4018decd60fbff0de1ab41c3f Mon Sep 17 00:00:00 2001 From: Martin Jansen Date: Sat, 25 Feb 2012 23:09:04 +0100 Subject: [PATCH 3/6] Make the unit testing target compile. --- AFCache-iOS.xcodeproj/project.pbxproj | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/AFCache-iOS.xcodeproj/project.pbxproj b/AFCache-iOS.xcodeproj/project.pbxproj index 35ac192..2f67193 100644 --- a/AFCache-iOS.xcodeproj/project.pbxproj +++ b/AFCache-iOS.xcodeproj/project.pbxproj @@ -31,7 +31,6 @@ 0566AE551332D98200583E6A /* Icon72x72.png in Resources */ = {isa = PBXBuildFile; fileRef = 0566AE521332D98200583E6A /* Icon72x72.png */; }; 0566AE561332D98200583E6A /* Icon114x114.png in Resources */ = {isa = PBXBuildFile; fileRef = 0566AE531332D98200583E6A /* Icon114x114.png */; }; 0566AE581332DA2500583E6A /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 0566AE571332DA2500583E6A /* libz.dylib */; }; - 0569DE601328AD5B00B3D016 /* AFCache-iOSTests-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 0569DE591328AD5B00B3D016 /* AFCache-iOSTests-Info.plist */; }; 0569DE611328AD5B00B3D016 /* AFCache_iOSTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0569DE5C1328AD5B00B3D016 /* AFCache_iOSTests.m */; }; 0569DE621328AD5B00B3D016 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0569DE5D1328AD5B00B3D016 /* InfoPlist.strings */; }; 0569DE941328AD9C00B3D016 /* AFRegexString.h in Headers */ = {isa = PBXBuildFile; fileRef = 0569DE671328AD9C00B3D016 /* AFRegexString.h */; }; @@ -84,6 +83,8 @@ 0569DECA1328AD9C00B3D016 /* DateParser.h in Headers */ = {isa = PBXBuildFile; fileRef = 0569DE921328AD9C00B3D016 /* DateParser.h */; }; 0569DECB1328AD9C00B3D016 /* DateParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 0569DE931328AD9C00B3D016 /* DateParser.m */; }; 0569DECC1328AD9C00B3D016 /* DateParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 0569DE931328AD9C00B3D016 /* DateParser.m */; }; + 5BEBBEF714F992EB003C5E09 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 0566AE571332DA2500583E6A /* libz.dylib */; }; + 5BEBBEF814F992F8003C5E09 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0566AE3A1332D7E300583E6A /* SystemConfiguration.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -185,6 +186,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 5BEBBEF814F992F8003C5E09 /* SystemConfiguration.framework in Frameworks */, + 5BEBBEF714F992EB003C5E09 /* libz.dylib in Frameworks */, 051B387C1328AACA0057F2F5 /* UIKit.framework in Frameworks */, 051B387D1328AACA0057F2F5 /* Foundation.framework in Frameworks */, 051B387F1328AACA0057F2F5 /* CoreGraphics.framework in Frameworks */, @@ -511,7 +514,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0569DE601328AD5B00B3D016 /* AFCache-iOSTests-Info.plist in Resources */, 0569DE621328AD5B00B3D016 /* InfoPlist.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -694,8 +696,9 @@ "$(DEVELOPER_LIBRARY_DIR)/Frameworks", ); GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "AFCache-iOSTests/AFCache-iOSTests-Prefix.pch"; - INFOPLIST_FILE = "AFCache-iOSTests/AFCache-iOSTests-Info.plist"; + GCC_PREFIX_HEADER = "test/iOS/AFCache-iOSTests-Prefix.pch"; + GCC_VERSION = ""; + INFOPLIST_FILE = "test/iOS/AFCache-iOSTests-Info.plist"; OTHER_LDFLAGS = ( "-framework", SenTestingKit, @@ -714,8 +717,9 @@ "$(DEVELOPER_LIBRARY_DIR)/Frameworks", ); GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "AFCache-iOSTests/AFCache-iOSTests-Prefix.pch"; - INFOPLIST_FILE = "AFCache-iOSTests/AFCache-iOSTests-Info.plist"; + GCC_PREFIX_HEADER = "test/iOS/AFCache-iOSTests-Prefix.pch"; + GCC_VERSION = ""; + INFOPLIST_FILE = "test/iOS/AFCache-iOSTests-Info.plist"; OTHER_LDFLAGS = ( "-framework", SenTestingKit, From 28420afb036f57e00453f7b9e47a58f34faf7e63 Mon Sep 17 00:00:00 2001 From: Martin Jansen Date: Sat, 25 Feb 2012 23:40:14 +0100 Subject: [PATCH 4/6] RFC 2616 conforming parser for Internet Media Types. --- src/shared/AFMediaTypeParser.h | 20 ++++++++++++++ src/shared/AFMediaTypeParser.m | 50 ++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 src/shared/AFMediaTypeParser.h create mode 100644 src/shared/AFMediaTypeParser.m diff --git a/src/shared/AFMediaTypeParser.h b/src/shared/AFMediaTypeParser.h new file mode 100644 index 0000000..37be4f4 --- /dev/null +++ b/src/shared/AFMediaTypeParser.h @@ -0,0 +1,20 @@ +// +// AFMIMEParser.h +// AFCache-iOS +// +// Created by Martin Jansen on 25.02.12. +// Copyright (c) 2012 Artifacts - Fine Software Development. All rights reserved. +// + +#import + +@interface AFMIMEParser : NSObject { + NSString *mimeType; +} + +- (id) initWithMIMEType:(NSString*)theMIMEType; + +- (NSString*) textEncoding; +- (NSString*) contentType; + +@end diff --git a/src/shared/AFMediaTypeParser.m b/src/shared/AFMediaTypeParser.m new file mode 100644 index 0000000..cba7ba5 --- /dev/null +++ b/src/shared/AFMediaTypeParser.m @@ -0,0 +1,50 @@ +// +// AFMIMEParser.m +// AFCache-iOS +// +// Created by Martin Jansen on 25.02.12. +// Copyright (c) 2012 Artifacts - Fine Software Development. All rights reserved. +// + +#import "AFMIMEParser.h" + +@implementation AFMIMEParser + +#pragma mark Object lifecycle + +- (id) initWithMIMEType:(NSString*)theMIMEType +{ + self = [super init]; + + if (self) { + mimeType = [theMIMEType retain]; + } + + return self; +} + +- (void) dealloc +{ + [mimeType release]; + + [super dealloc]; +} + +#pragma mark - + +- (void) parse +{ + +} + +- (NSString*) textEncoding +{ + return @""; +} + +- (NSString*) contentType +{ + return @""; +} + +@end From 3605bc42126a0a1c3ccad476522a5611671dcbe2 Mon Sep 17 00:00:00 2001 From: Martin Jansen Date: Sat, 25 Feb 2012 23:42:06 +0100 Subject: [PATCH 5/6] Committing pending changes regarding the RFC 2616 parser. These changes should have been part of the previous commit but somehow got lost somewhere. --- AFCache-iOS.xcodeproj/project.pbxproj | 10 ++++ src/shared/AFMediaTypeParser.h | 17 +++++-- src/shared/AFMediaTypeParser.m | 70 ++++++++++++++++++++++----- src/shared/AFURLCache.m | 17 ++----- test/iOS/AFCache_iOSTests.m | 20 ++++++-- 5 files changed, 101 insertions(+), 33 deletions(-) diff --git a/AFCache-iOS.xcodeproj/project.pbxproj b/AFCache-iOS.xcodeproj/project.pbxproj index 2f67193..0a7e24a 100644 --- a/AFCache-iOS.xcodeproj/project.pbxproj +++ b/AFCache-iOS.xcodeproj/project.pbxproj @@ -85,6 +85,9 @@ 0569DECC1328AD9C00B3D016 /* DateParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 0569DE931328AD9C00B3D016 /* DateParser.m */; }; 5BEBBEF714F992EB003C5E09 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 0566AE571332DA2500583E6A /* libz.dylib */; }; 5BEBBEF814F992F8003C5E09 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0566AE3A1332D7E300583E6A /* SystemConfiguration.framework */; }; + 5BEBBEFB14F993A7003C5E09 /* AFMediaTypeParser.h in Headers */ = {isa = PBXBuildFile; fileRef = 5BEBBEF914F993A7003C5E09 /* AFMediaTypeParser.h */; }; + 5BEBBEFC14F993A7003C5E09 /* AFMediaTypeParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BEBBEFA14F993A7003C5E09 /* AFMediaTypeParser.m */; }; + 5BEBBEFD14F993A7003C5E09 /* AFMediaTypeParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BEBBEFA14F993A7003C5E09 /* AFMediaTypeParser.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -171,6 +174,8 @@ 0569DE911328AD9C00B3D016 /* Constants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Constants.h; sourceTree = ""; }; 0569DE921328AD9C00B3D016 /* DateParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DateParser.h; sourceTree = ""; }; 0569DE931328AD9C00B3D016 /* DateParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DateParser.m; sourceTree = ""; }; + 5BEBBEF914F993A7003C5E09 /* AFMediaTypeParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFMediaTypeParser.h; sourceTree = ""; }; + 5BEBBEFA14F993A7003C5E09 /* AFMediaTypeParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFMediaTypeParser.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -370,6 +375,8 @@ 0569DE7E1328AD9C00B3D016 /* shared */ = { isa = PBXGroup; children = ( + 5BEBBEF914F993A7003C5E09 /* AFMediaTypeParser.h */, + 5BEBBEFA14F993A7003C5E09 /* AFMediaTypeParser.m */, 0569DE7F1328AD9C00B3D016 /* AFCache+Mimetypes.h */, 0569DE801328AD9C00B3D016 /* AFCache+Mimetypes.m */, 0569DE811328AD9C00B3D016 /* AFCache+Packaging.h */, @@ -422,6 +429,7 @@ 0569DEC61328AD9C00B3D016 /* AFURLCache.h in Headers */, 0569DEC91328AD9C00B3D016 /* Constants.h in Headers */, 0569DECA1328AD9C00B3D016 /* DateParser.h in Headers */, + 5BEBBEFB14F993A7003C5E09 /* AFMediaTypeParser.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -571,6 +579,7 @@ 0569DEC41328AD9C00B3D016 /* AFPackageInfo.m in Sources */, 0569DEC71328AD9C00B3D016 /* AFURLCache.m in Sources */, 0569DECB1328AD9C00B3D016 /* DateParser.m in Sources */, + 5BEBBEFC14F993A7003C5E09 /* AFMediaTypeParser.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -594,6 +603,7 @@ 0569DEC51328AD9C00B3D016 /* AFPackageInfo.m in Sources */, 0569DEC81328AD9C00B3D016 /* AFURLCache.m in Sources */, 0569DECC1328AD9C00B3D016 /* DateParser.m in Sources */, + 5BEBBEFD14F993A7003C5E09 /* AFMediaTypeParser.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/src/shared/AFMediaTypeParser.h b/src/shared/AFMediaTypeParser.h index 37be4f4..e7593c9 100644 --- a/src/shared/AFMediaTypeParser.h +++ b/src/shared/AFMediaTypeParser.h @@ -8,13 +8,20 @@ #import -@interface AFMIMEParser : NSObject { - NSString *mimeType; +/** + * Implements a RFC 2616 confirming parser for extracting the + * content type and the character encoding from Internet Media + * Types + */ +@interface AFMediaTypeParser : NSObject { + NSString* mimeType; + NSString* _textEncoding; + NSString* _contentType; } -- (id) initWithMIMEType:(NSString*)theMIMEType; +@property (nonatomic, readonly) NSString* textEncoding; +@property (nonatomic, readonly) NSString* contentType; -- (NSString*) textEncoding; -- (NSString*) contentType; +- (id) initWithMIMEType:(NSString*)theMIMEType; @end diff --git a/src/shared/AFMediaTypeParser.m b/src/shared/AFMediaTypeParser.m index cba7ba5..1f05449 100644 --- a/src/shared/AFMediaTypeParser.m +++ b/src/shared/AFMediaTypeParser.m @@ -6,20 +6,38 @@ // Copyright (c) 2012 Artifacts - Fine Software Development. All rights reserved. // -#import "AFMIMEParser.h" +#import "AFMediaTypeParser.h" -@implementation AFMIMEParser +#define kCharsetName @"charset" +#define kTokenDelimiter @";" +#define kParameterDelimiter @"=" + +@interface AFMediaTypeParser (private) + +- (void) parse; +- (NSString*) trim:(NSString*)aString; + +@end + +@implementation AFMediaTypeParser + +@synthesize textEncoding = _textEncoding; +@synthesize contentType = _contentType; #pragma mark Object lifecycle -- (id) initWithMIMEType:(NSString*)theMIMEType +- (id) initWithMIMEType:(NSString*)aMIMEType { self = [super init]; - + if (self) { - mimeType = [theMIMEType retain]; + mimeType = [aMIMEType retain]; + _textEncoding = nil; + _contentType = nil; + + [self parse]; } - + return self; } @@ -34,17 +52,43 @@ - (void) dealloc - (void) parse { - -} + NSArray *components = [mimeType componentsSeparatedByString:kTokenDelimiter]; -- (NSString*) textEncoding -{ - return @""; + if (1 >= [components count]) { + _contentType = [self trim:mimeType]; + return; + } + + _contentType = [self trim:(NSString*)[components objectAtIndex:0]]; + for (NSUInteger i = 1; i < [components count]; i++) { + NSString *parameter = [components objectAtIndex:i]; + NSArray *parameterComponents = [parameter componentsSeparatedByString:kParameterDelimiter]; + + if (2 != [parameterComponents count]) { + continue; + } + + NSString *name = [self trim:[parameterComponents objectAtIndex:0]]; + NSString *value = [self trim:[parameterComponents objectAtIndex:1]]; + + if ([name isEqualToString:kCharsetName]) { + _textEncoding = value; + break; + } + } } -- (NSString*) contentType +- (NSString*) trim:(NSString*)aString { - return @""; + // http://stackoverflow.com/a/8293671/132475 + + NSMutableString *mStr = [aString mutableCopy]; + CFStringTrimWhitespace((CFMutableStringRef)mStr); + + NSString *result = [mStr copy]; + + [mStr release]; + return [result autorelease]; } @end diff --git a/src/shared/AFURLCache.m b/src/shared/AFURLCache.m index 26c239d..bb4777a 100755 --- a/src/shared/AFURLCache.m +++ b/src/shared/AFURLCache.m @@ -23,6 +23,7 @@ #import "AFCacheableItem+Packaging.h" #import "AFCache+Packaging.h" #import "DateParser.h" +#import "AFMediaTypeParser.h" @implementation AFURLCache @@ -32,23 +33,15 @@ -(NSCachedURLResponse*)cachedResponseForRequest:(NSURLRequest*)request AFCacheableItem* item = [[AFCache sharedInstance] cacheableItemFromCacheStore:url]; if (item && item.cacheStatus == kCacheStatusFresh) { - /* Split content type and content encoding from the "MIME type". */ - - /* TODO: Figure out a better way to parse the MIME type here. */ - NSString *mime = item.info.mimeType; - NSString *encoding = nil; - NSRange range = [mime rangeOfString:@"; charset="]; - if (range.length > 0) { - encoding = [mime substringFromIndex:(range.location + 10)]; - mime = [mime substringToIndex:range.location]; - } + AFMediaTypeParser *parser = [[AFMediaTypeParser alloc] initWithMIMEType:item.info.mimeType]; NSURLResponse* response = [[NSURLResponse alloc] initWithURL:item.url - MIMEType:mime + MIMEType:parser.contentType expectedContentLength:[item.data length] - textEncodingName:encoding]; + textEncodingName:parser.contentType]; NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:item.data userInfo:nil storagePolicy:NSURLCacheStorageAllowedInMemoryOnly]; [response release]; + [parser release]; return [cachedResponse autorelease]; } else { //NSLog(@"Cache miss for file: %@", [[AFCache sharedInstance] filenameForURL: url]); diff --git a/test/iOS/AFCache_iOSTests.m b/test/iOS/AFCache_iOSTests.m index 165ea30..09966f0 100644 --- a/test/iOS/AFCache_iOSTests.m +++ b/test/iOS/AFCache_iOSTests.m @@ -7,7 +7,7 @@ // #import "AFCache_iOSTests.h" - +#import "AFMediaTypeParser.h" @implementation AFCache_iOSTests @@ -25,9 +25,23 @@ - (void)tearDown [super tearDown]; } -- (void)testExample +- (void) testMIMEParsing { - STFail(@"Unit tests are not implemented yet in AFCache-iOSTests"); + AFMediaTypeParser* parser = [[AFMediaTypeParser alloc] initWithMIMEType:@"text/html"]; + + STAssertNil([parser textEncoding], @"Text encoding is not nil"); + STAssertEqualObjects(parser.contentType, @"text/html", @"content type is nil"); + [parser release]; + + parser = [[AFMediaTypeParser alloc] initWithMIMEType:@"text/html; charset=utf-8"]; + STAssertEqualObjects(parser.textEncoding, @"utf-8", @"text encoding is not utf-8"); + STAssertEqualObjects(parser.contentType, @"text/html", @"content type is not text/html"); + [parser release]; + + parser = [[AFMediaTypeParser alloc] initWithMIMEType:@"text/html;bla=foo;charset=utf-8;hello=world"]; + STAssertEqualObjects(parser.textEncoding, @"utf-8", @"text encoding is not utf-8"); + STAssertEqualObjects(parser.contentType, @"text/html", @"content type is not text/html"); + [parser release]; } @end From 70325b50f8429b8254cb7ff855864fbb125c2140 Mon Sep 17 00:00:00 2001 From: Martin Jansen Date: Mon, 4 Feb 2013 10:14:29 +0100 Subject: [PATCH 6/6] Parse MIME type from the manifest file. --- src/shared/AFCache+Packaging.m | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/shared/AFCache+Packaging.m b/src/shared/AFCache+Packaging.m index 6816b0a..176a2f0 100755 --- a/src/shared/AFCache+Packaging.m +++ b/src/shared/AFCache+Packaging.m @@ -20,6 +20,7 @@ @implementation AFCache (Packaging) ManifestKeyURL = 0, ManifestKeyLastModified = 1, ManifestKeyExpires = 2, + ManifestKeyMimeType = 3, }; - (AFCacheableItem *)requestPackageArchive: (NSURL *) url delegate: (id) aDelegate { @@ -165,6 +166,7 @@ - (AFPackageInfo*)newPackageInfoByImportingCacheManifestAtPath:(NSString*)manife NSString *URL = nil; NSString *lastModified = nil; NSString *expires = nil; + NSString *mimeType = nil; NSString *key = nil; int line = 0; @@ -218,6 +220,11 @@ - (AFPackageInfo*)newPackageInfoByImportingCacheManifestAtPath:(NSString*)manife info.expireDate = [dateParser gh_parseHTTP:expires]; } + if ([values count] > 3) { + mimeType = [values objectAtIndex:ManifestKeyMimeType]; + info.mimeType = mimeType; + } + key = [self filenameForURLString:URL]; [resourceURLs addObject:URL];