Scanning an XML String in MuleSoft with Cloudmersive Virus Scan |
10/15/2025 - Cloudmersive Support |
In this flow, we take in XML as a x-www-form-urlencoded string with parameter name xml . This string is then sent to the Cloudmersive Virus Scan API for virus scanning. We can choose between the Advanced Virus Scan and Basic Virus Scan . Note that you should replace REPLACE_WITH_YOUR_CLOUDMERSIVE_API_KEY .

Here is the full Flow XML for the Mule 4 flow:
<?xml version="1.0" encoding="UTF-8"?>
<mule xmlns="http://www.mulesoft.org/schema/mule/core"
xmlns:ee="http://www.mulesoft.org/schema/mule/ee/core"
xmlns:http="http://www.mulesoft.org/schema/mule/http"
xmlns:doc="http://www.mulesoft.org/schema/mule/documentation"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd
http://www.mulesoft.org/schema/mule/http http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd
http://www.mulesoft.org/schema/mule/ee/core http://www.mulesoft.org/schema/mule/ee/core/current/mule-ee.xsd">
<!-- Listener on 8081 -->
<http:listener-config name="listener" doc:name="Listener (8081)">
<http:listener-connection host="0.0.0.0" port="8081"/>
</http:listener-config>
<!-- Health check -->
<flow name="ping">
<http:listener doc:name="GET /ping" config-ref="listener" path="/ping" allowedMethods="GET"/>
<set-payload value="OK"/>
</flow>
<!-- Cloudmersive request config -->
<http:request-config name="cloudmersive" doc:name="Cloudmersive">
<http:request-connection protocol="HTTPS" host="api.cloudmersive.com" port="443"/>
</http:request-config>
<!-- POST /scan-xml -->
<flow name="scan-xml-flow">
<http:listener doc:name="POST /scan-xml" config-ref="listener" path="/scan-xml" allowedMethods="POST">
<http:response statusCode="#[vars.httpStatus default 200]">
<http:headers><![CDATA[#[{'Content-Type':'application/json'}]]]></http:headers>
</http:response>
<http:error-response statusCode="#[vars.httpStatus default 500]">
<http:headers><![CDATA[#[{'Content-Type':'application/json'}]]]></http:headers>
</http:error-response>
</http:listener>
<logger level="INFO"
message="scan-xml invoked. Content-Type: #[attributes.headers.'content-type'], inbound type: #[typeOf(payload)]"/>
<flow-ref name="process-scan-flow"/>
</flow>
<!-- POST /api/scan-xml (same processing) -->
<flow name="scan-xml-with-basepath-flow">
<http:listener doc:name="POST /api/scan-xml" config-ref="listener" path="/api/scan-xml" allowedMethods="POST">
<http:response statusCode="#[vars.httpStatus default 200]">
<http:headers><![CDATA[#[{'Content-Type':'application/json'}]]]></http:headers>
</http:response>
<http:error-response statusCode="#[vars.httpStatus default 500]">
<http:headers><![CDATA[#[{'Content-Type':'application/json'}]]]></http:headers>
</http:error-response>
</http:listener>
<logger level="INFO"
message="/api/scan-xml invoked. Content-Type: #[attributes.headers.'content-type'], inbound type: #[typeOf(payload)]"/>
<flow-ref name="process-scan-flow"/>
</flow>
<!-- Shared processing -->
<flow name="process-scan-flow">
<!-- Normalize headers and basic flags -->
<set-variable variableName="contentType" value="#[lower((attributes.headers.'content-type' default '') as String)]"/>
<set-variable variableName="isForm" value="#[startsWith(vars.contentType, 'application/x-www-form-urlencoded')]"/>
<!-- Normalize to String -->
<choice doc:name="Normalize to String">
<!-- FORM: application/x-www-form-urlencoded -->
<when expression="#[vars.isForm]">
<!-- Always build a safe form map first; never dereference payload directly -->
<ee:transform doc:name="Ensure formMap">
<ee:variables>
<ee:set-variable variableName="formMap"><![CDATA[%dw 2.0
output application/java
---
payload match {
case s is String -> read(s, "application/x-www-form-urlencoded")
case b is Binary -> read((b as String { encoding: "UTF-8" }), "application/x-www-form-urlencoded")
case o is Object -> o
else -> {}
}]]></ee:set-variable>
</ee:variables>
</ee:transform>
<!-- Require 'xml' or 'input' (quoted because 'input' is reserved) -->
<choice>
<when expression="#[((vars.formMap.'xml' default null) == null) and ((vars.formMap.'input' default null) == null)]">
<raise-error type="VALIDATION:INVALID_PAYLOAD"
description="Missing form field 'xml' (or 'input') for application/x-www-form-urlencoded."/>
</when>
<otherwise>
<set-variable variableName="xmlString"
value="#[(vars.formMap.'xml' default vars.formMap.'input') as String]"/>
</otherwise>
</choice>
</when>
<!-- Non-form: String -->
<when expression="#[not vars.isForm and (payload is String)]">
<set-variable variableName="xmlString" value="#[payload]"/>
</when>
<!-- Non-form: Binary -->
<when expression="#[not vars.isForm and (payload is Binary)]">
<set-variable variableName="xmlString" value="#[(payload as String { encoding: 'UTF-8' })]"/>
</when>
<!-- Non-form: Mule parsed it (Object) -> serialize back to XML -->
<otherwise>
<set-variable variableName="xmlString" value="#[write(payload, 'application/xml')]"/>
</otherwise>
</choice>
<!-- Optional filename from query param -->
<set-variable variableName="fileName" value="#[attributes.queryParams.fileName default 'input.xml']"/>
<!-- Build multipart/form-data payload (part name must be 'inputFile') -->
<ee:transform doc:name="Build multipart/form-data body">
<ee:message>
<ee:set-payload><![CDATA[%dw 2.0
output multipart/form-data
---
{
parts: {
inputFile: {
headers: {
// Use a single string form for Content-Disposition
"Content-Disposition": "form-data; name=\"inputFile\"; filename=\"" ++ (vars.fileName as String) ++ "\"",
// IMPORTANT: treat content as raw bytes, not XML
"Content-Type": "application/octet-stream"
},
// Send the XML text as Binary to avoid XML writer
content: (vars.xmlString as Binary { encoding: "UTF-8" })
}
}
}
]]></ee:set-payload>
</ee:message>
</ee:transform>
<!-- Call Cloudmersive -->
<http:request doc:name="Scan via Cloudmersive"
method="POST"
config-ref="cloudmersive"
path="/virus/scan/file">
<http:headers><![CDATA[#[{
Apikey: 'REPLACE_WITH_YOUR_CLOUDMERSIVE_API_KEY',
Accept: 'application/json'
}]]]></http:headers>
</http:request>
<!-- Return upstream JSON -->
<ee:transform doc:name="Respond JSON">
<ee:message>
<ee:set-payload><![CDATA[%dw 2.0
output application/json
---
payload
]]></ee:set-payload>
</ee:message>
</ee:transform>
<!-- Errors -->
<error-handler>
<on-error-propagate type="VALIDATION:*" doc:name="400 Bad Request">
<set-variable variableName="httpStatus" value="400"/>
<ee:transform doc:name="Body 400">
<ee:message>
<ee:set-payload><![CDATA[%dw 2.0
output application/json
---
{
error: "Bad Request",
message: (error.description default "Validation error.")
}
]]></ee:set-payload>
</ee:message>
</ee:transform>
</on-error-propagate>
<on-error-propagate type="HTTP:*" doc:name="502 Upstream error">
<set-variable variableName="httpStatus" value="502"/>
<ee:transform doc:name="Body 502">
<ee:message>
<ee:set-payload><![CDATA[%dw 2.0
output application/json
---
{
error: "Upstream error",
upstreamType: error.errorType.identifier,
message: error.description
}
]]></ee:set-payload>
</ee:message>
</ee:transform>
</on-error-propagate>
<on-error-propagate doc:name="500 Fallback">
<set-variable variableName="httpStatus" value="500"/>
<ee:transform doc:name="Body 500">
<ee:message>
<ee:set-payload><![CDATA[%dw 2.0
output application/json
---
{
error: (error.errorType.identifier default "UNKNOWN"),
message: (error.description default "Unexpected error")
}
]]></ee:set-payload>
</ee:message>
</ee:transform>
</on-error-propagate>
</error-handler>
</flow>
</mule>
|