Skip to content

Commit 30b17ed

Browse files
authored
Merge pull request #700 from KingOfBrian/bugfix/SR-2301
2 parents e1f0119 + 377daa9 commit 30b17ed

File tree

4 files changed

+214
-112
lines changed

4 files changed

+214
-112
lines changed

CoreFoundation/Base.subproj/ForSwiftFoundationOnly.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ struct _NSXMLParserBridge {
171171
void (*startElementNs)(_CFXMLInterface ctx,
172172
const unsigned char *localname,
173173
const unsigned char *_Nullable prefix,
174-
const unsigned char *URI,
174+
const unsigned char *_Nullable URI,
175175
int nb_namespaces,
176176
const unsigned char *_Nullable *_Nonnull namespaces,
177177
int nb_attributes,
@@ -180,7 +180,7 @@ struct _NSXMLParserBridge {
180180
void (*endElementNs)(_CFXMLInterface ctx,
181181
const unsigned char *localname,
182182
const unsigned char *_Nullable prefix,
183-
const unsigned char *URI);
183+
const unsigned char *_Nullable URI);
184184
void (*characters)(_CFXMLInterface ctx,
185185
const unsigned char *ch,
186186
int len);

Foundation/NSXMLParser.swift

Lines changed: 87 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ extension XMLParser {
4242
}
4343
}
4444

45-
private func UTF8STRING(_ bytes: UnsafePointer<UInt8>) -> String? {
45+
private func UTF8STRING(_ bytes: UnsafePointer<UInt8>?) -> String? {
46+
guard let bytes = bytes else { return nil }
4647
// strlen operates on the wrong type, char*. We can't rebind the memory to a different type without knowing it's length,
4748
// but since we know strlen is in libc, its safe to directly bitcast the pointer without worrying about multiple accesses
4849
// of different types visible to the compiler.
@@ -243,16 +244,10 @@ internal func _colonSeparatedStringFromPrefixAndSuffix(_ prefix: UnsafePointer<U
243244
return "\(prefixString!):\(suffixString!)"
244245
}
245246

246-
internal func _NSXMLParserStartElementNs(_ ctx: _CFXMLInterface, localname: UnsafePointer<UInt8>, prefix: UnsafePointer<UInt8>?, URI: UnsafePointer<UInt8>, nb_namespaces: Int32, namespaces: UnsafeMutablePointer<UnsafePointer<UInt8>?>, nb_attributes: Int32, nb_defaulted: Int32, attributes: UnsafeMutablePointer<UnsafePointer<UInt8>?>) -> Void {
247+
internal func _NSXMLParserStartElementNs(_ ctx: _CFXMLInterface, localname: UnsafePointer<UInt8>, prefix: UnsafePointer<UInt8>?, URI: UnsafePointer<UInt8>?, nb_namespaces: Int32, namespaces: UnsafeMutablePointer<UnsafePointer<UInt8>?>, nb_attributes: Int32, nb_defaulted: Int32, attributes: UnsafeMutablePointer<UnsafePointer<UInt8>?>) -> Void {
247248
let parser = ctx.parser
248-
let reportQNameURI = parser.shouldProcessNamespaces
249249
let reportNamespaces = parser.shouldReportNamespacePrefixes
250-
// Since strlen is in libc, it's safe to bitcast the UInt8 pointer argument to an Int8 (char *) pointer.
251-
let prefixLen = prefix != nil ? UInt(strlen(unsafeBitCast(prefix!, to: UnsafePointer<Int8>.self))) : 0
252-
let localnameString = (prefixLen == 0 || reportQNameURI) ? UTF8STRING(localname) : nil
253-
let qualifiedNameString = prefixLen != 0 ? _colonSeparatedStringFromPrefixAndSuffix(prefix!, UInt(prefixLen), localname, UInt(strlen(unsafeBitCast(localname, to: UnsafePointer<Int8>.self)))) : localnameString
254-
let namespaceURIString = reportQNameURI ? UTF8STRING(URI) : nil
255-
250+
256251
var nsDict = [String:String]()
257252
var attrDict = [String:String]()
258253
if nb_attributes + nb_namespaces > 0 {
@@ -274,7 +269,7 @@ internal func _NSXMLParserStartElementNs(_ ctx: _CFXMLInterface, localname: Unsa
274269
nsDict[k] = v
275270
}
276271
}
277-
if !reportQNameURI {
272+
if !parser.shouldProcessNamespaces {
278273
if let k = asAttrNamespaceNameString,
279274
let v = namespaceValueString {
280275
attrDict[k] = v
@@ -323,37 +318,47 @@ internal func _NSXMLParserStartElementNs(_ ctx: _CFXMLInterface, localname: Unsa
323318
}
324319

325320
}
326-
327-
if let delegate = parser.delegate {
328-
if reportQNameURI {
329-
delegate.parser(parser, didStartElement: localnameString!, namespaceURI: (namespaceURIString != nil ? namespaceURIString : ""), qualifiedName: (qualifiedNameString != nil ? qualifiedNameString : ""), attributes: attrDict)
330-
} else {
331-
delegate.parser(parser, didStartElement: qualifiedNameString!, namespaceURI: nil, qualifiedName: nil, attributes: attrDict)
321+
322+
var elementName: String = UTF8STRING(localname)!
323+
var namespaceURI: String? = nil
324+
var qualifiedName: String? = nil
325+
if parser.shouldProcessNamespaces {
326+
namespaceURI = UTF8STRING(URI) ?? ""
327+
qualifiedName = elementName
328+
if let prefix = UTF8STRING(prefix) {
329+
qualifiedName = elementName + ":" + prefix
332330
}
333331
}
332+
else if let prefix = UTF8STRING(prefix) {
333+
elementName = elementName + ":" + prefix
334+
}
335+
336+
parser.delegate?.parser(parser, didStartElement: elementName, namespaceURI: namespaceURI, qualifiedName: qualifiedName, attributes: attrDict)
334337
}
335338

336-
internal func _NSXMLParserEndElementNs(_ ctx: _CFXMLInterface , localname: UnsafePointer<UInt8>, prefix: UnsafePointer<UInt8>?, URI: UnsafePointer<UInt8>) -> Void {
339+
internal func _NSXMLParserEndElementNs(_ ctx: _CFXMLInterface , localname: UnsafePointer<UInt8>, prefix: UnsafePointer<UInt8>?, URI: UnsafePointer<UInt8>?) -> Void {
337340
let parser = ctx.parser
338-
let reportQNameURI = parser.shouldProcessNamespaces
339-
let prefixLen = prefix != nil ? strlen(unsafeBitCast(prefix!, to: UnsafePointer<Int8>.self)) : 0
340-
let localnameString = (prefixLen == 0 || reportQNameURI) ? UTF8STRING(localname) : nil
341-
let nilStr: String? = nil
342-
let qualifiedNameString = (prefixLen != 0) ? _colonSeparatedStringFromPrefixAndSuffix(prefix!, UInt(prefixLen), localname, UInt(strlen(unsafeBitCast(localname, to: UnsafePointer<Int8>.self)))) : nilStr
343-
let namespaceURIString = reportQNameURI ? UTF8STRING(URI) : nilStr
344-
345-
346-
if let delegate = parser.delegate {
347-
if reportQNameURI {
348-
// When reporting namespace info, the delegate parameters are not passed in nil
349-
delegate.parser(parser, didEndElement: localnameString!, namespaceURI: namespaceURIString == nil ? "" : namespaceURIString, qualifiedName: qualifiedNameString == nil ? "" : qualifiedNameString)
350-
} else {
351-
delegate.parser(parser, didEndElement: qualifiedNameString!, namespaceURI: nil, qualifiedName: nil)
341+
342+
var elementName: String = UTF8STRING(localname)!
343+
var namespaceURI: String? = nil
344+
var qualifiedName: String? = nil
345+
if parser.shouldProcessNamespaces {
346+
namespaceURI = UTF8STRING(URI) ?? ""
347+
qualifiedName = elementName
348+
if let prefix = UTF8STRING(prefix) {
349+
qualifiedName = elementName + ":" + prefix
352350
}
353351
}
354-
352+
else if let prefix = UTF8STRING(prefix) {
353+
elementName = elementName + ":" + prefix
354+
}
355+
356+
parser.delegate?.parser(parser, didEndElement: elementName, namespaceURI: namespaceURI, qualifiedName: qualifiedName)
357+
355358
// Pop the last namespaces that were pushed (safe since XML is balanced)
356-
parser._popNamespaces()
359+
if parser.shouldReportNamespacePrefixes {
360+
parser._popNamespaces()
361+
}
357362
}
358363

359364
internal func _NSXMLParserCharacters(_ ctx: _CFXMLInterface, ch: UnsafePointer<UInt8>, len: Int32) -> Void {
@@ -410,8 +415,10 @@ open class XMLParser : NSObject {
410415
private var _handler: _CFXMLInterfaceSAXHandler
411416
internal var _stream: InputStream?
412417
internal var _data: Data?
418+
413419
internal var _chunkSize = Int(4096 * 32) // a suitably large number for a decent chunk size
414-
internal var _haveDetectedEncoding = false
420+
// This chunk of data stores the head of the stream. We know we have enough information for encoding
421+
// when there are atleast 4 bytes in here.
415422
internal var _bomChunk: Data?
416423
fileprivate var _parserContext: _CFXMLInterfaceParserContext?
417424
internal var _delegateAborted = false
@@ -500,72 +507,52 @@ open class XMLParser : NSObject {
500507

501508
internal func parseData(_ data: Data) -> Bool {
502509
_CFXMLInterfaceSetStructuredErrorFunc(interface, _structuredErrorFunc)
503-
var result = true
504-
/* The vast majority of this method just deals with ensuring we do a single parse
505-
on the first 4 received bytes before continuing on to the actual incremental section */
506-
if _haveDetectedEncoding {
507-
var totalLength = data.count
508-
if let chunk = _bomChunk {
509-
totalLength += chunk.count
510-
}
511-
if (totalLength < 4) {
512-
if let chunk = _bomChunk {
513-
var newData = Data()
514-
newData.append(chunk)
515-
newData.append(data)
516-
_bomChunk = newData
517-
} else {
518-
_bomChunk = data
519-
}
520-
} else {
521-
var allExistingData: Data
522-
if let chunk = _bomChunk {
523-
var newData = Data()
524-
newData.append(chunk)
525-
newData.append(data)
526-
allExistingData = newData
527-
} else {
528-
allExistingData = data
529-
}
530-
531-
var handler: _CFXMLInterfaceSAXHandler? = nil
532-
if delegate != nil {
533-
handler = _handler
534-
}
535-
536-
_parserContext = allExistingData.withUnsafeBytes { (bytes: UnsafePointer<Int8>) -> _CFXMLInterfaceParserContext in
537-
return _CFXMLInterfaceCreatePushParserCtxt(handler, interface, bytes, 4, nil)
538-
}
539510

540-
var options = _kCFXMLInterfaceRecover | _kCFXMLInterfaceNoEnt // substitute entities, recover on errors
541-
if shouldResolveExternalEntities {
542-
options |= _kCFXMLInterfaceDTDLoad
543-
}
544-
545-
if handler == nil {
546-
options |= (_kCFXMLInterfaceNoError | _kCFXMLInterfaceNoWarning)
547-
}
548-
549-
_CFXMLInterfaceCtxtUseOptions(_parserContext, options)
550-
_haveDetectedEncoding = true
551-
_bomChunk = nil
552-
553-
if (totalLength > 4) {
554-
let remainingData = allExistingData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Data in
555-
let ptr = bytes.advanced(by: 4)
556-
return Data(bytesNoCopy: ptr, count: totalLength - 4, deallocator: .none)
557-
}
558-
559-
let _ = parseData(remainingData)
511+
let handler: _CFXMLInterfaceSAXHandler? = (delegate != nil ? _handler : nil)
512+
let unparsedData: Data
513+
// If the parser context is nil, we have not received enough bytes to create the push parser
514+
if _parserContext == nil {
515+
// Look at the bomChunk and this data
516+
let bomChunk: Data = {
517+
guard var bomChunk = _bomChunk else {
518+
return data
560519
}
520+
bomChunk.append(data)
521+
return bomChunk
522+
}()
523+
// If we have not received 4 bytes, save the bomChunk for next pass
524+
if bomChunk.count < 4 {
525+
_bomChunk = bomChunk
526+
return false
561527
}
562-
} else {
563-
let parseResult = data.withUnsafeBytes { (bytes: UnsafePointer<Int8>) -> Int32 in
564-
return _CFXMLInterfaceParseChunk(_parserContext, bytes, Int32(data.count), 0)
528+
// Prepare options (substitute entities, recover on errors)
529+
var options = _kCFXMLInterfaceRecover | _kCFXMLInterfaceNoEnt
530+
if shouldResolveExternalEntities {
531+
options |= _kCFXMLInterfaceDTDLoad
532+
}
533+
if handler == nil {
534+
options |= (_kCFXMLInterfaceNoError | _kCFXMLInterfaceNoWarning)
535+
}
536+
537+
// Create the push context with the first 4 bytes
538+
bomChunk.withUnsafeBytes { bytes in
539+
_parserContext = _CFXMLInterfaceCreatePushParserCtxt(handler, interface, bytes, 4, nil)
565540
}
566-
567-
result = _handleParseResult(parseResult)
541+
_CFXMLInterfaceCtxtUseOptions(_parserContext, options)
542+
// Prepare the remaining data for parsing
543+
let dataRange = bomChunk.indices
544+
let unparsed = Range(uncheckedBounds: (dataRange.startIndex.advanced(by: 4), dataRange.endIndex))
545+
unparsedData = bomChunk.subdata(in: unparsed)
546+
}
547+
else {
548+
unparsedData = data
568549
}
550+
551+
let parseResult = unparsedData.withUnsafeBytes { (bytes: UnsafePointer<Int8>) -> Int32 in
552+
return _CFXMLInterfaceParseChunk(_parserContext, bytes, Int32(unparsedData.count), 0)
553+
}
554+
555+
let result = _handleParseResult(parseResult)
569556
_CFXMLInterfaceSetStructuredErrorFunc(interface, nil)
570557
return result
571558
}
@@ -745,13 +732,13 @@ public protocol XMLParserDelegate: class {
745732
func parser(_ parser: XMLParser, resolveExternalEntityName name: String, systemID: String?) -> Data?
746733
// this gives the delegate an opportunity to resolve an external entity itself and reply with the resulting data.
747734

748-
func parser(_ parser: XMLParser, parseErrorOccurred parseError: NSError)
735+
func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error)
749736
// ...and this reports a fatal error to the delegate. The parser will stop parsing.
750737

751-
func parser(_ parser: XMLParser, validationErrorOccurred validationError: NSError)
738+
func parser(_ parser: XMLParser, validationErrorOccurred validationError: Error)
752739
}
753740

754-
extension XMLParserDelegate {
741+
public extension XMLParserDelegate {
755742

756743
func parserDidStartDocument(_ parser: XMLParser) { }
757744
func parserDidEndDocument(_ parser: XMLParser) { }
@@ -788,9 +775,9 @@ extension XMLParserDelegate {
788775

789776
func parser(_ parser: XMLParser, resolveExternalEntityName name: String, systemID: String?) -> Data? { return nil }
790777

791-
func parser(_ parser: XMLParser, parseErrorOccurred parseError: NSError) { }
778+
func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) { }
792779

793-
func parser(_ parser: XMLParser, validationErrorOccurred validationError: NSError) { }
780+
func parser(_ parser: XMLParser, validationErrorOccurred validationError: Error) { }
794781
}
795782

796783
extension XMLParser {

TestFoundation/TestNSXMLDocument.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import Foundation
1414
import XCTest
1515
#else
16-
@testable import SwiftFoundation
16+
import SwiftFoundation
1717
import SwiftXCTest
1818
#endif
1919

0 commit comments

Comments
 (0)