/**
 * XML Security Library (http://www.aleksey.com/xmlsec).
 *
 * Relationship transform
 *
 * This is free software; see Copyright file in the source
 * distribution for preciese wording.
 *
 * Copyright (C) 2002-2016 Aleksey Sanin <aleksey@aleksey.com>. All Rights Reserved.
 */
#include "globals.h"

#include <stdlib.h>
#include <string.h>

#include <libxml/tree.h>
#include <libxml/xpointer.h>
#include <libxml/c14n.h>

#include <xmlsec/xmlsec.h>
#include <xmlsec/xmltree.h>
#include <xmlsec/keys.h>
#include <xmlsec/list.h>
#include <xmlsec/transforms.h>
#include <xmlsec/errors.h>


/******************************************************************************
 *
 * Relationship transform
 *
 * http://standards.iso.org/ittf/PubliclyAvailableStandards/c061796_ISO_IEC_29500-2_2012.zip
 *
 * 13.2.4.24 Relationships Transform Algorithm
 *
 * The relationships transform takes the XML document from the Relationships part and converts
 * it to another XML document.
 *
 * The package implementer might create relationships XML that contains content from several namespaces,
 * along with versioning instructions as defined in Part 3, “Markup Compatibility and Extensibility”. [O6.11]
 *
 * The relationships transform algorithm is as follows:
 *
 * Step 1: Process versioning instructions
 *   1. The package implementer shall process the versioning instructions, considering that the only
 *   known namespace is the Relationships namespace.
 *   2. The package implementer shall remove all ignorable content, ignoring preservation attributes.
 *   3. The package implementer shall remove all versioning instructions.
 *
 * Step 2: Sort and filter relationships
 *   1. The package implementer shall remove all namespace declarations except the Relationships
 *   namespace declaration.
 *   2. The package implementer shall remove the Relationships namespace prefix, if it is present.
 *   3. The package implementer shall sort relationship elements by Id value in lexicographical
 *   order, considering Id values as case-sensitive Unicode strings.
 *   4. The package implementer shall remove all Relationship elements that do not have either an Id
 *   value that matches any SourceId value or a Type value that matches any SourceType value, among
 *   the SourceId and SourceType values specified in the transform definition. Producers and consumers
 *   shall compare values as case-sensitive Unicode strings. [M6.27] The resulting XML document holds
 *   all Relationship elements that either have an Id value that matches a SourceId value or a Type value
 *   that matches a SourceType value specified in the transform definition.
 *
 * Step 3: Prepare for canonicalization
 *   1. The package implementer shall remove all characters between the Relationships start tag and
 *   the first Relationship start tag.
 *   2. The package implementer shall remove any contents of the Relationship element.
 *   3. The package implementer shall remove all characters between the last Relationship end tag and
 *   the Relationships end tag.
 *   4. If there are no Relationship elements, the package implementer shall remove all characters
 *   between the Relationships start tag and the Relationships end tag.
 *   5. The package implementer shall remove comments from the Relationships XML content.
 *   6. The package implementer shall add a TargetMode attribute with its default value, if this
 *   optional attribute is missing from the Relationship element.
 *   7. The package implementer can generate Relationship elements as start-tag/end-tag pairs with
 *   empty content, or as empty elements. A canonicalization transform, applied immediately after the
 *   Relationships Transform, converts all XML elements into start-tag/end-tag pairs.
 *
 *
 *   IMPLEMENTATION NOTES (https://github.com/lsh123/xmlsec/pull/24):
 *
 *   * We don't simply manipulate the XML tree, but do an XML tree -> output bytes transformation,
 *     so e.g. because we never write characters inside XML elements, we implicitly remove all character
 *     contents, as required by step 3, point 1. It also simplifies the task of the situation that
 *     realistically the input of the transformation is always a document that conforms to the OOXML
 *     relationships XML schema, so in practice it'll never happen that the input document has e.g.
 *     characters, as the schema requires that the document has only XML elements and attributes,
 *     but no characters.
 *
 *   * Step 2, point 4 talks about a SourceType value, but given that neither Microsoft Office, nor LibreOffice
 *     writes that theoretical attribute, the implementation doesn't handle it. If there is a real-world situation
 *     when there will be such an input, then it'll be easy to add support for that. But I didn't want to clutter
 *     the current implementation with details that doesn't seem to be used in practice
 *
 *****************************************************************************/
