Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Separating content type and text encoding when constructing cached response #13

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions AFCache-iOS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -84,6 +83,11 @@
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 */; };
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 */
Expand Down Expand Up @@ -170,6 +174,8 @@
0569DE911328AD9C00B3D016 /* Constants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Constants.h; sourceTree = "<group>"; };
0569DE921328AD9C00B3D016 /* DateParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DateParser.h; sourceTree = "<group>"; };
0569DE931328AD9C00B3D016 /* DateParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DateParser.m; sourceTree = "<group>"; };
5BEBBEF914F993A7003C5E09 /* AFMediaTypeParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFMediaTypeParser.h; sourceTree = "<group>"; };
5BEBBEFA14F993A7003C5E09 /* AFMediaTypeParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFMediaTypeParser.m; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand All @@ -185,6 +191,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 */,
Expand Down Expand Up @@ -367,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 */,
Expand Down Expand Up @@ -419,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;
};
Expand Down Expand Up @@ -511,7 +522,6 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0569DE601328AD5B00B3D016 /* AFCache-iOSTests-Info.plist in Resources */,
0569DE621328AD5B00B3D016 /* InfoPlist.strings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -569,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;
};
Expand All @@ -592,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;
};
Expand Down Expand Up @@ -694,8 +706,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,
Expand All @@ -714,8 +727,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,
Expand Down
2 changes: 1 addition & 1 deletion README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
7 changes: 7 additions & 0 deletions src/shared/AFCache+Packaging.m
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ @implementation AFCache (Packaging)
ManifestKeyURL = 0,
ManifestKeyLastModified = 1,
ManifestKeyExpires = 2,
ManifestKeyMimeType = 3,
};

- (AFCacheableItem *)requestPackageArchive: (NSURL *) url delegate: (id) aDelegate {
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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];

Expand Down
27 changes: 27 additions & 0 deletions src/shared/AFMediaTypeParser.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// AFMIMEParser.h
// AFCache-iOS
//
// Created by Martin Jansen on 25.02.12.
// Copyright (c) 2012 Artifacts - Fine Software Development. All rights reserved.
//

#import <Foundation/Foundation.h>

/**
* 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;
}

@property (nonatomic, readonly) NSString* textEncoding;
@property (nonatomic, readonly) NSString* contentType;

- (id) initWithMIMEType:(NSString*)theMIMEType;

@end
94 changes: 94 additions & 0 deletions src/shared/AFMediaTypeParser.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//
// AFMIMEParser.m
// AFCache-iOS
//
// Created by Martin Jansen on 25.02.12.
// Copyright (c) 2012 Artifacts - Fine Software Development. All rights reserved.
//

#import "AFMediaTypeParser.h"

#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*)aMIMEType
{
self = [super init];

if (self) {
mimeType = [aMIMEType retain];
_textEncoding = nil;
_contentType = nil;

[self parse];
}

return self;
}

- (void) dealloc
{
[mimeType release];

[super dealloc];
}

#pragma mark -

- (void) parse
{
NSArray *components = [mimeType componentsSeparatedByString:kTokenDelimiter];

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*) trim:(NSString*)aString
{
// http://stackoverflow.com/a/8293671/132475

NSMutableString *mStr = [aString mutableCopy];
CFStringTrimWhitespace((CFMutableStringRef)mStr);

NSString *result = [mStr copy];

[mStr release];
return [result autorelease];
}

@end
10 changes: 8 additions & 2 deletions src/shared/AFURLCache.m
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#import "AFCacheableItem+Packaging.h"
#import "AFCache+Packaging.h"
#import "DateParser.h"
#import "AFMediaTypeParser.h"

@implementation AFURLCache

Expand All @@ -31,11 +32,16 @@ -(NSCachedURLResponse*)cachedResponseForRequest:(NSURLRequest*)request
NSURL* url = request.URL;
AFCacheableItem* item = [[AFCache sharedInstance] cacheableItemFromCacheStore:url];
if (item && item.cacheStatus == kCacheStatusFresh) {

AFMediaTypeParser *parser = [[AFMediaTypeParser alloc] initWithMIMEType:item.info.mimeType];

NSURLResponse* response = [[NSURLResponse alloc] initWithURL:item.url
MIMEType:item.info.mimeType
expectedContentLength:[item.data length] textEncodingName:nil];
MIMEType:parser.contentType
expectedContentLength:[item.data length]
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]);
Expand Down
20 changes: 17 additions & 3 deletions test/iOS/AFCache_iOSTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
//

#import "AFCache_iOSTests.h"

#import "AFMediaTypeParser.h"

@implementation AFCache_iOSTests

Expand All @@ -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