PNXMPPFramework/Core/XMPPParser.m
2016-02-24 16:56:39 +01:00

851 lines
23 KiB
Objective-C

#import "XMPPParser.h"
#import "XMPPLogging.h"
#import <libxml/parser.h>
#import <libxml/parserInternals.h>
#if TARGET_OS_IPHONE
#import "DDXMLPrivate.h"
#endif
#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
#define CHECK_FOR_NULL(value) \
do { \
if (value == NULL) { \
xmpp_xmlAbortDueToMemoryShortage(ctxt); \
return; \
} \
} while(false)
#if !TARGET_OS_IPHONE
static void xmpp_recursiveAddChild(NSXMLElement *parent, xmlNodePtr childNode);
#endif
@implementation XMPPParser
{
#if __has_feature(objc_arc_weak)
__weak id delegate;
#else
__unsafe_unretained id delegate;
#endif
dispatch_queue_t delegateQueue;
dispatch_queue_t parserQueue;
void *xmppParserQueueTag;
BOOL hasReportedRoot;
unsigned depth;
xmlParserCtxt *parserCtxt;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark iPhone
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if TARGET_OS_IPHONE
static void xmpp_onDidReadRoot(XMPPParser *parser, xmlNodePtr root)
{
if (parser->delegateQueue && [parser->delegate respondsToSelector:@selector(xmppParser:didReadRoot:)])
{
// We first copy the root node.
// We do this to allow the delegate to retain and make changes to the reported root
// without affecting the underlying xmpp parser.
// xmlCopyNode(const xmlNodePtr node, int extended)
//
// node:
// the node to copy
// extended:
// if 1 do a recursive copy (properties, namespaces and children when applicable)
// if 2 copy properties and namespaces (when applicable)
xmlNodePtr rootCopy = xmlCopyNode(root, 2);
DDXMLElement *rootCopyWrapper = [DDXMLElement nodeWithElementPrimitive:rootCopy owner:nil];
__strong id theDelegate = parser->delegate;
dispatch_async(parser->delegateQueue, ^{ @autoreleasepool {
[theDelegate xmppParser:parser didReadRoot:rootCopyWrapper];
}});
// Note: DDXMLElement will properly free the rootCopy when it's deallocated.
}
}
static void xmpp_onDidReadElement(XMPPParser *parser, xmlNodePtr child)
{
// Detach the child from the xml tree.
//
// clean: Nullify next, prev, parent and doc pointers of child.
// fixNamespaces: Recurse through subtree, and ensure no namespaces are pointing to xmlNs nodes outside the tree.
// E.G. in a parent node that will no longer be available after the child is detached.
//
// We don't need to fix namespaces since we used xmpp_xmlSearchNs() to ensure we never created any
// namespaces outside the subtree of the child in the first place.
[DDXMLNode detachChild:child andClean:YES andFixNamespaces:NO];
DDXMLElement *childWrapper = [DDXMLElement nodeWithElementPrimitive:child owner:nil];
// Note: We want to detach the child from the root even if the delegate method isn't setup.
// This prevents the doc from growing infinitely large.
if (parser->delegateQueue && [parser->delegate respondsToSelector:@selector(xmppParser:didReadElement:)])
{
__strong id theDelegate = parser->delegate;
dispatch_async(parser->delegateQueue, ^{ @autoreleasepool {
[theDelegate xmppParser:parser didReadElement:childWrapper];
}});
}
// Note: DDXMLElement will properly free the child when it's deallocated.
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Mac
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#else
static void xmpp_setName(NSXMLElement *element, xmlNodePtr node)
{
// Remember: The NSString initWithUTF8String raises an exception if passed NULL
if (node->name == NULL)
{
[element setName:@""];
return;
}
if ((node->ns != NULL) && (node->ns->prefix != NULL))
{
// E.g: <deusty:element xmlns:deusty="deusty.com"/>
NSString *prefix = [[NSString alloc] initWithUTF8String:(const char *)node->ns->prefix];
NSString *name = [[NSString alloc] initWithUTF8String:(const char *)node->name];
NSString *elementName = [[NSString alloc] initWithFormat:@"%@:%@", prefix, name];
[element setName:elementName];
}
else
{
NSString *elementName = [[NSString alloc] initWithUTF8String:(const char *)node->name];
[element setName:elementName];
}
}
static void xmpp_addNamespaces(NSXMLElement *element, xmlNodePtr node)
{
// Remember: The NSString initWithUTF8String raises an exception if passed NULL
xmlNsPtr nsNode = node->nsDef;
while (nsNode != NULL)
{
if (nsNode->href == NULL)
{
// Namespace doesn't have a value!
}
else
{
NSXMLNode *ns = [[NSXMLNode alloc] initWithKind:NSXMLNamespaceKind];
if (nsNode->prefix != NULL)
{
NSString *nsName = [[NSString alloc] initWithUTF8String:(const char *)nsNode->prefix];
[ns setName:nsName];
}
else
{
// Default namespace.
// E.g: xmlns="deusty.com"
[ns setName:@""];
}
NSString *nsValue = [[NSString alloc] initWithUTF8String:(const char *)nsNode->href];
[ns setStringValue:nsValue];
[element addNamespace:ns];
}
nsNode = nsNode->next;
}
}
static void xmpp_addChildren(NSXMLElement *element, xmlNodePtr node)
{
// Remember: The NSString initWithUTF8String raises an exception if passed NULL
xmlNodePtr childNode = node->children;
while (childNode != NULL)
{
if (childNode->type == XML_ELEMENT_NODE)
{
xmpp_recursiveAddChild(element, childNode);
}
else if (childNode->type == XML_TEXT_NODE)
{
if (childNode->content != NULL)
{
NSString *value = [[NSString alloc] initWithUTF8String:(const char *)childNode->content];
[element setStringValue:value];
}
}
childNode = childNode->next;
}
}
static void xmpp_addAttributes(NSXMLElement *element, xmlNodePtr node)
{
// Remember: The NSString initWithUTF8String raises an exception if passed NULL
xmlAttrPtr attrNode = node->properties;
while (attrNode != NULL)
{
if (attrNode->name == NULL)
{
// Attribute doesn't have a name!
}
else if (attrNode->children == NULL)
{
// Attribute doesn't have a value node!
}
else if (attrNode->children->content == NULL)
{
// Attribute doesn't have a value!
}
else
{
NSXMLNode *attr = [[NSXMLNode alloc] initWithKind:NSXMLAttributeKind];
if ((attrNode->ns != NULL) && (attrNode->ns->prefix != NULL))
{
// E.g: <element xmlns:deusty="deusty.com" deusty:attr="value"/>
NSString *prefix = [[NSString alloc] initWithUTF8String:(const char *)attrNode->ns->prefix];
NSString *name = [[NSString alloc] initWithUTF8String:(const char *)attrNode->name];
NSString *attrName = [[NSString alloc] initWithFormat:@"%@:%@", prefix, name];
[attr setName:attrName];
}
else
{
NSString *attrName = [[NSString alloc] initWithUTF8String:(const char *)attrNode->name];
[attr setName:attrName];
}
NSString *attrValue = [[NSString alloc] initWithUTF8String:(const char *)attrNode->children->content];
[attr setStringValue:attrValue];
[element addAttribute:attr];
}
attrNode = attrNode->next;
}
}
/**
* Recursively adds all the child elements to the given parent.
*
* Note: This method is almost the same as xmpp_nsxmlFromLibxml, with one important difference.
* It doen't add any objects to the autorelease pool (xmpp_nsxmlFromLibXml has return value).
**/
static void xmpp_recursiveAddChild(NSXMLElement *parent, xmlNodePtr childNode)
{
// Remember: The NSString initWithUTF8String raises an exception if passed NULL
NSXMLElement *child = [[NSXMLElement alloc] initWithKind:NSXMLElementKind];
xmpp_setName(child, childNode);
xmpp_addNamespaces(child, childNode);
xmpp_addChildren(child, childNode);
xmpp_addAttributes(child, childNode);
[parent addChild:child];
}
/**
* Creates and returns an NSXMLElement from the given node.
* Use this method after finding the root element, or root.child element.
**/
static NSXMLElement* xmpp_nsxmlFromLibxml(xmlNodePtr rootNode)
{
// Remember: The NSString initWithUTF8String raises an exception if passed NULL
NSXMLElement *root = [[NSXMLElement alloc] initWithKind:NSXMLElementKind];
xmpp_setName(root, rootNode);
xmpp_addNamespaces(root, rootNode);
xmpp_addChildren(root, rootNode);
xmpp_addAttributes(root, rootNode);
return root;
}
static void xmpp_onDidReadRoot(XMPPParser *parser, xmlNodePtr root)
{
if (parser->delegateQueue && [parser->delegate respondsToSelector:@selector(xmppParser:didReadRoot:)])
{
NSXMLElement *nsRoot = xmpp_nsxmlFromLibxml(root);
__strong id theDelegate = parser->delegate;
dispatch_async(parser->delegateQueue, ^{ @autoreleasepool {
[theDelegate xmppParser:parser didReadRoot:nsRoot];
}});
}
}
static void xmpp_onDidReadElement(XMPPParser *parser, xmlNodePtr child)
{
if (parser->delegateQueue && [parser->delegate respondsToSelector:@selector(xmppParser:didReadElement:)])
{
NSXMLElement *nsChild = xmpp_nsxmlFromLibxml(child);
__strong id theDelegate = parser->delegate;
dispatch_async(parser->delegateQueue, ^{ @autoreleasepool {
[theDelegate xmppParser:parser didReadElement:nsChild];
}});
}
// Note: We want to detach the child from the root even if the delegate method isn't setup.
// This prevents the doc from growing infinitely large.
// Detach and free child to keep memory footprint small
xmlUnlinkNode(child);
xmlFreeNode(child);
}
#endif
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Common
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* This method is called at the end of the xmlStartElement method.
* This allows us to inspect the parser and xml tree, and determine if we need to invoke any delegate methods.
**/
static void xmpp_postStartElement(xmlParserCtxt *ctxt)
{
XMPPParser *parser = (__bridge XMPPParser *)ctxt->_private;
parser->depth++;
if (!(parser->hasReportedRoot) && (parser->depth == 1))
{
// We've received the full root - report it to the delegate
if (ctxt->myDoc)
{
xmlNodePtr root = xmlDocGetRootElement(ctxt->myDoc);
if (root)
{
xmpp_onDidReadRoot(parser, root);
parser->hasReportedRoot = YES;
}
}
}
}
/**
* This method is called at the end of the xmlEndElement method.
* This allows us to inspect the parser and xml tree, and determine if we need to invoke any delegate methods.
**/
static void xmpp_postEndElement(xmlParserCtxt *ctxt)
{
XMPPParser *parser = (__bridge XMPPParser *)ctxt->_private;
parser->depth--;
if (parser->depth == 1)
{
// End of full xmpp element.
// That is, a child of the root element.
// Extract the child, and pass it to the delegate.
xmlDocPtr doc = ctxt->myDoc;
xmlNodePtr root = xmlDocGetRootElement(doc);
xmlNodePtr child = root->children;
while (child != NULL)
{
if (child->type == XML_ELEMENT_NODE)
{
xmpp_onDidReadElement(parser, child);
// Exit while loop
break;
}
child = child->next;
}
}
else if (parser->depth == 0)
{
// End of the root element
if (parser->delegateQueue && [parser->delegate respondsToSelector:@selector(xmppParserDidEnd:)])
{
__strong id theDelegate = parser->delegate;
dispatch_async(parser->delegateQueue, ^{ @autoreleasepool {
[theDelegate xmppParserDidEnd:parser];
}});
}
}
}
/**
* We're screwed...
**/
static void xmpp_xmlAbortDueToMemoryShortage(xmlParserCtxt *ctxt)
{
XMPPParser *parser = (__bridge XMPPParser *)ctxt->_private;
xmlStopParser(ctxt);
if (parser->delegateQueue && [parser->delegate respondsToSelector:@selector(xmppParser:didFail:)])
{
NSString *errMsg = @"Unable to allocate memory in xmpp parser";
NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg};
NSError *error = [NSError errorWithDomain:@"libxmlErrorDomain" code:1001 userInfo:info];
__strong id theDelegate = parser->delegate;
dispatch_async(parser->delegateQueue, ^{ @autoreleasepool {
[theDelegate xmppParser:parser didFail:error];
}});
}
}
/**
* (Similar to the libxml "xmlSearchNs" method, with one very important difference.)
*
* This method searches for an existing xmlNsPtr in the given node,
* recursing on the parents but stopping before it reaches the root node of the document.
*
* Why do we skip the root node?
* Because all nodes are going to be detached from the root node.
* So it makes no sense to allow them to reference namespaces stored in the root node,
* since the detach algorithm will be forced to copy the namespaces later anyway.
**/
static xmlNsPtr xmpp_xmlSearchNs(xmlDocPtr doc, xmlNodePtr node, const xmlChar *nameSpace)
{
xmlNodePtr rootNode = xmlDocGetRootElement(doc);
xmlNodePtr currentNode = node;
while (currentNode && currentNode != rootNode)
{
xmlNsPtr currentNs = currentNode->nsDef;
while (currentNs)
{
if (currentNs->href != NULL)
{
if ((currentNs->prefix == NULL) && (nameSpace == NULL))
{
return currentNs;
}
if ((currentNs->prefix != NULL) && (nameSpace != NULL))
{
if (xmlStrEqual(currentNs->prefix, nameSpace))
return currentNs;
}
}
currentNs = currentNs->next;
}
currentNode = currentNode->parent;
}
return NULL;
}
/**
* SAX parser C-style callback.
* Invoked when a new node element is started.
**/
static void xmpp_xmlStartElement(void *ctx, const xmlChar *nodeName,
const xmlChar *nodePrefix,
const xmlChar *nodeUri,
int nb_namespaces,
const xmlChar **namespaces,
int nb_attributes,
int nb_defaulted,
const xmlChar **attributes)
{
int i, j;
xmlNsPtr lastAddedNs = NULL;
xmlParserCtxt *ctxt = (xmlParserCtxt *)ctx;
// We store the parent node in the context's node pointer.
// We keep this updated by "pushing" the node in the startElement method,
// and "popping" the node in the endElement method.
xmlNodePtr parent = ctxt->node;
// Create the node
xmlNodePtr newNode = xmlNewDocNode(ctxt->myDoc, NULL, nodeName, NULL);
CHECK_FOR_NULL(newNode);
// Add the node to the tree
if (parent == NULL)
{
// Root node
xmlAddChild((xmlNodePtr)ctxt->myDoc, newNode);
}
else
{
xmlAddChild(parent, newNode);
}
// Process the namespaces
for (i = 0, j = 0; j < nb_namespaces; j++)
{
// Extract namespace prefix and uri
const xmlChar *nsPrefix = namespaces[i++];
const xmlChar *nsUri = namespaces[i++];
// Create the namespace
xmlNsPtr newNs = xmlNewNs(NULL, nsUri, nsPrefix);
CHECK_FOR_NULL(newNs);
// Add namespace to node.
// Each node has a linked list of nodes (in the nsDef variable).
// The linked list is forward only.
// In other words, each ns has a next, but not a prev pointer.
if (newNode->nsDef == NULL)
{
newNode->nsDef = newNs;
lastAddedNs = newNs;
}
else
{
if(lastAddedNs != NULL)
{
lastAddedNs->next = newNs;
}
lastAddedNs = newNs;
}
// Is this the namespace for the node?
if (nodeUri && (nodePrefix == nsPrefix))
{
// Ex 1: node == <stream:stream xmlns:stream="url"> && newNs == stream:url
// Ex 2: node == <starttls xmlns="url"> && newNs == null:url
newNode->ns = newNs;
}
}
// Search for the node's namespace if it wasn't already found
if ((nodeUri) && (newNode->ns == NULL))
{
newNode->ns = xmpp_xmlSearchNs(ctxt->myDoc, newNode, nodePrefix);
if (newNode->ns == NULL)
{
// We use href==NULL in the case of an element creation where the namespace was not defined.
//
// We do NOT use xmlNewNs(newNode, nodeUri, nodePrefix) because that method doesn't properly add
// the namespace to BOTH nsDef and ns.
xmlNsPtr newNs = xmlNewNs(NULL, nodeUri, nodePrefix);
CHECK_FOR_NULL(newNs);
if (newNode->nsDef == NULL)
{
newNode->nsDef = newNs;
}
else if(lastAddedNs != NULL)
{
lastAddedNs->next = newNs;
}
newNode->ns = newNs;
}
}
// Process all the attributes
for (i = 0, j = 0; j < nb_attributes; j++)
{
const xmlChar *attrName = attributes[i++];
const xmlChar *attrPrefix = attributes[i++];
const xmlChar *attrUri = attributes[i++];
const xmlChar *valueBegin = attributes[i++];
const xmlChar *valueEnd = attributes[i++];
// The attribute value might contain character references which need to be decoded.
//
// "Franks &#38; Beans" -> "Franks & Beans"
xmlChar *value = xmlStringLenDecodeEntities(ctxt, // the parser context
valueBegin, // the input string
(int)(valueEnd - valueBegin), // the input string length
(XML_SUBSTITUTE_REF), // what to substitue
0, 0, 0); // end markers, 0 if none
CHECK_FOR_NULL(value);
if ((attrPrefix == NULL) && (attrUri == NULL))
{
// Normal attribute - no associated namespace
xmlAttrPtr newAttr = xmlNewProp(newNode, attrName, value);
CHECK_FOR_NULL(newAttr);
}
else
{
// Find the namespace for the attribute
xmlNsPtr attrNs = xmpp_xmlSearchNs(ctxt->myDoc, newNode, attrPrefix);
if (attrNs != NULL)
{
xmlAttrPtr newAttr = xmlNewNsProp(newNode, attrNs, attrName, value);
CHECK_FOR_NULL(newAttr);
}
else
{
attrNs = xmlNewNs(NULL, NULL, nodePrefix);
CHECK_FOR_NULL(attrNs);
xmlAttrPtr newAttr = xmlNewNsProp(newNode, attrNs, attrName, value);
CHECK_FOR_NULL(newAttr);
}
}
xmlFree(value);
}
// Update our parent node pointer
ctxt->node = newNode;
// Invoke delegate methods if needed
xmpp_postStartElement(ctxt);
}
/**
* SAX parser C-style callback.
* Invoked when characters are found within a node.
**/
static void xmpp_xmlCharacters(void *ctx, const xmlChar *ch, int len)
{
xmlParserCtxt *ctxt = (xmlParserCtxt *)ctx;
if (ctxt->node != NULL)
{
xmlNodePtr textNode = xmlNewTextLen(ch, len);
// xmlAddChild(xmlNodePtr parent, xmlNodePtr cur)
//
// Add a new node to @parent, at the end of the child list
// merging adjacent TEXT nodes (in which case @cur is freed).
xmlAddChild(ctxt->node, textNode);
}
}
/**
* SAX parser C-style callback.
* Invoked when a new node element is ended.
**/
static void xmpp_xmlEndElement(void *ctx, const xmlChar *localname,
const xmlChar *prefix,
const xmlChar *URI)
{
xmlParserCtxt *ctxt = (xmlParserCtxt *)ctx;
// Update our parent node pointer
if (ctxt->node != NULL)
ctxt->node = ctxt->node->parent;
// Invoke delegate methods if needed
xmpp_postEndElement(ctxt);
}
- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq
{
return [self initWithDelegate:aDelegate delegateQueue:dq parserQueue:NULL];
}
- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq parserQueue:(dispatch_queue_t)pq
{
if ((self = [super init]))
{
delegate = aDelegate;
delegateQueue = dq;
#if !OS_OBJECT_USE_OBJC
if (delegateQueue)
dispatch_retain(delegateQueue);
#endif
if (pq) {
parserQueue = pq;
#if !OS_OBJECT_USE_OBJC
dispatch_retain(parserQueue);
#endif
}
else {
parserQueue = dispatch_queue_create("xmpp.parser", NULL);
}
xmppParserQueueTag = &xmppParserQueueTag;
dispatch_queue_set_specific(parserQueue, xmppParserQueueTag, xmppParserQueueTag, NULL);
hasReportedRoot = NO;
depth = 0;
// Create SAX handler
xmlSAXHandler saxHandler;
memset(&saxHandler, 0, sizeof(xmlSAXHandler));
saxHandler.initialized = XML_SAX2_MAGIC;
saxHandler.startElementNs = xmpp_xmlStartElement;
saxHandler.characters = xmpp_xmlCharacters;
saxHandler.endElementNs = xmpp_xmlEndElement;
// Create the push parser context
parserCtxt = xmlCreatePushParserCtxt(&saxHandler, NULL, NULL, 0, NULL);
// Note: This method copies the saxHandler, so we don't have to keep it around.
// Create the document to hold the parsed elements
parserCtxt->myDoc = xmlNewDoc(parserCtxt->version);
// Store reference to ourself
parserCtxt->_private = (__bridge void *)(self);
// Note: The parserCtxt also has a userData variable, but it is used by the DOM building functions.
// If we put a value there, it actually causes a crash!
// We need to be sure to use the _private variable which libxml won't touch.
}
return self;
}
- (void)dealloc
{
if (parserCtxt)
{
// The xmlFreeParserCtxt method will not free the created document in parserCtxt->myDoc.
if (parserCtxt->myDoc)
{
// Free the created xmlDoc
xmlFreeDoc(parserCtxt->myDoc);
}
xmlFreeParserCtxt(parserCtxt);
}
#if !OS_OBJECT_USE_OBJC
if (delegateQueue)
dispatch_release(delegateQueue);
if (parserQueue)
dispatch_release(parserQueue);
#endif
}
- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue
{
#if !OS_OBJECT_USE_OBJC
if (newDelegateQueue)
dispatch_retain(newDelegateQueue);
#endif
dispatch_block_t block = ^{
delegate = newDelegate;
#if !OS_OBJECT_USE_OBJC
if (delegateQueue)
dispatch_release(delegateQueue);
#endif
delegateQueue = newDelegateQueue;
};
if (dispatch_get_specific(xmppParserQueueTag))
block();
else
dispatch_async(parserQueue, block);
}
- (void)parseData:(NSData *)data
{
dispatch_block_t block = ^{ @autoreleasepool {
int result = xmlParseChunk(parserCtxt, (const char *)[data bytes], (int)[data length], 0);
if (result == 0)
{
if (delegateQueue && [delegate respondsToSelector:@selector(xmppParserDidParseData:)])
{
__strong id theDelegate = delegate;
dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate xmppParserDidParseData:self];
}});
}
}
else
{
if (delegateQueue && [delegate respondsToSelector:@selector(xmppParser:didFail:)])
{
NSError *error;
xmlError *xmlErr = xmlCtxtGetLastError(parserCtxt);
if (xmlErr->message)
{
NSString *errMsg = [NSString stringWithFormat:@"%s", xmlErr->message];
NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg};
error = [NSError errorWithDomain:@"libxmlErrorDomain" code:xmlErr->code userInfo:info];
}
else
{
error = [NSError errorWithDomain:@"libxmlErrorDomain" code:xmlErr->code userInfo:nil];
}
__strong id theDelegate = delegate;
dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate xmppParser:self didFail:error];
}});
}
}
}};
if (dispatch_get_specific(xmppParserQueueTag))
block();
else
dispatch_async(parserQueue, block);
}
@end