typedef struct _xmlSecRelationshipCtx           xmlSecRelationshipCtx,
                                                *xmlSecRelationshipCtxPtr;
struct _xmlSecRelationshipCtx {
    xmlSecPtrListPtr                    sourceIdList;
};
#define xmlSecRelationshipSize        \
    (sizeof(xmlSecTransform) + sizeof(xmlSecRelationshipCtx))
#define xmlSecRelationshipGetCtx(transform)        \
    ((xmlSecRelationshipCtxPtr)(((xmlSecByte*)(transform)) + sizeof(xmlSecTransform)))

static int              xmlSecRelationshipInitialize      (xmlSecTransformPtr transform);
static void             xmlSecRelationshipFinalize        (xmlSecTransformPtr transform);
static int              xmlSecTransformRelationshipPopBin (xmlSecTransformPtr transform,
                                                           xmlSecByte* data,
                                                           xmlSecSize maxDataSize,
                                                           xmlSecSize* dataSize,
                                                           xmlSecTransformCtxPtr transformCtx);
static int              xmlSecTransformRelationshipPushXml(xmlSecTransformPtr transform,
                                                           xmlSecNodeSetPtr nodes,
                                                           xmlSecTransformCtxPtr transformCtx);
static int              xmlSecRelationshipReadNode        (xmlSecTransformPtr transform,
                                                           xmlNodePtr node,
                                                           xmlSecTransformCtxPtr transformCtx);

static int              xmlSecTransformRelationshipProcessElementNode(xmlSecTransformPtr transform,
                                                            xmlOutputBufferPtr buf,
                                                            xmlNodePtr cur);


static xmlSecTransformKlass xmlSecRelationshipKlass = {
    /* klass/object sizes */
    sizeof(xmlSecTransformKlass),               /* xmlSecSize klassSize */
    xmlSecRelationshipSize,                     /* xmlSecSize objSize */

    xmlSecNameRelationship,                     /* const xmlChar* name; */
    xmlSecHrefRelationship,                     /* const xmlChar* href; */
    xmlSecTransformUsageDSigTransform,          /* xmlSecTransformUsage usage; */

    xmlSecRelationshipInitialize,               /* xmlSecTransformInitializeMethod initialize; */
    xmlSecRelationshipFinalize,                 /* xmlSecTransformFinalizeMethod finalize; */
    xmlSecRelationshipReadNode,                 /* xmlSecTransformNodeReadMethod readNode; */
    NULL,                                       /* xmlSecTransformNodeWriteMethod writeNode; */
    NULL,                                       /* xmlSecTransformSetKeyReqMethod setKeyReq; */
    NULL,                                       /* xmlSecTransformSetKeyMethod setKey; */
    NULL,                                       /* xmlSecTransformValidateMethod validate; */
    xmlSecTransformDefaultGetDataType,          /* xmlSecTransformGetDataTypeMethod getDataType; */
    NULL,                                       /* xmlSecTransformPushBinMethod pushBin; */
    xmlSecTransformRelationshipPopBin,          /* xmlSecTransformPopBinMethod popBin; */
    xmlSecTransformRelationshipPushXml,         /* xmlSecTransformPushXmlMethod pushXml; */
    NULL,                                       /* xmlSecTransformPopXmlMethod popXml; */
    NULL,                                       /* xmlSecTransformExecuteMethod execute; */

    NULL,                                       /* void* reserved0; */
    NULL,                                       /* void* reserved1; */
};

xmlSecTransformId
xmlSecTransformRelationshipGetKlass(void) {
    return(&xmlSecRelationshipKlass);
}

