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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user