Repository: CycloneDX/cyclonedx-rust-cargo (language: rust)

Issue:
XML deserialization of empty tags is incorrect
As a working example, I am going to reference the following area of code, but I believe it is possible to run into this in any area that the library is parsing an `Option<String>` field: https://github.com/CycloneDX/cyclonedx-rust-cargo/blob/2911287b2520a7ddab1782b48c35112279b1be17/cyclonedx-bom/src/specs/common/component.rs#L513-L517

Setting context, I was attempting to swap out an ad-hoc CycloneDX parser with `cyclonedx-bom`. One of the tests for my ad-hoc parser was to ensure that the keycloak BOM found here loads: https://github.com/CycloneDX/bom-examples/blob/c0436d86cd60693f01d19fe1aacfd01e70e17036/SBOM/keycloak-10.0.2/bom.xml. Now, it obviously doesn't because it is a v1.2 spec and `cyclonedx-bom` only supports v1.3-v1.5. But, looking through the spec and looking at the BOM, it seemed like it should load as a v1.3, so I changed the version locally. That seemed a reasonable test given that a diff between https://github.com/CycloneDX/bom-examples/blob/c0436d86cd60693f01d19fe1aacfd01e70e17036/SBOM/laravel-7.12.0/bom.1.2.xml and https://github.com/CycloneDX/bom-examples/blob/c0436d86cd60693f01d19fe1aacfd01e70e17036/SBOM/laravel-7.12.0/bom.1.3.xml only added some properties and changed the spec version. 

However, when I attempted to load the modified keycloak BOM, I got the following error:

```
    Got unexpected XML element when reading {http://cyclonedx.org/schema/bom/1.3}description: Got unexpected element EndElement({http://cyclonedx.org/schema/bom/1.3}description)
```

After digging into the issue, what I discovered was that the keycloak BOM has several empty description tags in it:

```xml
<description />
```

A quick modification of the `laravel-7.12.0/bom.1.3.xml` file to modify one of the description tags the same way caused the same error to occur. I also attempted to just modify one of the description like the following and received the same error:

```xml
<description></description>
```