static int
xmlSecRelationshipInitialize(xmlSecTransformPtr transform) {
    xmlSecRelationshipCtxPtr ctx;

    xmlSecAssert2(xmlSecTransformCheckId(transform, xmlSecTransformRelationshipId), -1);
    xmlSecAssert2(xmlSecTransformCheckSize(transform, xmlSecRelationshipSize), -1);

    ctx = xmlSecRelationshipGetCtx(transform);
    xmlSecAssert2(ctx != NULL, -1);

    /* initialize context */
    memset(ctx, 0, sizeof(xmlSecRelationshipCtx));

    ctx->sourceIdList = xmlSecPtrListCreate(xmlSecStringListId);
    if(ctx->sourceIdList == NULL) {
        xmlSecError(XMLSEC_ERRORS_HERE,
                    xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
                    "xmlSecPtrListCreate",
                     XMLSEC_ERRORS_R_XMLSEC_FAILED,
                     XMLSEC_ERRORS_NO_MESSAGE);
        return(-1);
    }
    return(0);
}

static void
xmlSecRelationshipFinalize(xmlSecTransformPtr transform) {
    xmlSecRelationshipCtxPtr ctx;

    xmlSecAssert(xmlSecTransformCheckId(transform, xmlSecTransformRelationshipId));
    xmlSecAssert(xmlSecTransformCheckSize(transform, xmlSecRelationshipSize));

    ctx = xmlSecRelationshipGetCtx(transform);
    xmlSecAssert(ctx != NULL);

    if(ctx->sourceIdList != NULL) {
       xmlSecPtrListDestroy(ctx->sourceIdList);
    }

    memset(ctx, 0, sizeof(xmlSecRelationshipCtx));
}

static int
xmlSecRelationshipReadNode(xmlSecTransformPtr transform, xmlNodePtr node, xmlSecTransformCtxPtr transformCtx) {
    xmlSecRelationshipCtxPtr ctx;
    xmlNodePtr cur;
    int ret;

    xmlSecAssert2(xmlSecTransformCheckId(transform, xmlSecTransformRelationshipId), -1);
    xmlSecAssert2(xmlSecTransformCheckSize(transform, xmlSecRelationshipSize), -1);
    xmlSecAssert2(node != NULL, -1);
    xmlSecAssert2(transformCtx != NULL, -1);
    ctx = xmlSecRelationshipGetCtx(transform);
    xmlSecAssert2(ctx != NULL, -1);

    cur = node->children;
    while(cur != NULL) {
        if(xmlSecCheckNodeName(cur, xmlSecNodeRelationshipReference, xmlSecRelationshipReferenceNs)) {
            xmlChar* sourceId;
            xmlChar* tmp;

            sourceId = xmlGetProp(cur, xmlSecRelationshipAttrSourceId);
            if(sourceId == NULL) {
                xmlSecError(XMLSEC_ERRORS_HERE,
                            "xmlGetProp",
                            xmlSecErrorsSafeString(xmlSecRelationshipAttrSourceId),
                            XMLSEC_ERRORS_R_INVALID_NODE_ATTRIBUTE,
                            "node=%s",
                            xmlSecErrorsSafeString(xmlSecNodeGetName(node)));
                return(-1);
            }

            tmp = xmlStrdup(sourceId);
            if(tmp == NULL) {
                xmlSecError(XMLSEC_ERRORS_HERE,
                            xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
                            "xmlStrdup",
                            XMLSEC_ERRORS_R_STRDUP_FAILED,
                            "len=%d", xmlStrlen(sourceId));
                return(-1);
            }

            ret = xmlSecPtrListAdd(ctx->sourceIdList, tmp);
            if(ret < 0) {
                xmlSecError(XMLSEC_ERRORS_HERE,
                            xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
                            "xmlSecPtrListAdd",
                            XMLSEC_ERRORS_R_XMLSEC_FAILED,
                            XMLSEC_ERRORS_NO_MESSAGE);
                xmlFree(tmp);
                return(-1);
            }
        }

        cur = cur->next;
    }

    return(0);
}

/* Sorts Relationship elements by Id value in lexicographical order. */
static int
xmlSecTransformRelationshipCompare(xmlNodePtr node1, xmlNodePtr node2) {
    xmlChar* id1;
    xmlChar* id2;

    if(node1 == node2) {
        return(0);
    }
    if(node1 == NULL) {
        return(-1);
    }
    if(node2 == NULL) {
        return(1);
    }

    id1 = xmlGetProp(node1, xmlSecRelationshipAttrId);
    id2 = xmlGetProp(node2, xmlSecRelationshipAttrId);
    if(id1 == NULL) {
        return(-1);
    }
    if(id2 == NULL) {
        return(1);
    }

    return(xmlStrcmp(id1, id2));
}

