|
22 | 22 | import java.util.Map;
|
23 | 23 | import org.slf4j.Logger;
|
24 | 24 | import org.slf4j.LoggerFactory;
|
| 25 | +import org.w3c.dom.Document; |
| 26 | +import org.w3c.dom.Element; |
| 27 | +import org.w3c.dom.NamedNodeMap; |
| 28 | +import org.w3c.dom.Node; |
| 29 | +import org.w3c.dom.NodeList; |
25 | 30 |
|
26 | 31 | public final class ObjectIntrospection {
|
27 | 32 |
|
@@ -197,6 +202,17 @@ private static Object doConversion(Object obj, int depth, State state) {
|
197 | 202 | }
|
198 | 203 | }
|
199 | 204 |
|
| 205 | + // XML DOM nodes (W3C DOM API) |
| 206 | + if (obj instanceof Document || obj instanceof Element) { |
| 207 | + try { |
| 208 | + return doConversionXmlDom(obj, depth, state); |
| 209 | + } catch (Throwable e) { |
| 210 | + // in case of failure let default conversion run |
| 211 | + log.debug("Error handling xml dom node {}", clazz, e); |
| 212 | + return null; |
| 213 | + } |
| 214 | + } |
| 215 | + |
200 | 216 | // maps
|
201 | 217 | if (obj instanceof Map) {
|
202 | 218 | Map<Object, Object> newMap = new HashMap<>((int) Math.ceil(((Map) obj).size() / .75));
|
@@ -424,6 +440,104 @@ private static Object doConversionJacksonNode(
|
424 | 440 | }
|
425 | 441 | }
|
426 | 442 |
|
| 443 | + /** |
| 444 | + * Converts XML DOM objects to WAF-compatible data structures. |
| 445 | + * |
| 446 | + * <p>XML DOM objects ({@link org.w3c.dom.Document} and {@link org.w3c.dom.Element}) are converted |
| 447 | + * to appropriate data types for WAF analysis. This method handles the conversion of XML structure |
| 448 | + * to Map/List format similar to JSON handling. |
| 449 | + * |
| 450 | + * <p>Supported XML DOM types and their conversions: |
| 451 | + * |
| 452 | + * <ul> |
| 453 | + * <li>{@code Document} - Converted to {@link HashMap} with root element children as keys |
| 454 | + * <li>{@code Element} - Converted to {@link HashMap} with attributes and child elements |
| 455 | + * <li>Attributes are preserved as key-value pairs in the element map |
| 456 | + * <li>Text content is stored under the "_text" key |
| 457 | + * <li>Child elements are recursively converted and stored by tag name |
| 458 | + * </ul> |
| 459 | + * |
| 460 | + * <p>The method applies the same truncation limits as the main conversion logic. |
| 461 | + */ |
| 462 | + private static Object doConversionXmlDom(Object obj, int depth, State state) { |
| 463 | + if (obj == null) { |
| 464 | + return null; |
| 465 | + } |
| 466 | + state.elemsLeft--; |
| 467 | + if (state.elemsLeft <= 0) { |
| 468 | + state.listMapTooLarge = true; |
| 469 | + return null; |
| 470 | + } |
| 471 | + if (depth > MAX_DEPTH) { |
| 472 | + state.objectTooDeep = true; |
| 473 | + return null; |
| 474 | + } |
| 475 | + |
| 476 | + if (obj instanceof Document) { |
| 477 | + Document doc = (Document) obj; |
| 478 | + Element rootElement = doc.getDocumentElement(); |
| 479 | + if (rootElement != null) { |
| 480 | + return doConversionXmlDom(rootElement, depth + 1, state); |
| 481 | + } |
| 482 | + return new HashMap<>(); |
| 483 | + } else if (obj instanceof Element) { |
| 484 | + Element elem = (Element) obj; |
| 485 | + Map<String, Object> newMap = new HashMap<>(); |
| 486 | + |
| 487 | + // Add attributes |
| 488 | + NamedNodeMap attributes = elem.getAttributes(); |
| 489 | + for (int i = 0; i < attributes.getLength(); i++) { |
| 490 | + Node attr = attributes.item(i); |
| 491 | + String attrName = attr.getNodeName(); |
| 492 | + String attrValue = attr.getNodeValue(); |
| 493 | + if (attrValue != null) { |
| 494 | + newMap.put(attrName, checkStringLength(attrValue, state)); |
| 495 | + } |
| 496 | + } |
| 497 | + |
| 498 | + // Process child nodes |
| 499 | + NodeList nodeList = elem.getChildNodes(); |
| 500 | + StringBuilder textContent = new StringBuilder(); |
| 501 | + Map<String, List<Object>> childElements = new HashMap<>(); |
| 502 | + |
| 503 | + for (int i = 0; i < nodeList.getLength(); i++) { |
| 504 | + Node node = nodeList.item(i); |
| 505 | + if (node instanceof Element) { |
| 506 | + Element childElem = (Element) node; |
| 507 | + String tagName = childElem.getTagName(); |
| 508 | + Object childValue = guardedConversion(childElem, depth + 1, state); |
| 509 | + |
| 510 | + // Handle multiple elements with same tag name |
| 511 | + childElements.computeIfAbsent(tagName, k -> new ArrayList<>()).add(childValue); |
| 512 | + } else if (node instanceof org.w3c.dom.Text) { |
| 513 | + String text = node.getNodeValue(); |
| 514 | + if (text != null && !text.trim().isEmpty()) { |
| 515 | + textContent.append(text.trim()); |
| 516 | + } |
| 517 | + } |
| 518 | + } |
| 519 | + |
| 520 | + // Add child elements to map |
| 521 | + for (Map.Entry<String, List<Object>> entry : childElements.entrySet()) { |
| 522 | + List<Object> values = entry.getValue(); |
| 523 | + if (values.size() == 1) { |
| 524 | + newMap.put(entry.getKey(), values.get(0)); |
| 525 | + } else { |
| 526 | + newMap.put(entry.getKey(), values); |
| 527 | + } |
| 528 | + } |
| 529 | + |
| 530 | + // Add text content if present |
| 531 | + if (textContent.length() > 0) { |
| 532 | + newMap.put("_text", checkStringLength(textContent.toString(), state)); |
| 533 | + } |
| 534 | + |
| 535 | + return newMap; |
| 536 | + } |
| 537 | + |
| 538 | + return null; |
| 539 | + } |
| 540 | + |
427 | 541 | /**
|
428 | 542 | * Context class used to cache method resolutions while converting a top level json node class.
|
429 | 543 | */
|
|
0 commit comments