The [v1.3 (XML)](https://cyclonedx.org/docs/1.3/xml/#type_component) and [v1.2 (XML)](https://cyclonedx.org/docs/1.2/xml/#type_component) specs 
both define the description field as `<bom:description> xs:normalizedString </bom:description> [0..1]` and an `xs:normalizedString` _can_ be empty.

Again, there are multiple places just in the `Component::read_xml_element` where this could be an issue. My guess is that, since the `pom.xml` file that is used to generate the CycloneDX Maven BOMs can have empty strings for the description, that is why it is getting forwarded on to the BOM that way instead of stripping out the description tag completely. 

I could make an argument for handling it either by making an empty string become a `None` for the `Option<String>` OR by literally making it an `Some(String::new())`. I think that the latter is closer to the behavior that would happen with the JSON implementation of the spec, since an equivalent there would be:

```json
"description": ""
```

and that would be parsed as `Some(String::new())` by `serde`.




Discussion / hints:
Thank you for reporting this!

Treating this as `Some(String::new())` for consistency with JSON sounds good to me. I'm not familiar with XML parsing myself, but I'd be happy to merge a PR with this change.
Hello,

I asked on the CycloneDX Slack to clarify how to handle the `description` tag. It's not totally clear to me if `<description />` is a valid entry or if it needs to be either absent or contain text. I'm uncertain, because the XML sample files in the [specification](https://github.com/CycloneDX/specification/) repository don't contain any empty `<description />` tag. We used these sample files as test data for this repository, but they may not cover all cases.
**Update**: The answer on Slack was the `<specification />` tag is valid, it should result in an empty string. The current XML parse logic is therefore wrong, at least for these types of fields.

Fix patch:
diff --git a/cyclonedx-bom/src/xml.rs b/cyclonedx-bom/src/xml.rs
index 20541279..2cd8f193 100644
--- a/cyclonedx-bom/src/xml.rs
+++ b/cyclonedx-bom/src/xml.rs
@@ -442,6 +442,17 @@ impl FromXmlType for f32 {
     }
 }
 
+/// Reads a simple String tag.
+///
+/// ```xml
+/// <description>Content</description>
+/// ```
+/// &
+/// ```xml
+/// <description />
+/// ```
+///
+/// are valid XML tags. The first returns the string "Content", the latter is an empty string.
 pub(crate) fn read_simple_tag<R: Read>(
     event_reader: &mut EventReader<R>,
     element: &OwnedName,
@@ -449,13 +460,19 @@ pub(crate) fn read_simple_tag<R: Read>(
     let element_display = element.to_string();
     let content = event_reader
         .next()
-        .map_err(to_xml_read_error(&element_display))
-        .and_then(inner_text_or_error(&element_display))?;
-
-    event_reader
-        .next()
-        .map_err(to_xml_read_error(&element_display))
-        .and_then(closing_tag_or_error(element))?;
+        .map_err(to_xml_read_error(&element_display))?;
+
+    let content = match content {
+        reader::XmlEvent::EndElement { .. } => String::new(),
+        reader::XmlEvent::Characters(content) | reader::XmlEvent::CData(content) => {
+            event_reader
+                .next()
+                .map_err(to_xml_read_error(&element_display))
+                .and_then(closing_tag_or_error(element))?;
+            content
+        }
+        unexpected => return Err(unexpected_element_error(element, unexpected)),
+    };
 
     Ok(content)
 }


Test patch:
diff --git a/cyclonedx-bom/tests/spec/1.3/valid-service-empty-description-1.3.xml b/cyclonedx-bom/tests/spec/1.3/valid-service-empty-description-1.3.xml
new file mode 100644
index 00000000..2ff484b9
--- /dev/null
+++ b/cyclonedx-bom/tests/spec/1.3/valid-service-empty-description-1.3.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0"?>
+<bom serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79" version="1" xmlns="http://cyclonedx.org/schema/bom/1.3">
+    <components>
+        <component type="library" bom-ref="pkg:maven/com.acme/stock-java-client@1.0.12">
+            <group>com.acme</group>
+            <name>stock-java-client</name>
+            <version>1.0.12</version>
+            <hashes>
+                <hash alg="SHA-1">e6b1000b94e835ffd37f4c6dcbdad43f4b48a02a</hash>
+            </hashes>
+            <licenses>
+                <license>
+                    <id>Apache-2.0</id>
+                </license>
+            </licenses>
+            <purl>pkg:maven/com.acme/stock-java-client@1.0.12</purl>
+        </component>
+    </components>
+    <services>
+        <service bom-ref="b2a46a4b-8367-4bae-9820-95557cfe03a8">
+            <provider>
+                <name>Partner Org</name>
+                <url>https://partner.org</url>
+                <contact>
+                    <name>Support</name>
+                    <email>support@partner</email>
+                    <phone>800-555-1212</phone>
+                </contact>
+            </provider>
+            <group>org.partner</group>
+            <name>Stock ticker service</name>
+            <version>2020-Q2</version>
+            <description />
+            <authenticated>true</authenticated>
+            <x-trust-boundary>true</x-trust-boundary>
+            <licenses>
+                <license>
+                    <name>Partner license</name>
+                </license>
+            </licenses>
+        </service>
+    </services>
+    <dependencies>
+        <dependency ref="pkg:maven/com.acme/stock-java-client@1.0.12">
+            <dependency ref="b2a46a4b-8367-4bae-9820-95557cfe03a8"/>
+        </dependency>
+    </dependencies>
+</bom>
diff --git a/cyclonedx-bom/tests/spec/snapshots/1.3/it_should_parse_all_of_the_valid_xml_specifications@valid-service-empty-description-1.3.xml.snap b/cyclonedx-bom/tests/spec/snapshots/1.3/it_should_parse_all_of_the_valid_xml_specifications@valid-service-empty-description-1.3.xml.snap
new file mode 100644
index 00000000..dc323228
--- /dev/null
+++ b/cyclonedx-bom/tests/spec/snapshots/1.3/it_should_parse_all_of_the_valid_xml_specifications@valid-service-empty-description-1.3.xml.snap
@@ -0,0 +1,54 @@
+---
+source: cyclonedx-bom/tests/specification_tests_v1_3.rs
+assertion_line: 27
+expression: bom_output
+input_file: cyclonedx-bom/tests/spec/1.3/valid-service-empty-description-1.3.xml
+---
+<?xml version="1.0" encoding="utf-8"?>
+<bom xmlns="http://cyclonedx.org/schema/bom/1.3" serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79" version="1">
+  <components>
+    <component type="library" bom-ref="pkg:maven/com.acme/stock-java-client@1.0.12">
+      <group>com.acme</group>
+      <name>stock-java-client</name>
+      <version>1.0.12</version>
+      <hashes>
+        <hash alg="SHA-1">e6b1000b94e835ffd37f4c6dcbdad43f4b48a02a</hash>
+      </hashes>
+      <licenses>
+        <license>
+          <id>Apache-2.0</id>
+        </license>
+      </licenses>
+      <purl>pkg:maven/com.acme/stock-java-client@1.0.12</purl>
+    </component>
+  </components>
+  <services>
+    <service bom-ref="b2a46a4b-8367-4bae-9820-95557cfe03a8">
+      <provider>
+        <name>Partner Org</name>
+        <url>https://partner.org</url>
+        <contact>
+          <name>Support</name>
+          <email>support@partner</email>
+          <phone>800-555-1212</phone>
+        </contact>
+      </provider>
+      <group>org.partner</group>
+      <name>Stock ticker service</name>
+      <version>2020-Q2</version>
+      <description></description>
+      <authenticated>true</authenticated>
+      <x-trust-boundary>true</x-trust-boundary>
+      <licenses>
+        <license>
+          <name>Partner license</name>
+        </license>
+      </licenses>
+    </service>
+  </services>
+  <dependencies>
+    <dependency ref="pkg:maven/com.acme/stock-java-client@1.0.12">
+      <dependency ref="b2a46a4b-8367-4bae-9820-95557cfe03a8" />
+    </dependency>
+  </dependencies>
+</bom>