/**
 * This is step 2, point 4: if the input sourceId list doesn't contain the Id attribute of the current node,
 * then exclude it from the output, instead of processing it.
 */
static int
xmlSecTransformRelationshipProcessNode(xmlSecTransformPtr transform, xmlOutputBufferPtr buf, xmlNodePtr cur) {
    int found = -1;
    xmlSecRelationshipCtxPtr ctx;
    xmlSecSize ii;
    int ret;

    xmlSecAssert2(transform != NULL, -1);
    xmlSecAssert2(buf != NULL, -1);
    xmlSecAssert2(cur != NULL, -1);

    if(xmlSecCheckNodeName(cur, xmlSecNodeRelationship, xmlSecRelationshipsNs)) {
        xmlChar* id = xmlGetProp(cur, xmlSecRelationshipAttrId);
        if(id == NULL) {
            xmlSecError(XMLSEC_ERRORS_HERE,
                        xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
                        "xmlGetProp(xmlSecRelationshipAttrId)",
                        XMLSEC_ERRORS_R_XML_FAILED,
                        "name=Id");
            return(-1);
        }

        ctx = xmlSecRelationshipGetCtx(transform);
        for(ii = 0; ii < xmlSecPtrListGetSize(ctx->sourceIdList); ++ii) {
            if(xmlStrcmp(xmlSecPtrListGetItem(ctx->sourceIdList, ii), id) == 0) {
                found = 1;
                break;
            }
        }

        if(found < 0) {
            return(0);
        }
    }

    ret = xmlSecTransformRelationshipProcessElementNode(transform, buf, cur);
    if(ret < 0) {
        xmlSecError(XMLSEC_ERRORS_HERE,
                    xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
                    "xmlSecTransformRelationshipProcessElementNode",
                    XMLSEC_ERRORS_R_XMLSEC_FAILED,
                    XMLSEC_ERRORS_NO_MESSAGE);
        return(-1);
    }

    return(0);
}

/**
 * This is step 2, point 3: sort elements by Id: we process other elements as-is, but for elements we collect them in a list,
 * then sort, and finally process them (process the head of the list, then pop the head, till the list becomes empty).
 */
static int
xmlSecTransformRelationshipProcessNodeList(xmlSecTransformPtr transform, xmlOutputBufferPtr buf, xmlNodePtr cur) {
    xmlListPtr list;
    int ret;

    xmlSecAssert2(transform != NULL, -1);
    xmlSecAssert2(buf != NULL, -1);
    xmlSecAssert2(cur != NULL, -1);

    list = xmlListCreate(NULL, (xmlListDataCompare)xmlSecTransformRelationshipCompare);
    if(list == NULL) {
        xmlSecError(XMLSEC_ERRORS_HERE,
                    xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
                    "xmlListCreate",
                    XMLSEC_ERRORS_R_XMLSEC_FAILED,
                    XMLSEC_ERRORS_NO_MESSAGE);
        return(-1);
    }

    for(; cur; cur = cur->next) {
        if(xmlStrcmp(cur->name, xmlSecNodeRelationship) == 0) {
            if(xmlListInsert(list, cur) != 0) {
                xmlSecError(XMLSEC_ERRORS_HERE,
                            xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
                            "xmlListInsert",
                            XMLSEC_ERRORS_R_XMLSEC_FAILED,
                            XMLSEC_ERRORS_NO_MESSAGE);
                return(-1);
            }
        } else {
            ret = xmlSecTransformRelationshipProcessNode(transform, buf, cur);
            if(ret < 0) {
                xmlSecError(XMLSEC_ERRORS_HERE,
                            xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
                            "xmlSecTransformRelationshipProcessNode",
                            XMLSEC_ERRORS_R_XMLSEC_FAILED,
                            XMLSEC_ERRORS_NO_MESSAGE);
                xmlListDelete(list);
                return(-1);
            }
        }
    }

    xmlListSort(list);

    while(!xmlListEmpty(list)) {
        xmlLinkPtr link = xmlListFront(list);
        xmlNodePtr node = (xmlNodePtr)xmlLinkGetData(link);

        ret = xmlSecTransformRelationshipProcessNode(transform, buf, node);
        if(ret < 0) {
            xmlSecError(XMLSEC_ERRORS_HERE,
                        xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
                        "xmlSecTransformRelationshipProcessNode",
                        XMLSEC_ERRORS_R_XMLSEC_FAILED,
                        XMLSEC_ERRORS_NO_MESSAGE);
            xmlListDelete(list);
            return(-1);
        }

        xmlListPopFront(list);
    }

    /* done */
    xmlListDelete(list);
    return(0);
}

