Add several checks and error reporting while processing the input stream

Control and undefined character, isolated surrogates and carriage returns are handled according to the standard
This commit is contained in:
iska
2014-09-15 23:24:16 +02:00
parent 1fc1f1c71c
commit fb9dcaa608
5 changed files with 172 additions and 6 deletions
+1 -2
View File
@@ -7,8 +7,7 @@
//
#import <Foundation/Foundation.h>
typedef void (^ HTMLStreamReaderErrorCallback)(NSError *error);
#import "HTMLInputStreamReaderErrors.h"
/**
* HTML Input Stream Reader processor conforming to the HTML standard
+89 -4
View File
@@ -8,10 +8,57 @@
#import "HTMLInputStreamReader.h"
#pragma mark Constants & Inlines
static UTF32Char const REPLACEMENT = 0xFFFD;
static UTF32Char const LINE_FEED = 0x000A;
static UTF32Char const CARRIAGE_RETURN = 0x000D;
NS_INLINE BOOL isControlOrUndefinedCharacter(UTF32Char character)
{
return ((character >= 0x0001 && character <= 0x0008) ||
(character >= 0x000E && character <= 0x001F) ||
(character >= 0x007F && character <= 0x009F) ||
(character >= 0xFDD0 && character <= 0xFDEF) ||
character == 0x000B ||
character == 0xFFFE ||
character == 0xFFFF ||
character == 0x1FFFE ||
character == 0x1FFFF ||
character == 0x2FFFE ||
character == 0x2FFFF ||
character == 0x3FFFE ||
character == 0x3FFFF ||
character == 0x4FFFE ||
character == 0x4FFFF ||
character == 0x5FFFE ||
character == 0x5FFFF ||
character == 0x6FFFE ||
character == 0x6FFFF ||
character == 0x7FFFE ||
character == 0x7FFFF ||
character == 0x8FFFE ||
character == 0x8FFFF ||
character == 0x9FFFE ||
character == 0x9FFFF ||
character == 0xAFFFE ||
character == 0xAFFFF ||
character == 0xBFFFE ||
character == 0xBFFFF ||
character == 0xCFFFE ||
character == 0xCFFFF ||
character == 0xDFFFE ||
character == 0xDFFFF ||
character == 0xEFFFE ||
character == 0xEFFFF ||
character == 0xFFFFE ||
character == 0xFFFFF ||
character == 0x10FFFE ||
character == 0x10FFFF);
}
#pragma mark - HTMLInputStreamReader
@interface HTMLInputStreamReader ()
{
NSString *_string;
@@ -19,7 +66,7 @@ static UTF32Char const CARRIAGE_RETURN = 0x000D;
CFStringInlineBuffer _buffer;
NSUInteger _location;
UTF32Char _currentInputCharacter;
NSUInteger _consume;
HTMLStreamReaderErrorCallback _errorCallback;
}
@end
@@ -52,20 +99,58 @@ static UTF32Char const CARRIAGE_RETURN = 0x000D;
{
if (_location >= _string.length) return EOF;
_consume = 1;
UTF32Char nextInputCharacter = CFStringGetCharacterFromInlineBuffer(&_buffer, _location);
if (nextInputCharacter == 0) return EOF;
if (nextInputCharacter == CARRIAGE_RETURN) return LINE_FEED;
if (CFStringIsSurrogateLowCharacter(nextInputCharacter)) return REPLACEMENT;
if (nextInputCharacter == CARRIAGE_RETURN) {
UniChar next = CFStringGetCharacterFromInlineBuffer(&_buffer, _location + 1);
if (next == LINE_FEED) _consume++;
return LINE_FEED;
}
if (CFStringIsSurrogateLowCharacter(nextInputCharacter)) {
[HTMLInputStreamReaderErrors reportParseError:HTMLStreamReaderErrorIsolatedLowSurrogate
atLocation:_location
andCallback:_errorCallback];
return REPLACEMENT;
}
if (CFStringIsSurrogateHighCharacter(nextInputCharacter)) {
UniChar surrogateLow = CFStringGetCharacterFromInlineBuffer(&_buffer, _location + 1);
if (CFStringIsSurrogateLowCharacter(surrogateLow) == NO) return REPLACEMENT;
if (CFStringIsSurrogateLowCharacter(surrogateLow) == NO) {
[HTMLInputStreamReaderErrors reportParseError:HTMLStreamReaderErrorIsolatedHighSurrogate
atLocation:_location
andCallback:_errorCallback];
return REPLACEMENT;
}
nextInputCharacter = CFStringGetLongCharacterForSurrogatePair(nextInputCharacter, surrogateLow);
}
if (isControlOrUndefinedCharacter(nextInputCharacter)) {
[HTMLInputStreamReaderErrors reportParseError:HTMLStreamReaderErrorControlOrUndefined
atLocation:_location
andCallback:_errorCallback];
}
return nextInputCharacter;
}
- (UTF32Char)consumeNextInputCharacter
{
UTF32Char nextInputCharacter = [self nextInputCharacter];
_location += _consume;
_scanner.scanLocation = _location;
_currentInputCharacter = nextInputCharacter;
return nextInputCharacter;
}
- (void)unconsumeCurrentInputCharacter
{
_location -= _consume;
_scanner.scanLocation = _location;
_consume = 0;
}
@end
+28
View File
@@ -0,0 +1,28 @@
//
// HTMLInputStreamReaderErrors.h
// HTMLKit
//
// Created by Iska on 15/09/14.
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
#import <Foundation/Foundation.h>
extern NSString * const HTMLStreamReaderErrorDomain;
typedef NS_ENUM(NSUInteger, HTMLStreamReaderError)
{
HTMLStreamReaderErrorIsolatedLowSurrogate = 100,
HTMLStreamReaderErrorIsolatedHighSurrogate = 200,
HTMLStreamReaderErrorControlOrUndefined = 300
};
typedef void (^ HTMLStreamReaderErrorCallback)(NSError *error);
@interface HTMLInputStreamReaderErrors : NSObject
+ (void)reportParseError:(HTMLStreamReaderError)parseError
atLocation:(NSUInteger)location
andCallback:(HTMLStreamReaderErrorCallback)callback;
@end
+44
View File
@@ -0,0 +1,44 @@
//
// HTMLInputStreamReaderErrors.m
// HTMLKit
//
// Created by Iska on 15/09/14.
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
#import "HTMLInputStreamReaderErrors.h"
NSString * const HTMLStreamReaderErrorDomain = @"HTMLStreamReaderErrorDomain";
static inline NSString * ReasonStringForError(HTMLStreamReaderError error)
{
switch (error) {
case HTMLStreamReaderErrorIsolatedLowSurrogate:
return @"Non-Unicode character found (an isolated low surrogate)";
case HTMLStreamReaderErrorIsolatedHighSurrogate:
return @"Non-Unicode character found (an isolated high surrogate)";
case HTMLStreamReaderErrorControlOrUndefined:
return @"A control/undefined character found";
default:
break;
}
}
@implementation HTMLInputStreamReaderErrors
+ (void)reportParseError:(HTMLStreamReaderError)parseError atLocation:(NSUInteger)location andCallback:(HTMLStreamReaderErrorCallback)callback
{
if (callback == nil) return;
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey : [NSString stringWithFormat:@"HTML Stream parse error at [%ld]", location],
NSLocalizedFailureReasonErrorKey : ReasonStringForError(parseError)
};
NSError *error = [[NSError alloc] initWithDomain:HTMLStreamReaderErrorDomain
code:parseError
userInfo:userInfo];
callback(error);
}
@end