Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

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

Open
wants to merge 6 commits into from

2 participants

@mj
mj commented

While using AFCache I noticed that apparently UIWebView gets confused when the text encoding is part of the MIME type (aka content type) field of a NSURLResponse. This can happen when the original request was responded by a web application with e.g. the perfectly legal header Content-type: text/html; charset=utf-8.

I fixed this by implementing a simple parser for MIME types per RFC 2616 (see section 3.7) and using that one to split the content type and the text encoding.

Please let me know if you like the patch. If further works needs to be done to get this into your branch, I'd be happy to do that.

mj added some commits
@mj mj (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.
196f5e6
@mj mj The name of the manifest is actually manifest.afcache. a7f4cb4
@mj mj Make the unit testing target compile. c0bf897
@mj mj RFC 2616 conforming parser for Internet Media Types. 28420af
@mj mj Committing pending changes regarding the RFC 2616 parser.
These changes should have been part of the previous commit but
somehow got lost somewhere.
3605bc4
@kteman

This should be added. It worked great to eliminate the problem of UIWebview showing HTML code instead of the actual webpage due to mime types not being recognized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 21, 2012
  1. @mj

    (At least) UIWebView instances get confused when the content encoding…

    mj authored
    … 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.
Commits on Feb 25, 2012
  1. @mj
  2. @mj
  3. @mj
  4. @mj

    Committing pending changes regarding the RFC 2616 parser.

    mj authored
    These changes should have been part of the previous commit but
    somehow got lost somewhere.
Commits on Feb 4, 2013
  1. @mj
This page is out of date. Refresh to see the latest.
View
26 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,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 */
@@ -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 */
@@ -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 */,
@@ -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 */,
@@ -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;
};
@@ -511,7 +522,6 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- 0569DE601328AD5B00B3D016 /* AFCache-iOSTests-Info.plist in Resources */,
0569DE621328AD5B00B3D016 /* InfoPlist.strings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -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;
};
@@ -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;
};
@@ -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,
@@ -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,
View
2  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
View
7 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];
View
27 src/shared/AFMediaTypeParser.h
@@ -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
View
94 src/shared/AFMediaTypeParser.m
@@ -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
View
10 src/shared/AFURLCache.m
@@ -23,6 +23,7 @@
#import "AFCacheableItem+Packaging.h"
#import "AFCache+Packaging.h"
#import "DateParser.h"
+#import "AFMediaTypeParser.h"
@implementation AFURLCache
@@ -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]);
View
20 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
Something went wrong with that request. Please try again.