static int
xmlSecTransformRelationshipWriteProp(xmlOutputBufferPtr buf, const xmlChar * name, const xmlChar * value) {
    int ret;

    xmlSecAssert2(buf != NULL, -1);
    xmlSecAssert2(name != NULL, -1);

    ret = xmlOutputBufferWriteString(buf, " ");
    if(ret < 0) {
        xmlSecError(XMLSEC_ERRORS_HERE,
                    NULL,
                    "xmlOutputBufferWriteString",
                    XMLSEC_ERRORS_R_XMLSEC_FAILED,
                    XMLSEC_ERRORS_NO_MESSAGE);
        return(-1);
    }

    ret = xmlOutputBufferWriteString(buf, (const char*) name);
    if(ret < 0) {
        xmlSecError(XMLSEC_ERRORS_HERE,
                    NULL,
                    "xmlOutputBufferWriteString",
                    XMLSEC_ERRORS_R_XMLSEC_FAILED,
                    XMLSEC_ERRORS_NO_MESSAGE);
        return(-1);
    }

    if(value != NULL) {
        ret = xmlOutputBufferWriteString(buf, "=\"");
        if(ret < 0) {
            xmlSecError(XMLSEC_ERRORS_HERE,
                        NULL,
                        "xmlOutputBufferWriteString",
                        XMLSEC_ERRORS_R_XMLSEC_FAILED,
                        XMLSEC_ERRORS_NO_MESSAGE);
            return(-1);
        }
        ret = xmlOutputBufferWriteString(buf, (const char*) value);
        if(ret < 0) {
            xmlSecError(XMLSEC_ERRORS_HERE,
                        NULL,
                        "xmlOutputBufferWriteString",
                        XMLSEC_ERRORS_R_XMLSEC_FAILED,
                        XMLSEC_ERRORS_NO_MESSAGE);
            return(-1);
        }
        ret = xmlOutputBufferWriteString(buf, "\"");
        if(ret < 0) {
            xmlSecError(XMLSEC_ERRORS_HERE,
                        NULL,
                        "xmlOutputBufferWriteString",
                        XMLSEC_ERRORS_R_XMLSEC_FAILED,
                        XMLSEC_ERRORS_NO_MESSAGE);
            return(-1);
        }
    }

    return (0);
}

static int
xmlSecTransformRelationshipWriteNs(xmlOutputBufferPtr buf, const xmlChar * href) {
    xmlSecAssert2(buf != NULL, -1);

    return(xmlSecTransformRelationshipWriteProp(buf, BAD_CAST "xmlns", (href != NULL) ? href : BAD_CAST ""));
}


