/src/xmlsec/src/relationship.c
Line | Count | Source |
1 | | /** |
2 | | * XML Security Library (http://www.aleksey.com/xmlsec). |
3 | | * |
4 | | * This is free software; see the Copyright file in the source distribution for precise wording. |
5 | | * |
6 | | * Copyright (C) 2002-2026 Aleksey Sanin <aleksey@aleksey.com>. All Rights Reserved. |
7 | | */ |
8 | | /** |
9 | | * @addtogroup xmlsec_core_transforms |
10 | | * @brief Relationship transform implementation. |
11 | | */ |
12 | | #include "globals.h" |
13 | | |
14 | | #include <stdlib.h> |
15 | | #include <string.h> |
16 | | |
17 | | #include <libxml/tree.h> |
18 | | |
19 | | #include <xmlsec/xmlsec.h> |
20 | | #include <xmlsec/xmltree.h> |
21 | | #include <xmlsec/list.h> |
22 | | #include <xmlsec/transforms.h> |
23 | | #include <xmlsec/errors.h> |
24 | | |
25 | | #include "cast_helpers.h" |
26 | | |
27 | 0 | #define xmlSecRelationshipXmlNs BAD_CAST "http://www.w3.org/XML/1998/namespace" |
28 | | |
29 | | |
30 | | /****************************************************************************** |
31 | | * |
32 | | * XML Relationship transform |
33 | | * |
34 | | * * [Relationship transform](http://standards.iso.org/ittf/PubliclyAvailableStandards/c061796_ISO_IEC_29500-2_2012.zip) |
35 | | * |
36 | | * The relationships transform takes the XML document from the Relationships part and converts |
37 | | * it to another XML document. |
38 | | * |
39 | | * The package implementer might create relationships XML that contains content from several namespaces, |
40 | | * along with versioning instructions as defined in Part 3, “Markup Compatibility and Extensibility”. [O6.11] |
41 | | * |
42 | | * The relationships transform algorithm is as follows: |
43 | | * |
44 | | * Step 1: Process versioning instructions |
45 | | * 1. The package implementer shall process the versioning instructions, considering that the only |
46 | | * known namespace is the Relationships namespace. |
47 | | * 2. The package implementer shall remove all ignorable content, ignoring preservation attributes. |
48 | | * 3. The package implementer shall remove all versioning instructions. |
49 | | * |
50 | | * Step 2: Sort and filter relationships |
51 | | * 1. The package implementer shall remove all namespace declarations except the Relationships |
52 | | * namespace declaration. |
53 | | * 2. The package implementer shall remove the Relationships namespace prefix, if it is present. |
54 | | * 3. The package implementer shall sort relationship elements by Id value in lexicographical |
55 | | * order, considering Id values as case-sensitive Unicode strings. |
56 | | * 4. The package implementer shall remove all Relationship elements that do not have either an Id |
57 | | * value that matches any SourceId value or a Type value that matches any SourceType value, among |
58 | | * the SourceId and SourceType values specified in the transform definition. Producers and consumers |
59 | | * shall compare values as case-sensitive Unicode strings. [M6.27] The resulting XML document holds |
60 | | * all Relationship elements that either have an Id value that matches a SourceId value or a Type value |
61 | | * that matches a SourceType value specified in the transform definition. |
62 | | * |
63 | | * Step 3: Prepare for canonicalization |
64 | | * 1. The package implementer shall remove all characters between the Relationships start tag and |
65 | | * the first Relationship start tag. |
66 | | * 2. The package implementer shall remove any contents of the Relationship element. |
67 | | * 3. The package implementer shall remove all characters between the last Relationship end tag and |
68 | | * the Relationships end tag. |
69 | | * 4. If there are no Relationship elements, the package implementer shall remove all characters |
70 | | * between the Relationships start tag and the Relationships end tag. |
71 | | * 5. The package implementer shall remove comments from the Relationships XML content. |
72 | | * 6. The package implementer shall add a TargetMode attribute with its default value, if this |
73 | | * optional attribute is missing from the Relationship element. |
74 | | * 7. The package implementer can generate Relationship elements as start-tag/end-tag pairs with |
75 | | * empty content, or as empty elements. A canonicalization transform, applied immediately after the |
76 | | * Relationships Transform, converts all XML elements into start-tag/end-tag pairs. |
77 | | * |
78 | | * |
79 | | * IMPLEMENTATION NOTES (https://github.com/lsh123/xmlsec/pull/24): |
80 | | * |
81 | | * * We don't simply manipulate the XML tree, but do an XML tree -> output bytes transformation, |
82 | | * because we never write characters inside XML elements, we implicitly remove all character |
83 | | * contents, as required by step 3, point 1. It also simplifies the task of the situation that |
84 | | * realistically the input of the transformation is always a document that conforms to the OOXML |
85 | | * relationships XML schema, so in practice it'll never happen that the input document has e.g. |
86 | | * characters, as the schema requires that the document has only XML elements and attributes, |
87 | | * but no characters. |
88 | | * |
89 | | * * Step 1 says that versioning instructions are processed with only the Relationships namespace |
90 | | * treated as known. In non-legacy mode we therefore serialize only elements in the Relationships |
91 | | * namespace plus unprefixed or built-in xml:* attributes; MCE attributes/elements and other |
92 | | * foreign-namespaced content are omitted from the canonical output. |
93 | | * |
94 | | * * Step 2, point 4 talks about a SourceType value, but given that neither Microsoft Office, nor LibreOffice |
95 | | * writes that theoretical attribute, the implementation doesn't handle it. If there is a real-world situation |
96 | | * when there will be such an input, then it'll be easy to add support for that. But I didn't want to clutter |
97 | | * the current implementation with details that don't seem to be used in practice |
98 | | * |
99 | | * xmlSecTransform + xmlSecRelationshipCtx |
100 | | * |
101 | | *****************************************************************************/ |
102 | | typedef struct _xmlSecRelationshipCtx xmlSecRelationshipCtx, |
103 | | *xmlSecRelationshipCtxPtr; |
104 | | struct _xmlSecRelationshipCtx { |
105 | | xmlSecPtrListPtr sourceIdList; |
106 | | }; |
107 | | |
108 | 0 | XMLSEC_TRANSFORM_DECLARE(Relationship, xmlSecRelationshipCtx) |
109 | 0 | #define xmlSecRelationshipSize XMLSEC_TRANSFORM_SIZE(Relationship) |
110 | 0 |
|
111 | 0 | static int xmlSecRelationshipInitialize (xmlSecTransformPtr transform); |
112 | 0 | static void xmlSecRelationshipFinalize (xmlSecTransformPtr transform); |
113 | 0 | static int xmlSecTransformRelationshipPopBin (xmlSecTransformPtr transform, |
114 | 0 | xmlSecByte* data, |
115 | 0 | xmlSecSize maxDataSize, |
116 | 0 | xmlSecSize* dataSize, |
117 | 0 | xmlSecTransformCtxPtr transformCtx); |
118 | 0 | static int xmlSecTransformRelationshipPushXml(xmlSecTransformPtr transform, |
119 | 0 | xmlSecNodeSetPtr nodes, |
120 | 0 | xmlSecTransformCtxPtr transformCtx); |
121 | 0 | static int xmlSecRelationshipReadNode (xmlSecTransformPtr transform, |
122 | 0 | xmlNodePtr node, |
123 | 0 | xmlSecTransformCtxPtr transformCtx); |
124 | 0 |
|
125 | 0 | static int xmlSecTransformRelationshipProcessElementNode(xmlSecTransformPtr transform, |
126 | 0 | xmlOutputBufferPtr buf, |
127 | 0 | xmlNodePtr cur, |
128 | 0 | unsigned int depth, |
129 | 0 | xmlSecTransformCtxPtr transformCtx); |
130 | 0 |
|
131 | 0 |
|
132 | 0 | static xmlSecTransformKlass xmlSecRelationshipKlass = { |
133 | 0 | /* klass/object sizes */ |
134 | 0 | sizeof(xmlSecTransformKlass), /* xmlSecSize klassSize */ |
135 | 0 | xmlSecRelationshipSize, /* xmlSecSize objSize */ |
136 | 0 |
|
137 | 0 | xmlSecNameRelationship, /* const xmlChar* name; */ |
138 | 0 | xmlSecHrefRelationship, /* const xmlChar* href; */ |
139 | 0 | xmlSecTransformUsageDSigTransform, /* xmlSecTransformUsage usage; */ |
140 | 0 |
|
141 | 0 | xmlSecRelationshipInitialize, /* xmlSecTransformInitializeMethod initialize; */ |
142 | 0 | xmlSecRelationshipFinalize, /* xmlSecTransformFinalizeMethod finalize; */ |
143 | 0 | xmlSecRelationshipReadNode, /* xmlSecTransformNodeReadMethod readNode; */ |
144 | 0 | NULL, /* xmlSecTransformNodeWriteMethod writeNode; */ |
145 | 0 | NULL, /* xmlSecTransformSetKeyReqMethod setKeyReq; */ |
146 | 0 | NULL, /* xmlSecTransformSetKeyMethod setKey; */ |
147 | 0 | NULL, /* xmlSecTransformVerifyMethod verify; */ |
148 | 0 | xmlSecTransformDefaultGetDataType, /* xmlSecTransformGetDataTypeMethod getDataType; */ |
149 | 0 | NULL, /* xmlSecTransformPushBinMethod pushBin; */ |
150 | 0 | xmlSecTransformRelationshipPopBin, /* xmlSecTransformPopBinMethod popBin; */ |
151 | 0 | xmlSecTransformRelationshipPushXml, /* xmlSecTransformPushXmlMethod pushXml; */ |
152 | 0 | NULL, /* xmlSecTransformPopXmlMethod popXml; */ |
153 | 0 | NULL, /* xmlSecTransformExecuteMethod execute; */ |
154 | 0 |
|
155 | 0 | NULL, /* void* reserved0; */ |
156 | 0 | NULL, /* void* reserved1; */ |
157 | 0 | }; |
158 | 0 |
|
159 | 0 | /** |
160 | 0 | * @brief Gets the Relationship transform klass. |
161 | 0 | * |
162 | 0 | * @return Relationship transform klass. |
163 | 0 | */ |
164 | 0 | xmlSecTransformId |
165 | 1 | xmlSecTransformRelationshipGetKlass(void) { |
166 | 1 | return(&xmlSecRelationshipKlass); |
167 | 1 | } |
168 | | |
169 | | static int |
170 | 0 | xmlSecRelationshipInitialize(xmlSecTransformPtr transform) { |
171 | 0 | xmlSecRelationshipCtxPtr ctx; |
172 | |
|
173 | 0 | xmlSecAssert2(xmlSecTransformCheckId(transform, xmlSecTransformRelationshipId), -1); |
174 | 0 | xmlSecAssert2(xmlSecTransformCheckSize(transform, xmlSecRelationshipSize), -1); |
175 | | |
176 | 0 | ctx = xmlSecRelationshipGetCtx(transform); |
177 | 0 | xmlSecAssert2(ctx != NULL, -1); |
178 | | |
179 | | /* initialize context */ |
180 | 0 | memset(ctx, 0, sizeof(xmlSecRelationshipCtx)); |
181 | |
|
182 | 0 | ctx->sourceIdList = xmlSecPtrListCreate(xmlSecStringListId); |
183 | 0 | if(ctx->sourceIdList == NULL) { |
184 | 0 | xmlSecInternalError("xmlSecPtrListCreate", |
185 | 0 | xmlSecTransformGetName(transform)); |
186 | 0 | return(-1); |
187 | 0 | } |
188 | 0 | return(0); |
189 | 0 | } |
190 | | |
191 | | static void |
192 | 0 | xmlSecRelationshipFinalize(xmlSecTransformPtr transform) { |
193 | 0 | xmlSecRelationshipCtxPtr ctx; |
194 | |
|
195 | 0 | xmlSecAssert(xmlSecTransformCheckId(transform, xmlSecTransformRelationshipId)); |
196 | 0 | xmlSecAssert(xmlSecTransformCheckSize(transform, xmlSecRelationshipSize)); |
197 | | |
198 | 0 | ctx = xmlSecRelationshipGetCtx(transform); |
199 | 0 | xmlSecAssert(ctx != NULL); |
200 | | |
201 | 0 | if(ctx->sourceIdList != NULL) { |
202 | 0 | xmlSecPtrListDestroy(ctx->sourceIdList); |
203 | 0 | } |
204 | |
|
205 | 0 | memset(ctx, 0, sizeof(xmlSecRelationshipCtx)); |
206 | 0 | } |
207 | | |
208 | | static int |
209 | 0 | xmlSecRelationshipReadNode(xmlSecTransformPtr transform, xmlNodePtr node, xmlSecTransformCtxPtr transformCtx) { |
210 | 0 | xmlSecRelationshipCtxPtr ctx; |
211 | 0 | xmlNodePtr cur; |
212 | 0 | xmlChar* sourceId; |
213 | 0 | int ret; |
214 | |
|
215 | 0 | xmlSecAssert2(xmlSecTransformCheckId(transform, xmlSecTransformRelationshipId), -1); |
216 | 0 | xmlSecAssert2(xmlSecTransformCheckSize(transform, xmlSecRelationshipSize), -1); |
217 | 0 | xmlSecAssert2(node != NULL, -1); |
218 | 0 | xmlSecAssert2(transformCtx != NULL, -1); |
219 | | |
220 | | /* Collect list of source ids from all RelationshipReference nodes */ |
221 | 0 | ctx = xmlSecRelationshipGetCtx(transform); |
222 | 0 | xmlSecAssert2(ctx != NULL, -1); |
223 | | |
224 | 0 | for(cur = node->children; cur != NULL; cur = cur->next) { |
225 | | /* we only care about RelationshipReference nodes */ |
226 | 0 | if(!xmlSecCheckNodeName(cur, xmlSecNodeRelationshipReference, xmlSecRelationshipReferenceNs)) { |
227 | 0 | continue; |
228 | 0 | } |
229 | | |
230 | | /* which MUST have sourceId attribute. xmlGetProp() returns NULL both when the |
231 | | * attribute is absent and when it cannot allocate the return value, so check |
232 | | * for presence first to report the correct error. */ |
233 | 0 | if(!xmlHasProp(cur, xmlSecRelationshipAttrSourceId)) { |
234 | 0 | xmlSecInvalidNodeAttributeError(cur, xmlSecRelationshipAttrSourceId, NULL, "missing"); |
235 | 0 | return(-1); |
236 | 0 | } |
237 | 0 | sourceId = xmlGetProp(cur, xmlSecRelationshipAttrSourceId); |
238 | 0 | if(sourceId == NULL) { |
239 | 0 | xmlSecXmlError("xmlGetProp", xmlSecTransformGetName(transform)); |
240 | 0 | return(-1); |
241 | 0 | } |
242 | | |
243 | | /* add it to the list */ |
244 | 0 | ret = xmlSecPtrListAdd(ctx->sourceIdList, sourceId); |
245 | 0 | if(ret < 0) { |
246 | 0 | xmlSecInternalError("xmlSecPtrListAdd", xmlSecTransformGetName(transform)); |
247 | 0 | xmlFree(sourceId); |
248 | 0 | return(-1); |
249 | 0 | } |
250 | 0 | } |
251 | | |
252 | | /* done */ |
253 | 0 | return(0); |
254 | 0 | } |
255 | | |
256 | | /* |
257 | | * An item of the list used to collect Relationship elements before sorting: |
258 | | * the element itself plus the (cached) value of its Id attribute. Caching the |
259 | | * Id value avoids re-reading (and re-allocating) the attribute on every |
260 | | * comparison while the list is being sorted. |
261 | | */ |
262 | | typedef struct _xmlSecRelationshipListItem xmlSecRelationshipListItem, |
263 | | *xmlSecRelationshipListItemPtr; |
264 | | struct _xmlSecRelationshipListItem { |
265 | | xmlNodePtr node; /**< the Relationship element. */ |
266 | | xmlChar* id; /**< the cached value of the Id attribute (NULL if the element has no Id or it could not be read). */ |
267 | | }; |
268 | | |
269 | | static void xmlSecRelationshipListItemDestroy (xmlSecPtr item); |
270 | | |
271 | | static xmlSecPtrListKlass xmlSecRelationshipListItemKlass = { |
272 | | BAD_CAST "relationship-list-items", |
273 | | NULL, /* xmlSecPtrDuplicateItemMethod duplicateItem; */ |
274 | | xmlSecRelationshipListItemDestroy, /* xmlSecPtrDestroyItemMethod destroyItem; */ |
275 | | NULL, /* xmlSecPtrDebugDumpItemMethod debugDumpItem; */ |
276 | | NULL, /* xmlSecPtrDebugDumpItemMethod debugXmlDumpItem; */ |
277 | | }; |
278 | | |
279 | | /** |
280 | | * @brief Creates a new list item for @p node. |
281 | | * @details The Id attribute of @p node is read once and cached in the new |
282 | | * item. Caller is responsible for freeing the returned item by calling |
283 | | * #xmlSecRelationshipListItemDestroy function. |
284 | | * @param node the pointer to the Relationship element. |
285 | | * @return pointer to the newly allocated item or NULL if an error occurs. |
286 | | */ |
287 | | static xmlSecRelationshipListItemPtr |
288 | 0 | xmlSecRelationshipListItemCreate(xmlNodePtr node) { |
289 | 0 | xmlSecRelationshipListItemPtr item; |
290 | |
|
291 | 0 | xmlSecAssert2(node != NULL, NULL); |
292 | | |
293 | 0 | item = (xmlSecRelationshipListItemPtr)xmlMalloc(sizeof(xmlSecRelationshipListItem)); |
294 | 0 | if(item == NULL) { |
295 | 0 | xmlSecMallocError(sizeof(xmlSecRelationshipListItem), NULL); |
296 | 0 | return(NULL); |
297 | 0 | } |
298 | | |
299 | 0 | item->node = node; |
300 | | /* |
301 | | * xmlGetProp() returns NULL both when the Id attribute is absent and |
302 | | * when it cannot allocate the return value; in either case the item is |
303 | | * left with no cached Id (the element is dropped later by |
304 | | * xmlSecTransformRelationshipProcessNode, which reports the same |
305 | | * condition). |
306 | | */ |
307 | 0 | item->id = xmlGetProp(node, xmlSecRelationshipAttrId); |
308 | 0 | return(item); |
309 | 0 | } |
310 | | |
311 | | static void |
312 | 0 | xmlSecRelationshipListItemDestroy(xmlSecPtr item) { |
313 | 0 | xmlSecRelationshipListItemPtr listItem = (xmlSecRelationshipListItemPtr)item; |
314 | |
|
315 | 0 | xmlSecAssert(item != NULL); |
316 | | |
317 | 0 | if(listItem->id != NULL) { |
318 | 0 | xmlFree(listItem->id); |
319 | 0 | } |
320 | 0 | xmlFree(listItem); |
321 | 0 | } |
322 | | |
323 | | /* |
324 | | * Compares two list items by their cached Id value in lexicographical order |
325 | | * (case-sensitive). Items without an Id value sort before items that have |
326 | | * one; two items that both lack an Id compare equal, so the comparator is |
327 | | * a strict weak ordering. |
328 | | */ |
329 | | static int |
330 | 0 | xmlSecTransformRelationshipCompareItems(xmlSecPtr item1, xmlSecPtr item2) { |
331 | 0 | const xmlSecRelationshipListItem* listItem1 = (const xmlSecRelationshipListItem*)item1; |
332 | 0 | const xmlSecRelationshipListItem* listItem2 = (const xmlSecRelationshipListItem*)item2; |
333 | 0 | int ret; |
334 | |
|
335 | 0 | if((listItem1->id == NULL) && (listItem2->id == NULL)) { |
336 | 0 | ret = 0; |
337 | 0 | } else if(listItem1->id == NULL) { |
338 | 0 | ret = -1; |
339 | 0 | } else if(listItem2->id == NULL) { |
340 | 0 | ret = 1; |
341 | 0 | } else { |
342 | 0 | ret = xmlStrcmp(listItem1->id, listItem2->id); |
343 | 0 | } |
344 | 0 | return(ret); |
345 | 0 | } |
346 | | |
347 | | |
348 | | static int |
349 | 0 | xmlSecRelationshipCtxFindSourceId(xmlSecRelationshipCtxPtr ctx, const xmlChar* id) { |
350 | 0 | xmlSecSize ii, size; |
351 | |
|
352 | 0 | xmlSecAssert2(ctx != NULL, -1); |
353 | 0 | xmlSecAssert2(id != NULL, -1); |
354 | | |
355 | 0 | size = xmlSecPtrListGetSize(ctx->sourceIdList); |
356 | 0 | for(ii = 0; ii < size; ++ii) { |
357 | 0 | if(xmlStrcmp((xmlChar *)xmlSecPtrListGetItem(ctx->sourceIdList, ii), id) == 0) { |
358 | 0 | return(1); |
359 | 0 | } |
360 | 0 | } |
361 | | |
362 | 0 | return(0); |
363 | 0 | } |
364 | | |
365 | | static int |
366 | 0 | xmlSecTransformRelationshipIsOutputElement(xmlNodePtr cur) { |
367 | 0 | xmlSecAssert2(cur != NULL, 0); |
368 | | |
369 | 0 | return((cur->ns != NULL) && |
370 | 0 | (cur->ns->href != NULL) && |
371 | 0 | (xmlStrcmp(cur->ns->href, xmlSecRelationshipsNs) == 0)); |
372 | 0 | } |
373 | | |
374 | | static int |
375 | 0 | xmlSecTransformRelationshipIsOutputAttr(xmlAttrPtr attr) { |
376 | 0 | xmlSecAssert2(attr != NULL, 0); |
377 | | |
378 | 0 | return((attr->ns == NULL) || |
379 | 0 | ((attr->ns->href != NULL) && |
380 | 0 | (xmlStrcmp(attr->ns->href, xmlSecRelationshipXmlNs) == 0))); |
381 | 0 | } |
382 | | |
383 | | |
384 | | /* |
385 | | * Step 1 processes versioning instructions with only the Relationships namespace |
386 | | * treated as known, so in non-legacy mode we drop any element outside that |
387 | | * namespace before serialization. Step 2, point 4 then filters surviving |
388 | | * Relationship elements by SourceId. |
389 | | */ |
390 | | static int |
391 | 0 | xmlSecTransformRelationshipProcessNode(xmlSecTransformPtr transform, xmlOutputBufferPtr buf, xmlNodePtr cur, unsigned int depth, xmlSecTransformCtxPtr transformCtx) { |
392 | 0 | xmlSecRelationshipCtxPtr ctx; |
393 | 0 | int ret; |
394 | |
|
395 | 0 | xmlSecAssert2(transform != NULL, -1); |
396 | 0 | xmlSecAssert2(buf != NULL, -1); |
397 | 0 | xmlSecAssert2(cur != NULL, -1); |
398 | 0 | xmlSecAssert2(transformCtx != NULL, -1); |
399 | | |
400 | 0 | ctx = xmlSecRelationshipGetCtx(transform); |
401 | 0 | xmlSecAssert2(ctx != NULL, -1); |
402 | | |
403 | 0 | if(((transformCtx->flags & XMLSEC_TRANSFORMCTX_FLAGS_RELATIONSHIP_LEGACY) == 0) && |
404 | 0 | !xmlSecTransformRelationshipIsOutputElement(cur)) { |
405 | 0 | return(0); |
406 | 0 | } |
407 | | |
408 | 0 | if(xmlSecCheckNodeName(cur, xmlSecNodeRelationship, xmlSecRelationshipsNs)) { |
409 | 0 | xmlChar* id = xmlGetProp(cur, xmlSecRelationshipAttrId); |
410 | 0 | if(id == NULL) { |
411 | | /* xmlGetProp() returns NULL both when the Id attribute is absent |
412 | | * and when it cannot allocate the return value. Current behavior |
413 | | * treats either case as "no usable Id", so per step 2, point 4 |
414 | | * the node is dropped instead of failing the transform. */ |
415 | 0 | return(0); |
416 | 0 | } |
417 | | |
418 | 0 | ret = xmlSecRelationshipCtxFindSourceId(ctx, id); |
419 | 0 | if(ret < 0) { |
420 | 0 | xmlSecInternalError("xmlSecRelationshipCtxFindSourceId", xmlSecTransformGetName(transform)); |
421 | 0 | xmlFree(id); |
422 | 0 | return(-1); |
423 | 0 | } |
424 | 0 | xmlFree(id); |
425 | |
|
426 | 0 | if(ret == 0) { |
427 | | /* Id is not in the sourceId list, so we skip this node */ |
428 | 0 | return(0); |
429 | 0 | } |
430 | 0 | } |
431 | | |
432 | 0 | ret = xmlSecTransformRelationshipProcessElementNode(transform, buf, cur, depth, transformCtx); |
433 | 0 | if(ret < 0) { |
434 | 0 | xmlSecInternalError("xmlSecTransformRelationshipProcessElementNode", xmlSecTransformGetName(transform)); |
435 | 0 | return(-1); |
436 | 0 | } |
437 | | |
438 | | /* done */ |
439 | 0 | return(0); |
440 | 0 | } |
441 | | |
442 | | /* |
443 | | * This is step 2, point 3: sort elements by Id: we process other elements as-is, but for |
444 | | * Relationship elements we collect them in a list, sort the list in place, and finally |
445 | | * process the elements in the sorted order. |
446 | | */ |
447 | | static int |
448 | 0 | xmlSecTransformRelationshipProcessNodeList(xmlSecTransformPtr transform, xmlOutputBufferPtr buf, xmlNodePtr cur, unsigned int depth, xmlSecTransformCtxPtr transformCtx) { |
449 | 0 | xmlSecPtrListPtr list; |
450 | 0 | xmlSecRelationshipListItemPtr item = NULL; |
451 | 0 | xmlSecSize ii; |
452 | 0 | int ret; |
453 | |
|
454 | 0 | xmlSecAssert2(transform != NULL, -1); |
455 | 0 | xmlSecAssert2(buf != NULL, -1); |
456 | 0 | xmlSecAssert2(cur != NULL, -1); |
457 | 0 | xmlSecAssert2(transformCtx != NULL, -1); |
458 | | |
459 | | /* check depth */ |
460 | 0 | if((transformCtx->maxDepth > 0) && (depth > transformCtx->maxDepth)) { |
461 | 0 | xmlSecInternalError2("xmlSecTransformRelationshipProcessNodeList", NULL, |
462 | 0 | "recursion level exceeded: level=%u", depth); |
463 | 0 | return(-1); |
464 | 0 | } |
465 | | |
466 | 0 | list = xmlSecPtrListCreate(&xmlSecRelationshipListItemKlass); |
467 | 0 | if(list == NULL) { |
468 | 0 | xmlSecInternalError("xmlSecPtrListCreate", xmlSecTransformGetName(transform)); |
469 | 0 | return(-1); |
470 | 0 | } |
471 | | |
472 | 0 | for(; cur; cur = cur->next) { |
473 | | /* |
474 | | * Step 3: remove all characters (text nodes) and comments from the Relationships |
475 | | * XML content. Only element nodes are serialized; text/comment/PI nodes are skipped |
476 | | * so they are not written out as spurious <text>/<comment> elements. |
477 | | * |
478 | | * Legacy mode (XMLSEC_TRANSFORMCTX_FLAGS_RELATIONSHIP_LEGACY): restore the old |
479 | | * behaviour and serialize non-element nodes as well. |
480 | | */ |
481 | 0 | if(((transformCtx->flags & XMLSEC_TRANSFORMCTX_FLAGS_RELATIONSHIP_LEGACY) == 0) && |
482 | 0 | (cur->type != XML_ELEMENT_NODE)) { |
483 | 0 | continue; |
484 | 0 | } |
485 | | |
486 | | /* |
487 | | * Step 2: only Relationship elements in the Relationships namespace are sorted and |
488 | | * filtered. Match on name AND namespace so a foreign element that merely shares the |
489 | | * local name "Relationship" is not mistaken for a real relationship entry. |
490 | | */ |
491 | 0 | if(xmlSecCheckNodeName(cur, xmlSecNodeRelationship, xmlSecRelationshipsNs)) { |
492 | 0 | item = xmlSecRelationshipListItemCreate(cur); |
493 | 0 | if(item == NULL) { |
494 | 0 | xmlSecInternalError("xmlSecRelationshipListItemCreate", xmlSecTransformGetName(transform)); |
495 | 0 | xmlSecPtrListDestroy(list); |
496 | 0 | return(-1); |
497 | 0 | } |
498 | 0 | ret = xmlSecPtrListAdd(list, (xmlSecPtr)item); |
499 | 0 | if(ret < 0) { |
500 | 0 | xmlSecInternalError("xmlSecPtrListAdd", xmlSecTransformGetName(transform)); |
501 | 0 | xmlSecRelationshipListItemDestroy((xmlSecPtr)item); |
502 | 0 | xmlSecPtrListDestroy(list); |
503 | 0 | return(-1); |
504 | 0 | } |
505 | 0 | item = NULL; |
506 | 0 | } else { |
507 | 0 | ret = xmlSecTransformRelationshipProcessNode(transform, buf, cur, depth, transformCtx); |
508 | 0 | if(ret < 0) { |
509 | 0 | xmlSecInternalError("xmlSecTransformRelationshipProcessNode", xmlSecTransformGetName(transform)); |
510 | 0 | xmlSecPtrListDestroy(list); |
511 | 0 | return(-1); |
512 | 0 | } |
513 | 0 | } |
514 | 0 | } |
515 | | |
516 | | /* sort the collected elements by their cached Id value */ |
517 | 0 | ret = xmlSecPtrListSort(list, xmlSecTransformRelationshipCompareItems); |
518 | 0 | if(ret < 0) { |
519 | 0 | xmlSecInternalError("xmlSecPtrListSort", xmlSecTransformGetName(transform)); |
520 | 0 | xmlSecPtrListDestroy(list); |
521 | 0 | return(-1); |
522 | 0 | } |
523 | | |
524 | 0 | for(ii = 0; ii < xmlSecPtrListGetSize(list); ++ii) { |
525 | 0 | const xmlSecRelationshipListItem* listItem = |
526 | 0 | (const xmlSecRelationshipListItem*)xmlSecPtrListGetItem(list, ii); |
527 | |
|
528 | 0 | ret = xmlSecTransformRelationshipProcessNode(transform, buf, listItem->node, depth, transformCtx); |
529 | 0 | if(ret < 0) { |
530 | 0 | xmlSecInternalError("xmlSecTransformRelationshipProcessNode", xmlSecTransformGetName(transform)); |
531 | 0 | xmlSecPtrListDestroy(list); |
532 | 0 | return(-1); |
533 | 0 | } |
534 | 0 | } |
535 | | |
536 | | /* done */ |
537 | 0 | xmlSecPtrListDestroy(list); |
538 | 0 | return(0); |
539 | 0 | } |
540 | | |
541 | | /* |
542 | | * Writes an attribute value, escaping the XML special characters (&, <, >, ") so that the |
543 | | * generated document stays well-formed. Without this, a Target value containing e.g. '&' or '"' |
544 | | * would produce invalid XML that fails to re-parse ("EntityRef: expecting ';'"). |
545 | | */ |
546 | | static int |
547 | 0 | xmlSecTransformRelationshipWriteEscapedValue(xmlOutputBufferPtr buf, const xmlChar* value) { |
548 | 0 | xmlChar* escaped; |
549 | 0 | xmlChar* dst; |
550 | 0 | const xmlChar* src; |
551 | 0 | size_t len; |
552 | 0 | int ret; |
553 | |
|
554 | 0 | xmlSecAssert2(buf != NULL, -1); |
555 | 0 | xmlSecAssert2(value != NULL, -1); |
556 | | |
557 | | /* Worst case every character expands to """ (6 bytes). */ |
558 | 0 | len = strlen((const char*)value); |
559 | 0 | if(len > ((SIZE_MAX - 1U) / 6U)) { |
560 | 0 | xmlSecInvalidSizeError("value", len, ((SIZE_MAX - 1U) / 6U), NULL); |
561 | 0 | return(-1); |
562 | 0 | } |
563 | 0 | escaped = xmlMalloc(len * 6 + 1); |
564 | 0 | if(escaped == NULL) { |
565 | 0 | xmlSecXmlError("xmlMalloc", NULL); |
566 | 0 | return(-1); |
567 | 0 | } |
568 | | |
569 | 0 | dst = escaped; |
570 | 0 | for(src = value; *src != '\0'; ++src) { |
571 | 0 | switch(*src) { |
572 | 0 | case '&': |
573 | 0 | memcpy(dst, "&", 5); |
574 | 0 | dst += 5; |
575 | 0 | break; |
576 | 0 | case '<': |
577 | 0 | memcpy(dst, "<", 4); |
578 | 0 | dst += 4; |
579 | 0 | break; |
580 | 0 | case '>': |
581 | 0 | memcpy(dst, ">", 4); |
582 | 0 | dst += 4; |
583 | 0 | break; |
584 | 0 | case '"': |
585 | 0 | memcpy(dst, """, 6); |
586 | 0 | dst += 6; |
587 | 0 | break; |
588 | 0 | default: |
589 | 0 | *dst++ = *src; |
590 | 0 | break; |
591 | 0 | } |
592 | 0 | } |
593 | 0 | *dst = '\0'; |
594 | |
|
595 | 0 | ret = xmlOutputBufferWriteString(buf, (const char*)escaped); |
596 | 0 | xmlFree(escaped); |
597 | 0 | return(ret); |
598 | 0 | } |
599 | | |
600 | | static int |
601 | 0 | xmlSecTransformRelationshipWriteProp(xmlOutputBufferPtr buf, const xmlChar * name, const xmlChar * value) { |
602 | 0 | int ret; |
603 | |
|
604 | 0 | xmlSecAssert2(buf != NULL, -1); |
605 | 0 | xmlSecAssert2(name != NULL, -1); |
606 | | |
607 | 0 | ret = xmlOutputBufferWriteString(buf, " "); |
608 | 0 | if(ret < 0) { |
609 | 0 | xmlSecXmlError("xmlOutputBufferWriteString", NULL); |
610 | 0 | return(-1); |
611 | 0 | } |
612 | | |
613 | 0 | ret = xmlOutputBufferWriteString(buf, (const char*) name); |
614 | 0 | if(ret < 0) { |
615 | 0 | xmlSecXmlError("xmlOutputBufferWriteString", NULL); |
616 | 0 | return(-1); |
617 | 0 | } |
618 | | |
619 | 0 | if(value != NULL) { |
620 | 0 | ret = xmlOutputBufferWriteString(buf, "=\""); |
621 | 0 | if(ret < 0) { |
622 | 0 | xmlSecXmlError("xmlOutputBufferWriteString", NULL); |
623 | 0 | return(-1); |
624 | 0 | } |
625 | 0 | ret = xmlSecTransformRelationshipWriteEscapedValue(buf, value); |
626 | 0 | if(ret < 0) { |
627 | 0 | xmlSecXmlError("xmlSecTransformRelationshipWriteEscapedValue", NULL); |
628 | 0 | return(-1); |
629 | 0 | } |
630 | 0 | ret = xmlOutputBufferWriteString(buf, "\""); |
631 | 0 | if(ret < 0) { |
632 | 0 | xmlSecXmlError("xmlOutputBufferWriteString", NULL); |
633 | 0 | return(-1); |
634 | 0 | } |
635 | 0 | } |
636 | | |
637 | 0 | return (0); |
638 | 0 | } |
639 | | |
640 | | /* |
641 | | * Writes a single attribute to the output buffer using the attribute itself as |
642 | | * the value source. In non-legacy mode the caller only passes attributes that |
643 | | * survive Step 1 / Step 2 filtering, which means unprefixed attributes plus |
644 | | * built-in xml:* attributes. |
645 | | */ |
646 | | static int |
647 | 0 | xmlSecTransformRelationshipWriteAttribute(xmlSecTransformPtr transform, xmlOutputBufferPtr buf, xmlAttrPtr attr) { |
648 | 0 | xmlChar * value = NULL; |
649 | 0 | xmlChar * nameCopy = NULL; |
650 | 0 | const xmlChar * attrName; |
651 | 0 | size_t prefixLen; |
652 | 0 | size_t nameLen; |
653 | 0 | int ret; |
654 | |
|
655 | 0 | xmlSecAssert2(transform != NULL, -1); |
656 | 0 | xmlSecAssert2(buf != NULL, -1); |
657 | 0 | xmlSecAssert2(attr != NULL, -1); |
658 | | |
659 | | /* get correct attribute name */ |
660 | 0 | if((attr->ns != NULL) && (attr->ns->prefix != NULL)) { |
661 | 0 | prefixLen = strlen((const char*)attr->ns->prefix); |
662 | 0 | nameLen = strlen((const char*)attr->name); |
663 | 0 | nameCopy = xmlMalloc(prefixLen + nameLen + 2); |
664 | 0 | if(nameCopy == NULL) { |
665 | 0 | xmlSecXmlError("xmlMalloc", NULL); |
666 | 0 | return(-1); |
667 | 0 | } |
668 | 0 | memcpy(nameCopy, attr->ns->prefix, prefixLen); |
669 | 0 | nameCopy[prefixLen] = ':'; |
670 | 0 | memcpy(nameCopy + prefixLen + 1, attr->name, nameLen); |
671 | 0 | nameCopy[prefixLen + nameLen + 1] = '\0'; |
672 | 0 | attrName = nameCopy; |
673 | 0 | } else { |
674 | 0 | attrName = attr->name; |
675 | 0 | } |
676 | | |
677 | | /* |
678 | | * Get the attribute value as raw (unescaped) text. The third argument to |
679 | | * xmlNodeListGetString() is the "inLine" flag: when non-zero it returns the |
680 | | * unescaped content, which xmlSecTransformRelationshipWriteProp() then |
681 | | * escapes exactly once. An attribute without child nodes has an empty value |
682 | | * (e.g. Target=""), so skip the call and serialize an empty string instead |
683 | | * of failing. |
684 | | */ |
685 | 0 | if(attr->children != NULL) { |
686 | 0 | value = xmlNodeListGetString(attr->doc, attr->children, 1); |
687 | 0 | if(value == NULL) { |
688 | 0 | xmlSecXmlError("xmlNodeListGetString", xmlSecTransformGetName(transform)); |
689 | 0 | xmlFree(nameCopy); |
690 | 0 | return(-1); |
691 | 0 | } |
692 | 0 | } |
693 | | |
694 | | /* finally write the attribute to the output; an attribute without child |
695 | | * nodes has an empty value, e.g. Target="" */ |
696 | 0 | ret = xmlSecTransformRelationshipWriteProp(buf, attrName, (value != NULL) ? value : BAD_CAST ""); |
697 | 0 | if(ret < 0) { |
698 | 0 | xmlSecInternalError("xmlSecTransformRelationshipWriteProp", xmlSecTransformGetName(transform)); |
699 | 0 | xmlFree(nameCopy); |
700 | 0 | if(value != NULL) { |
701 | 0 | xmlFree(value); |
702 | 0 | } |
703 | 0 | return(-1); |
704 | 0 | } |
705 | | |
706 | 0 | xmlFree(nameCopy); |
707 | 0 | if(value != NULL) { |
708 | 0 | xmlFree(value); |
709 | 0 | } |
710 | 0 | return(0); |
711 | 0 | } |
712 | | |
713 | | /* |
714 | | * Write children. |
715 | | * |
716 | | * Step 3, point 2: remove any contents of the Relationship element. In |
717 | | * non-legacy mode a Relationship element is written with no content even |
718 | | * if the (non-conformant) input has content inside it; legacy mode keeps |
719 | | * the old behaviour of serializing the children. |
720 | | */ |
721 | | static int |
722 | | xmlSecTransformRelationshipProcessElementNodeChildren( |
723 | | xmlSecTransformPtr transform, |
724 | | xmlOutputBufferPtr buf, |
725 | | xmlNodePtr cur, |
726 | | unsigned int depth, |
727 | | xmlSecTransformCtxPtr transformCtx |
728 | 0 | ) { |
729 | 0 | int ret; |
730 | |
|
731 | 0 | xmlSecAssert2(transform != NULL, -1); |
732 | 0 | xmlSecAssert2(buf != NULL, -1); |
733 | 0 | xmlSecAssert2(cur != NULL, -1); |
734 | 0 | xmlSecAssert2(cur->name != NULL, -1); |
735 | 0 | xmlSecAssert2(transformCtx != NULL, -1); |
736 | | |
737 | | /* do nothing if there are no children */ |
738 | 0 | if(cur->children == NULL) { |
739 | 0 | return(0); |
740 | 0 | } |
741 | | |
742 | | /* in non-legacy mode, skip writing children for the Relationship element */ |
743 | 0 | if( |
744 | 0 | ((transformCtx->flags & XMLSEC_TRANSFORMCTX_FLAGS_RELATIONSHIP_LEGACY) == 0) && |
745 | 0 | (xmlSecCheckNodeName(cur, xmlSecNodeRelationship, xmlSecRelationshipsNs) == 1) |
746 | 0 | ) { |
747 | 0 | return(0); |
748 | 0 | } |
749 | | |
750 | | /* process the list of child nodes */ |
751 | 0 | ret = xmlSecTransformRelationshipProcessNodeList(transform, buf, cur->children, depth + 1, transformCtx); |
752 | 0 | if(ret < 0) { |
753 | 0 | xmlSecInternalError("xmlSecTransformRelationshipProcessNodeList", xmlSecTransformGetName(transform)); |
754 | 0 | return(-1); |
755 | 0 | } |
756 | | |
757 | 0 | return(0); |
758 | 0 | } |
759 | | |
760 | | static int |
761 | 0 | xmlSecTransformRelationshipProcessElementNode(xmlSecTransformPtr transform, xmlOutputBufferPtr buf, xmlNodePtr cur, unsigned int depth, xmlSecTransformCtxPtr transformCtx) { |
762 | 0 | xmlAttrPtr attr; |
763 | 0 | xmlNsPtr ns; |
764 | 0 | int foundTargetMode = 0; |
765 | 0 | int relNsWritten = 0; |
766 | 0 | int ret; |
767 | |
|
768 | 0 | xmlSecAssert2(transform != NULL, -1); |
769 | 0 | xmlSecAssert2(buf != NULL, -1); |
770 | 0 | xmlSecAssert2(cur != NULL, -1); |
771 | 0 | xmlSecAssert2(cur->name != NULL, -1); |
772 | 0 | xmlSecAssert2(transformCtx != NULL, -1); |
773 | | |
774 | | /* write open node */ |
775 | 0 | ret = xmlOutputBufferWriteString(buf, "<"); |
776 | 0 | if(ret < 0) { |
777 | 0 | xmlSecXmlError("xmlOutputBufferWriteString", |
778 | 0 | xmlSecTransformGetName(transform)); |
779 | 0 | return(-1); |
780 | 0 | } |
781 | 0 | ret = xmlOutputBufferWriteString(buf, (const char *)cur->name); |
782 | 0 | if(ret < 0) { |
783 | 0 | xmlSecXmlError("xmlOutputBufferWriteString", |
784 | 0 | xmlSecTransformGetName(transform)); |
785 | 0 | return(-1); |
786 | 0 | } |
787 | | |
788 | | /* |
789 | | * Write namespaces. |
790 | | * |
791 | | * Step 2, point 1: remove all namespace declarations except the Relationships namespace |
792 | | * declaration. Step 1 has already filtered out versioning instructions and |
793 | | * foreign-namespaced content from the serialized output, so we emit only the |
794 | | * namespace binding for Relationships itself here. Foreign namespace |
795 | | * declarations (e.g. xmlns:foo="...") are dropped so they do not leak into |
796 | | * the canonical output. |
797 | | * |
798 | | * Step 2, point 2: remove the Relationships namespace prefix, if it is present. Element |
799 | | * names are written bare, so the Relationships namespace is bound as the default |
800 | | * namespace (xmlns="...") for them to resolve into it. |
801 | | * |
802 | | * Legacy mode (XMLSEC_TRANSFORMCTX_FLAGS_RELATIONSHIP_LEGACY): restore the old behaviour |
803 | | * of writing a single unprefixed xmlns="..." declaration for the first declared namespace, |
804 | | * regardless of whether it is the Relationships namespace. |
805 | | */ |
806 | 0 | if((transformCtx->flags & XMLSEC_TRANSFORMCTX_FLAGS_RELATIONSHIP_LEGACY) != 0) { |
807 | 0 | if(cur->nsDef != NULL) { |
808 | 0 | ret = xmlSecTransformRelationshipWriteProp(buf, BAD_CAST "xmlns", |
809 | 0 | (cur->nsDef->href != NULL) ? cur->nsDef->href : BAD_CAST ""); |
810 | 0 | if(ret < 0) { |
811 | 0 | xmlSecInternalError("xmlSecTransformRelationshipWriteProp(xmlns)", |
812 | 0 | xmlSecTransformGetName(transform)); |
813 | 0 | return(-1); |
814 | 0 | } |
815 | 0 | } |
816 | 0 | } else { |
817 | 0 | for(ns = cur->nsDef; ns != NULL; ns = ns->next) { |
818 | 0 | if((ns->href == NULL) || (xmlStrcmp(ns->href, xmlSecRelationshipsNs) != 0)) { |
819 | 0 | continue; |
820 | 0 | } |
821 | 0 | if(relNsWritten) { |
822 | | /* The Relationships namespace is already emitted as the default |
823 | | * namespace; skip any further declarations bound to it. */ |
824 | 0 | continue; |
825 | 0 | } |
826 | 0 | ret = xmlSecTransformRelationshipWriteProp(buf, BAD_CAST "xmlns", ns->href); |
827 | 0 | if(ret < 0) { |
828 | 0 | xmlSecInternalError("xmlSecTransformRelationshipWriteProp(xmlns)", |
829 | 0 | xmlSecTransformGetName(transform)); |
830 | 0 | return(-1); |
831 | 0 | } |
832 | 0 | relNsWritten = 1; |
833 | 0 | } |
834 | 0 | } |
835 | | |
836 | | /* |
837 | | * Write attributes. |
838 | | * |
839 | | * This is step 3, point 6: add default value of TargetMode if there is no such attribute. |
840 | | * Only the unprefixed TargetMode attribute counts as present; a namespaced one must not |
841 | | * suppress the default. |
842 | | */ |
843 | 0 | for(attr = cur->properties; attr != NULL; attr = attr->next) { |
844 | 0 | if((attr->ns == NULL) && (xmlStrcmp(attr->name, xmlSecRelationshipAttrTargetMode) == 0)) { |
845 | 0 | foundTargetMode = 1; |
846 | 0 | } |
847 | |
|
848 | 0 | if(((transformCtx->flags & XMLSEC_TRANSFORMCTX_FLAGS_RELATIONSHIP_LEGACY) == 0) && |
849 | 0 | !xmlSecTransformRelationshipIsOutputAttr(attr)) { |
850 | 0 | continue; |
851 | 0 | } |
852 | | |
853 | 0 | ret = xmlSecTransformRelationshipWriteAttribute(transform, buf, attr); |
854 | 0 | if(ret < 0) { |
855 | 0 | xmlSecInternalError("xmlSecTransformRelationshipWriteAttribute", |
856 | 0 | xmlSecTransformGetName(transform)); |
857 | 0 | return(-1); |
858 | 0 | } |
859 | 0 | } |
860 | | |
861 | | /* |
862 | | * Step 3, point 6: add a TargetMode attribute with its default value ("Internal") if this |
863 | | * optional attribute is missing. Only apply to real Relationship elements (Relationships |
864 | | * namespace), not to foreign elements that merely share the local name. |
865 | | */ |
866 | 0 | if(xmlSecCheckNodeName(cur, xmlSecNodeRelationship, xmlSecRelationshipsNs) && !foundTargetMode) { |
867 | 0 | ret = xmlSecTransformRelationshipWriteProp(buf, xmlSecRelationshipAttrTargetMode, BAD_CAST "Internal"); |
868 | 0 | if(ret < 0) { |
869 | 0 | xmlSecInternalError("xmlSecTransformRelationshipWriteProp(TargetMode=Internal)", xmlSecTransformGetName(transform)); |
870 | 0 | return(-1); |
871 | 0 | } |
872 | 0 | } |
873 | | |
874 | | /* finish writing open node */ |
875 | 0 | ret = xmlOutputBufferWriteString(buf, ">"); |
876 | 0 | if(ret < 0) { |
877 | 0 | xmlSecXmlError("xmlOutputBufferWriteString", xmlSecTransformGetName(transform)); |
878 | 0 | return(-1); |
879 | 0 | } |
880 | | |
881 | | /* |
882 | | * Write children. |
883 | | */ |
884 | 0 | ret = xmlSecTransformRelationshipProcessElementNodeChildren(transform, buf, cur, depth, transformCtx); |
885 | 0 | if(ret < 0) { |
886 | 0 | xmlSecInternalError("xmlSecTransformRelationshipProcessElementNodeChildren", xmlSecTransformGetName(transform)); |
887 | 0 | return(-1); |
888 | 0 | } |
889 | | |
890 | | /* write closing node */ |
891 | 0 | ret = xmlOutputBufferWriteString(buf, "</"); |
892 | 0 | if(ret < 0) { |
893 | 0 | xmlSecXmlError("xmlOutputBufferWriteString", xmlSecTransformGetName(transform)); |
894 | 0 | return(-1); |
895 | 0 | } |
896 | 0 | ret = xmlOutputBufferWriteString(buf, (const char *)cur->name); |
897 | 0 | if(ret < 0) { |
898 | 0 | xmlSecXmlError("xmlOutputBufferWriteString", xmlSecTransformGetName(transform)); |
899 | 0 | return(-1); |
900 | 0 | } |
901 | 0 | if(xmlOutputBufferWriteString(buf, ">") < 0) { |
902 | 0 | xmlSecXmlError("xmlOutputBufferWriteString", xmlSecTransformGetName(transform)); |
903 | 0 | return(-1); |
904 | 0 | } |
905 | | |
906 | | /* done */ |
907 | 0 | return(0); |
908 | 0 | } |
909 | | |
910 | | static int |
911 | 0 | xmlSecTransformRelationshipExecute(xmlSecTransformPtr transform, xmlOutputBufferPtr buf, xmlDocPtr doc, xmlSecTransformCtxPtr transformCtx) { |
912 | 0 | int ret; |
913 | |
|
914 | 0 | xmlSecAssert2(transform != NULL, -1); |
915 | 0 | xmlSecAssert2(buf != NULL, -1); |
916 | 0 | xmlSecAssert2(doc != NULL, -1); |
917 | 0 | xmlSecAssert2(transformCtx != NULL, -1); |
918 | | |
919 | | /* |
920 | | * In non-legacy mode the top-level content must be in the Relationships namespace; |
921 | | * otherwise the whole document would be silently dropped and the transform would |
922 | | * produce empty output. Fail explicitly instead. |
923 | | */ |
924 | 0 | if((transformCtx->flags & XMLSEC_TRANSFORMCTX_FLAGS_RELATIONSHIP_LEGACY) == 0) { |
925 | 0 | xmlNodePtr top; |
926 | 0 | for(top = doc->children; top != NULL; top = top->next) { |
927 | 0 | if((top->type == XML_ELEMENT_NODE) && |
928 | 0 | !xmlSecTransformRelationshipIsOutputElement(top)) { |
929 | 0 | xmlSecInvalidNodeContentError(top, xmlSecTransformGetName(transform), |
930 | 0 | "top-level node is not in the Relationships namespace"); |
931 | 0 | return(-1); |
932 | 0 | } |
933 | 0 | } |
934 | 0 | } |
935 | | |
936 | 0 | if(doc->children != NULL) { |
937 | 0 | ret = xmlSecTransformRelationshipProcessNodeList(transform, buf, doc->children, 0, transformCtx); |
938 | 0 | if(ret < 0) { |
939 | 0 | xmlSecInternalError("xmlSecTransformRelationshipProcessNodeList", xmlSecTransformGetName(transform)); |
940 | 0 | return(-1); |
941 | 0 | } |
942 | 0 | } |
943 | | |
944 | 0 | return(0); |
945 | 0 | } |
946 | | |
947 | | static int |
948 | | xmlSecTransformRelationshipPushXml(xmlSecTransformPtr transform, xmlSecNodeSetPtr nodes, xmlSecTransformCtxPtr transformCtx) |
949 | 0 | { |
950 | 0 | xmlOutputBufferPtr buf; |
951 | 0 | xmlSecRelationshipCtxPtr ctx; |
952 | 0 | int ret; |
953 | |
|
954 | 0 | xmlSecAssert2(transform != NULL, -1); |
955 | 0 | xmlSecAssert2(nodes != NULL, -1); |
956 | 0 | xmlSecAssert2(nodes->doc != NULL, -1); |
957 | 0 | xmlSecAssert2(transformCtx != NULL, -1); |
958 | | |
959 | 0 | ctx = xmlSecRelationshipGetCtx(transform); |
960 | 0 | xmlSecAssert2(ctx != NULL, -1); |
961 | | |
962 | | /* check/update current transform status */ |
963 | 0 | switch(transform->status) { |
964 | 0 | case xmlSecTransformStatusNone: |
965 | 0 | transform->status = xmlSecTransformStatusWorking; |
966 | 0 | break; |
967 | 0 | case xmlSecTransformStatusWorking: |
968 | 0 | case xmlSecTransformStatusFinished: |
969 | 0 | return(0); |
970 | 0 | default: |
971 | 0 | xmlSecInvalidTransformStatusError(transform); |
972 | 0 | return(-1); |
973 | 0 | } |
974 | 0 | xmlSecAssert2(transform->status == xmlSecTransformStatusWorking, -1); |
975 | | |
976 | | /* prepare output buffer: next transform or ourselves */ |
977 | 0 | if(transform->next != NULL) { |
978 | 0 | buf = xmlSecTransformCreateOutputBuffer(transform->next, transformCtx); |
979 | 0 | if(buf == NULL) { |
980 | 0 | xmlSecInternalError("xmlSecTransformCreateOutputBuffer", xmlSecTransformGetName(transform)); |
981 | 0 | transform->status = xmlSecTransformStatusFail; |
982 | 0 | return(-1); |
983 | 0 | } |
984 | 0 | } else { |
985 | 0 | buf = xmlSecBufferCreateOutputBuffer(&(transform->outBuf)); |
986 | 0 | if(buf == NULL) { |
987 | 0 | xmlSecInternalError("xmlSecBufferCreateOutputBuffer", xmlSecTransformGetName(transform)); |
988 | 0 | transform->status = xmlSecTransformStatusFail; |
989 | 0 | return(-1); |
990 | 0 | } |
991 | 0 | } |
992 | | |
993 | 0 | ret = xmlSecTransformRelationshipExecute(transform, buf, nodes->doc, transformCtx); |
994 | 0 | if(ret < 0) { |
995 | 0 | xmlSecInternalError("xmlSecTransformRelationshipExecute", xmlSecTransformGetName(transform)); |
996 | | /* the buffer must be closed to release it (the IO buffer is only |
997 | | * freed by its close callback). The close pushes the partial data |
998 | | * into the next transform, which is harmless here: the whole |
999 | | * operation is aborted and the produced data is discarded */ |
1000 | 0 | (void)xmlOutputBufferClose(buf); |
1001 | | /* discard any partial output produced before the failure so it |
1002 | | * cannot be picked up by a later operation and mark the transform as failed */ |
1003 | 0 | xmlSecBufferEmpty(&(transform->outBuf)); |
1004 | 0 | transform->status = xmlSecTransformStatusFail; |
1005 | 0 | return(-1); |
1006 | 0 | } |
1007 | | |
1008 | 0 | ret = xmlOutputBufferClose(buf); |
1009 | 0 | if(ret < 0) { |
1010 | 0 | xmlSecXmlError("xmlOutputBufferClose", xmlSecTransformGetName(transform)); |
1011 | 0 | transform->status = xmlSecTransformStatusFail; |
1012 | 0 | return(-1); |
1013 | 0 | } |
1014 | 0 | transform->status = xmlSecTransformStatusFinished; |
1015 | 0 | return(0); |
1016 | 0 | } |
1017 | | |
1018 | | static int |
1019 | 0 | xmlSecTransformRelationshipPopBin(xmlSecTransformPtr transform, xmlSecByte* data, xmlSecSize maxDataSize, xmlSecSize* dataSize, xmlSecTransformCtxPtr transformCtx) { |
1020 | | /* |
1021 | | * Intentionally unimplemented. The xmlsec1 sign/verify and encrypt/decrypt flows drive |
1022 | | * the transform chain in push mode only: a parser is inserted before this transform (to |
1023 | | * turn the input bytes into XML) and its PushXml serializes the result and writes it |
1024 | | * straight into the following transform, so the pull-style PopBin entry point is never |
1025 | | * reached. The popBin method pointer is kept set solely so that data-type negotiation |
1026 | | * (xmlSecTransformConnect) still reports a "Bin" output and inserts the required parser |
1027 | | * after this transform; if it is ever invoked we fail loudly rather than produce output. |
1028 | | */ |
1029 | 0 | (void)transform; |
1030 | 0 | (void)data; |
1031 | 0 | (void)maxDataSize; |
1032 | 0 | (void)dataSize; |
1033 | 0 | (void)transformCtx; |
1034 | | xmlSecNotImplementedError("xmlSecTransformRelationshipPopBin"); |
1035 | 0 | return(-1); |
1036 | 0 | } |