static int
xmlSecTransformRelationshipProcessElementNode(xmlSecTransformPtr transform, xmlOutputBufferPtr buf, xmlNodePtr cur) {
    xmlAttrPtr attr;
    int foundTargetMode = 0;
    int ret;

    xmlSecAssert2(transform != NULL, -1);
    xmlSecAssert2(buf != NULL, -1);
    xmlSecAssert2(cur != NULL, -1);
    xmlSecAssert2(cur->name != NULL, -1);

    /* write open node */
    ret = xmlOutputBufferWriteString(buf, "<");
    if(ret < 0) {
        xmlSecError(XMLSEC_ERRORS_HERE,
                    xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
                    "xmlOutputBufferWriteString",
                    XMLSEC_ERRORS_R_XMLSEC_FAILED,
                    XMLSEC_ERRORS_NO_MESSAGE);
        return(-1);
    }
    ret = xmlOutputBufferWriteString(buf, (const char *)cur->name);
    if(ret < 0) {
        xmlSecError(XMLSEC_ERRORS_HERE,
                    xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
                    "xmlOutputBufferWriteString",
                    XMLSEC_ERRORS_R_XMLSEC_FAILED,
                    XMLSEC_ERRORS_NO_MESSAGE);
        return(-1);
    }

    /* write namespaces */
    if(cur->nsDef != NULL) {
        ret = xmlSecTransformRelationshipWriteNs(buf, cur->nsDef->href);
        if(ret < 0) {
            xmlSecError(XMLSEC_ERRORS_HERE,
                        xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
                        "xmlSecTransformRelationshipWriteNs",
                        XMLSEC_ERRORS_R_XMLSEC_FAILED,
                        XMLSEC_ERRORS_NO_MESSAGE);
            return(-1);
        }
    }

    /**
     *  write attributes:
     *
     *  This is step 3, point 6: add default value of TargetMode if there is no such attribute.
     */
    for(attr = cur->properties; attr != NULL; attr = attr->next) {
        xmlChar * value = xmlGetProp(cur, attr->name);

        if(xmlStrcmp(attr->name, xmlSecRelationshipAttrTargetMode) == 0) {
            foundTargetMode = 1;
        }

        ret = xmlSecTransformRelationshipWriteProp(buf, attr->name, value);
        if(ret < 0) {
            xmlSecError(XMLSEC_ERRORS_HERE,
                        xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
                        "xmlSecTransformRelationshipWriteProp",
                        XMLSEC_ERRORS_R_XMLSEC_FAILED,
                        XMLSEC_ERRORS_NO_MESSAGE);
            return(-1);
        }
    }

    /* write TargetMode */
    if(xmlStrcmp(cur->name, xmlSecNodeRelationship) == 0 && !foundTargetMode) {
        ret = xmlSecTransformRelationshipWriteProp(buf, xmlSecRelationshipAttrTargetMode, BAD_CAST "Internal");
        if(ret < 0) {
            xmlSecError(XMLSEC_ERRORS_HERE,
                        xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
                        "xmlSecTransformRelationshipWriteProp(TargetMode=Internal)",
                        XMLSEC_ERRORS_R_XMLSEC_FAILED,
                        XMLSEC_ERRORS_NO_MESSAGE);
            return(-1);
        }
    }

    /* finish writing open node */
    ret = xmlOutputBufferWriteString(buf, ">");
    if(ret < 0) {
        xmlSecError(XMLSEC_ERRORS_HERE,
                    xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
                    "xmlOutputBufferWriteString",
                    XMLSEC_ERRORS_R_XMLSEC_FAILED,
                    XMLSEC_ERRORS_NO_MESSAGE);
        return(-1);
    }

    /* write children */
    if(cur->children != NULL) {
        ret = xmlSecTransformRelationshipProcessNodeList(transform, buf, cur->children);
        if(ret < 0) {
            xmlSecError(XMLSEC_ERRORS_HERE,
                        xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
                        "xmlSecTransformRelationshipProcessNodeList",
                        XMLSEC_ERRORS_R_XMLSEC_FAILED,
                        XMLSEC_ERRORS_NO_MESSAGE);
            return(-1);
        }
    }

    /* write closing node */
    ret = xmlOutputBufferWriteString(buf, "</");
    if(ret < 0) {
        xmlSecError(XMLSEC_ERRORS_HERE,
                    xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
                    "xmlOutputBufferWriteString",
                    XMLSEC_ERRORS_R_XMLSEC_FAILED,
                    XMLSEC_ERRORS_NO_MESSAGE);
        return(-1);
    }
    ret = xmlOutputBufferWriteString(buf, (const char *)cur->name);
    if(ret < 0) {
        xmlSecError(XMLSEC_ERRORS_HERE,
                    xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
                    "xmlOutputBufferWriteString",
                    XMLSEC_ERRORS_R_XMLSEC_FAILED,
                    XMLSEC_ERRORS_NO_MESSAGE);
        return(-1);
    }
    if(xmlOutputBufferWriteString(buf, ">") < 0) {
        xmlSecError(XMLSEC_ERRORS_HERE,
                    xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
                    "xmlOutputBufferWriteString",
                    XMLSEC_ERRORS_R_XMLSEC_FAILED,
                    XMLSEC_ERRORS_NO_MESSAGE);
        return(-1);
    }

    /* done */
    return(0);
}

static int
xmlSecTransformRelationshipExecute(xmlSecTransformPtr transform, xmlOutputBufferPtr buf, xmlDocPtr doc) {
    int ret;

    xmlSecAssert2(transform != NULL, -1);
    xmlSecAssert2(buf != NULL, -1);
    xmlSecAssert2(doc != NULL, -1);

    if(doc->children != NULL) {
        ret = xmlSecTransformRelationshipProcessNodeList(transform, buf, doc->children);
        if(ret < 0) {
            xmlSecError(XMLSEC_ERRORS_HERE,
                        xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
                        "xmlSecTransformRelationshipProcessNodeList",
                        XMLSEC_ERRORS_R_XMLSEC_FAILED,
                        XMLSEC_ERRORS_NO_MESSAGE);
            return(-1);
        }
    }

    return(0);
}

static int
xmlSecTransformRelationshipPushXml(xmlSecTransformPtr transform, xmlSecNodeSetPtr nodes, xmlSecTransformCtxPtr transformCtx)
{
    xmlOutputBufferPtr buf;
    xmlSecRelationshipCtxPtr ctx;
    int ret;

    xmlSecAssert2(nodes != NULL, -1);
    xmlSecAssert2(nodes->doc != NULL, -1);
    xmlSecAssert2(transformCtx != NULL, -1);

    ctx = xmlSecRelationshipGetCtx(transform);
    xmlSecAssert2(ctx != NULL, -1);

    /* check/update current transform status */
    switch(transform->status) {
    case xmlSecTransformStatusNone:
       transform->status = xmlSecTransformStatusWorking;
       break;
    case xmlSecTransformStatusWorking:
    case xmlSecTransformStatusFinished:
       return(0);
    default:
       xmlSecError(XMLSEC_ERRORS_HERE,
                   xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
                   NULL,
                   XMLSEC_ERRORS_R_INVALID_STATUS,
                   "status=%d", transform->status);
       return(-1);
    }
    xmlSecAssert2(transform->status == xmlSecTransformStatusWorking, -1);

    /* prepare output buffer: next transform or ourselves */
    if(transform->next != NULL) {
       buf = xmlSecTransformCreateOutputBuffer(transform->next, transformCtx);
       if(buf == NULL) {
           xmlSecError(XMLSEC_ERRORS_HERE,
                       xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
                       "xmlSecTransformCreateOutputBuffer",
                       XMLSEC_ERRORS_R_XMLSEC_FAILED,
                       XMLSEC_ERRORS_NO_MESSAGE);
           return(-1);
       }
    } else {
       buf = xmlSecBufferCreateOutputBuffer(&(transform->outBuf));
       if(buf == NULL) {
           xmlSecError(XMLSEC_ERRORS_HERE,
                       xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
                       "xmlSecBufferCreateOutputBuffer",
                       XMLSEC_ERRORS_R_XMLSEC_FAILED,
                       XMLSEC_ERRORS_NO_MESSAGE);
           return(-1);
       }
    }

    ret = xmlSecTransformRelationshipExecute(transform, buf, nodes->doc);
    if(ret < 0) {
       xmlSecError(XMLSEC_ERRORS_HERE,
                   xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
                   "xmlC14NExecute",
                   XMLSEC_ERRORS_R_XMLSEC_FAILED,
                   XMLSEC_ERRORS_NO_MESSAGE);
       xmlOutputBufferClose(buf);
       return(-1);
    }

    ret = xmlOutputBufferClose(buf);
    if(ret < 0) {
       xmlSecError(XMLSEC_ERRORS_HERE,
                   xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
                   "xmlOutputBufferClose",
                   XMLSEC_ERRORS_R_XML_FAILED,
                   XMLSEC_ERRORS_NO_MESSAGE);
       return(-1);
    }
    transform->status = xmlSecTransformStatusFinished;
    return(0);
}

static int
xmlSecTransformRelationshipPopBin(xmlSecTransformPtr transform, xmlSecByte* data, xmlSecSize maxDataSize, xmlSecSize* dataSize, xmlSecTransformCtxPtr transformCtx) {
    xmlSecBufferPtr out;
    int ret;

    xmlSecAssert2(data != NULL, -1);
    xmlSecAssert2(dataSize != NULL, -1);
    xmlSecAssert2(transformCtx != NULL, -1);

    out = &(transform->outBuf);
    if(transform->status == xmlSecTransformStatusNone) {
       xmlOutputBufferPtr buf;

       xmlSecAssert2(transform->inNodes == NULL, -1);

       if(transform->prev == NULL) {
           (*dataSize) = 0;
           transform->status = xmlSecTransformStatusFinished;
           return(0);
       }

       /* get xml data from previous transform */
       ret = xmlSecTransformPopXml(transform->prev, &(transform->inNodes), transformCtx);
       if(ret < 0) {
           xmlSecError(XMLSEC_ERRORS_HERE,
                       xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
                       "xmlSecTransformPopXml",
                       XMLSEC_ERRORS_R_XMLSEC_FAILED,
                       XMLSEC_ERRORS_NO_MESSAGE);
           return(-1);
       }

       /* dump everything to internal buffer */
       buf = xmlSecBufferCreateOutputBuffer(out);
       if(buf == NULL) {
           xmlSecError(XMLSEC_ERRORS_HERE,
                       xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
                       "xmlSecBufferCreateOutputBuffer",
                       XMLSEC_ERRORS_R_XMLSEC_FAILED,
                       XMLSEC_ERRORS_NO_MESSAGE);
           return(-1);
       }

       ret = xmlC14NExecute(transform->inNodes->doc, (xmlC14NIsVisibleCallback)xmlSecNodeSetContains, transform->inNodes, XML_C14N_1_0, NULL, 0, buf);
       if(ret < 0) {
            xmlSecError(XMLSEC_ERRORS_HERE,
                       xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
                       "xmlSecTransformC14NExecute",
                       XMLSEC_ERRORS_R_XMLSEC_FAILED,
                       XMLSEC_ERRORS_NO_MESSAGE);
           xmlOutputBufferClose(buf);
           return(-1);
       }

       ret = xmlOutputBufferClose(buf);
       if(ret < 0) {
           xmlSecError(XMLSEC_ERRORS_HERE,
                       xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
                       "xmlOutputBufferClose",
                       XMLSEC_ERRORS_R_XML_FAILED,
                       XMLSEC_ERRORS_NO_MESSAGE);
           return(-1);
       }
       transform->status = xmlSecTransformStatusWorking;
    }

    if(transform->status == xmlSecTransformStatusWorking) {
       xmlSecSize outSize;

       /* return chunk after chunk */
       outSize = xmlSecBufferGetSize(out);
       if(outSize > maxDataSize) {
           outSize = maxDataSize;
       }
       if(outSize > XMLSEC_TRANSFORM_BINARY_CHUNK) {
           outSize = XMLSEC_TRANSFORM_BINARY_CHUNK;
       }
       if(outSize > 0) {
           xmlSecAssert2(xmlSecBufferGetData(out), -1);

           memcpy(data, xmlSecBufferGetData(out), outSize);
           ret = xmlSecBufferRemoveHead(out, outSize);
           if(ret < 0) {
               xmlSecError(XMLSEC_ERRORS_HERE,
                           xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
                           "xmlSecBufferRemoveHead",
                           XMLSEC_ERRORS_R_XMLSEC_FAILED,
                           "size=%d", outSize);
               return(-1);
           }
       } else if(xmlSecBufferGetSize(out) == 0) {
           transform->status = xmlSecTransformStatusFinished;
       }
       (*dataSize) = outSize;
    } else if(transform->status == xmlSecTransformStatusFinished) {
       /* the only way we can get here is if there is no output */
       xmlSecAssert2(xmlSecBufferGetSize(out) == 0, -1);
       (*dataSize) = 0;
    } else {
       xmlSecError(XMLSEC_ERRORS_HERE,
                   xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
                   NULL,
                   XMLSEC_ERRORS_R_INVALID_STATUS,
                   "status=%d", transform->status);
       return(-1);
    }

    return(0);
}
