This is an automated email from the ASF dual-hosted git repository.

apupier pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 47c90e8dc91fe1e203b772a7b7310c23b49aa71d
Author: smjain <[email protected]>
AuthorDate: Tue Sep 29 09:24:49 2026 +0530

    CAMEL-25127: camel-syslog - accept valid RFC 5424 messages that the parser 
rejected or misread
    
    SyslogConverter.parseMessage failed on, or misread, valid RFC 5424
    messages:
    
    - MSG is optional (SYSLOG-MSG = HEADER SP STRUCTURED-DATA [SP MSG]), but
      every field loop read until a space, so a message that ends at its
      structured data (RFC 5424 example 4) threw BufferUnderflowException.
      The end of the input now also ends a field, and MSG is then empty.
    - The structured data loop only tracked '[' and ']', so an escaped ']'
      inside a PARAM-VALUE (RFC 5424 6.3.3) ended the structured data and
      moved its tail into the log message. The loop now follows the grammar:
      a '"' after '=' starts a PARAM-VALUE, in which a backslash escapes the
      next char, and only a ']' outside a value closes the element.
    - The NILVALUE timestamp "-" (RFC 5424 6.2.3) threw
      IllegalArgumentException; the timestamp is now left null.
    
    A field that is followed by a space is read exactly as before.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../camel/component/syslog/SyslogConverter.java    |  42 ++++--
 .../component/syslog/SyslogRfc5424ParseTest.java   | 166 +++++++++++++++++++++
 2 files changed, 196 insertions(+), 12 deletions(-)

diff --git 
a/components/camel-syslog/src/main/java/org/apache/camel/component/syslog/SyslogConverter.java
 
b/components/camel-syslog/src/main/java/org/apache/camel/component/syslog/SyslogConverter.java
index b22551e8b471..9320a6ec9f4c 100644
--- 
a/components/camel-syslog/src/main/java/org/apache/camel/component/syslog/SyslogConverter.java
+++ 
b/components/camel-syslog/src/main/java/org/apache/camel/component/syslog/SyslogConverter.java
@@ -224,19 +224,22 @@ public final class SyslogConverter {
                 LOG.error("Invalid syslog message, missing a mandatory space 
after version");
             }
 
-            // This should be the timestamp
+            // This should be the timestamp, or the NILVALUE when the sender 
has no time (RFC 5424 6.2.3)
             StringBuilder date = new StringBuilder();
-            while ((charFound = (char) (byteBuffer.get() & 0xff)) != ' ') {
+            while (byteBuffer.hasRemaining() && (charFound = (char) 
(byteBuffer.get() & 0xff)) != ' ') {
                 date.append(charFound);
             }
 
-            
syslogMessage.setTimestamp(DatatypeConverter.parseDateTime(date.toString()));
+            if (!"-".contentEquals(date)) {
+                
syslogMessage.setTimestamp(DatatypeConverter.parseDateTime(date.toString()));
+            }
         }
 
-        // The host is the char sequence until the next ' '
+        // The host is the char sequence until the next ' ', or the end of the 
message: MSG is optional (RFC 5424 6),
+        // so any field can be the last one
 
         StringBuilder host = new StringBuilder();
-        while ((charFound = (char) (byteBuffer.get() & 0xff)) != ' ') {
+        while (byteBuffer.hasRemaining() && (charFound = (char) 
(byteBuffer.get() & 0xff)) != ' ') {
             host.append(charFound);
         }
 
@@ -245,32 +248,47 @@ public final class SyslogConverter {
         if (isRfc5424) {
             Rfc5424SyslogMessage rfc5424SyslogMessage = (Rfc5424SyslogMessage) 
syslogMessage;
             StringBuilder appName = new StringBuilder();
-            while ((charFound = (char) (byteBuffer.get() & 0xff)) != ' ') {
+            while (byteBuffer.hasRemaining() && (charFound = (char) 
(byteBuffer.get() & 0xff)) != ' ') {
                 appName.append(charFound);
             }
             rfc5424SyslogMessage.setAppName(decode(appName, charset));
 
             StringBuilder procId = new StringBuilder();
-            while ((charFound = (char) (byteBuffer.get() & 0xff)) != ' ') {
+            while (byteBuffer.hasRemaining() && (charFound = (char) 
(byteBuffer.get() & 0xff)) != ' ') {
                 procId.append(charFound);
             }
             rfc5424SyslogMessage.setProcId(decode(procId, charset));
 
             StringBuilder msgId = new StringBuilder();
-            while ((charFound = (char) (byteBuffer.get() & 0xff)) != ' ') {
+            while (byteBuffer.hasRemaining() && (charFound = (char) 
(byteBuffer.get() & 0xff)) != ' ') {
                 msgId.append(charFound);
             }
             rfc5424SyslogMessage.setMsgId(decode(msgId, charset));
 
+            // STRUCTURED-DATA is the NILVALUE or SD-ELEMENTs (RFC 5424 6.3). 
A PARAM-VALUE is quoted, and escapes
+            // '"', '\' and ']' with a backslash, so a ']' only closes the 
element outside a PARAM-VALUE
             StringBuilder structuredData = new StringBuilder();
             boolean inblock = false;
-            while ((charFound = (char) (byteBuffer.get() & 0xff)) != ' ' || 
inblock) {
-                if (charFound == '[') {
+            boolean inValue = false;
+            boolean escaped = false;
+            char previous = 0;
+            while (byteBuffer.hasRemaining() && ((charFound = (char) 
(byteBuffer.get() & 0xff)) != ' ' || inblock)) {
+                if (inValue) {
+                    if (escaped) {
+                        escaped = false;
+                    } else if (charFound == '\\') {
+                        escaped = true;
+                    } else if (charFound == '"') {
+                        inValue = false;
+                    }
+                } else if (charFound == '[') {
                     inblock = true;
-                }
-                if (charFound == ']') {
+                } else if (charFound == ']') {
                     inblock = false;
+                } else if (charFound == '"' && inblock && previous == '=') {
+                    inValue = true;
                 }
+                previous = charFound;
                 structuredData.append(charFound);
             }
             rfc5424SyslogMessage.setStructuredData(decode(structuredData, 
charset));
diff --git 
a/components/camel-syslog/src/test/java/org/apache/camel/component/syslog/SyslogRfc5424ParseTest.java
 
b/components/camel-syslog/src/test/java/org/apache/camel/component/syslog/SyslogRfc5424ParseTest.java
new file mode 100644
index 000000000000..251b73b2e0fa
--- /dev/null
+++ 
b/components/camel-syslog/src/test/java/org/apache/camel/component/syslog/SyslogRfc5424ParseTest.java
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.syslog;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+/**
+ * Valid RFC 5424 messages: without MSG, with escaped characters in a 
PARAM-VALUE, and with a NILVALUE timestamp.
+ */
+public class SyslogRfc5424ParseTest extends CamelTestSupport {
+
+    private static final String HEADER = "<165>1 2003-10-11T22:14:15.003Z 
mymachine.example.com evntslog - ID47 ";
+    private static final String EXAMPLE_SD = "[exampleSDID@32473 iut=\"3\" 
eventSource=\"Application\" eventID=\"1011\"]";
+
+    @Test
+    public void testStructuredDataOnly() {
+        // RFC 5424 6.5, example 4: "This is a valid message"
+        String sd = EXAMPLE_SD + "[examplePriority@32473 class=\"high\"]";
+
+        Rfc5424SyslogMessage message = parse(HEADER + sd);
+
+        assertEquals("mymachine.example.com", message.getHostname());
+        assertEquals("evntslog", message.getAppName());
+        assertEquals("-", message.getProcId());
+        assertEquals("ID47", message.getMsgId());
+        assertEquals(sd, message.getStructuredData());
+        assertEquals("", message.getLogMessage());
+    }
+
+    @Test
+    public void testNilStructuredDataWithoutMsg() {
+        Rfc5424SyslogMessage message = parse("<34>1 2003-10-11T22:14:15.003Z 
mymachine.example.com su - ID47 -");
+
+        assertEquals("ID47", message.getMsgId());
+        assertEquals("-", message.getStructuredData());
+        assertEquals("", message.getLogMessage());
+    }
+
+    @Test
+    public void testEscapedBracketInParamValue() {
+        String sd = "[exampleSDID@32473 note=\"a\\] b\"]";
+
+        Rfc5424SyslogMessage message = parse(HEADER + sd + " hello");
+
+        assertEquals(sd, message.getStructuredData());
+        assertEquals("hello", message.getLogMessage());
+    }
+
+    @Test
+    public void testEscapedQuoteAndBackslashInParamValue() {
+        String sd = "[exampleSDID@32473 quote=\"say \\\"x\\] y\\\"\" 
path=\"c:\\\\\" note=\"b\\] c\"][other@32473 n=\"2\"]";
+
+        Rfc5424SyslogMessage message = parse(HEADER + sd + " hello world");
+
+        assertEquals(sd, message.getStructuredData());
+        assertEquals("hello world", message.getLogMessage());
+    }
+
+    @Test
+    public void testNilTimestamp() {
+        // RFC 5424 6.2.3: a sender that cannot obtain the time MUST send the 
NILVALUE
+        Rfc5424SyslogMessage message = parse("<34>1 - mymachine.example.com su 
- ID47 - hello");
+
+        assertNull(message.getTimestamp());
+        assertEquals("mymachine.example.com", message.getHostname());
+        assertEquals("su", message.getAppName());
+        assertEquals("hello", message.getLogMessage());
+    }
+
+    @Test
+    public void testRfc3164WithoutMsg() {
+        SyslogMessage message = SyslogConverter.toSyslogMessage("<34>Oct 11 
22:14:15 mymachine");
+
+        assertEquals("mymachine", message.getHostname());
+        assertEquals("", message.getLogMessage());
+    }
+
+    @Test
+    public void testRfc5424ExamplesUnchanged() {
+        Rfc5424SyslogMessage example1 = parse(
+                "<34>1 2003-10-11T22:14:15.003Z mymachine.example.com su - 
ID47 - 'su root' failed for lonvick on /dev/pts/8");
+        assertNotNull(example1.getTimestamp());
+        assertEquals("-", example1.getStructuredData());
+        assertEquals("'su root' failed for lonvick on /dev/pts/8", 
example1.getLogMessage());
+
+        Rfc5424SyslogMessage example2
+                = parse("<165>1 2003-08-24T05:14:15.000003-07:00 192.0.2.1 
myproc 8710 - - %% It's time to make the do-nuts.");
+        assertEquals("192.0.2.1", example2.getHostname());
+        assertEquals("8710", example2.getProcId());
+        assertEquals("-", example2.getMsgId());
+        assertEquals("-", example2.getStructuredData());
+        assertEquals("%% It's time to make the do-nuts.", 
example2.getLogMessage());
+
+        Rfc5424SyslogMessage example3 = parse(HEADER + EXAMPLE_SD + " An 
application event log entry...");
+        assertEquals(EXAMPLE_SD, example3.getStructuredData());
+        assertEquals("An application event log entry...", 
example3.getLogMessage());
+
+        // a quote that does not start a PARAM-VALUE, as parsed before
+        Rfc5424SyslogMessage stray = parse(HEADER + "[id@1 a=\"1\"b\"] hello");
+        assertEquals("[id@1 a=\"1\"b\"]", stray.getStructuredData());
+        assertEquals("hello", stray.getLogMessage());
+    }
+
+    @Test
+    public void testUnmarshalStructuredDataOnly() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:result");
+        mock.expectedMessageCount(1);
+        mock.expectedHeaderReceived(SyslogConstants.SYSLOG_HOSTNAME, 
"mymachine.example.com");
+
+        String sd = EXAMPLE_SD + "[examplePriority@32473 class=\"high\"]";
+        template.sendBody("direct:unmarshal", HEADER + sd);
+
+        MockEndpoint.assertIsSatisfied(context);
+        Rfc5424SyslogMessage message
+                = assertInstanceOf(Rfc5424SyslogMessage.class, 
mock.getReceivedExchanges().get(0).getIn().getBody());
+        assertEquals(sd, message.getStructuredData());
+    }
+
+    @Test
+    public void testUnmarshalNilTimestamp() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:result");
+        mock.expectedMessageCount(1);
+
+        template.sendBody("direct:unmarshal", "<34>1 - mymachine.example.com 
su - ID47 - hello");
+
+        MockEndpoint.assertIsSatisfied(context);
+        
assertNull(mock.getReceivedExchanges().get(0).getIn().getHeader(SyslogConstants.SYSLOG_TIMESTAMP));
+        assertEquals("hello", 
mock.getReceivedExchanges().get(0).getIn().getBody(SyslogMessage.class).getLogMessage());
+    }
+
+    private static Rfc5424SyslogMessage parse(String text) {
+        return assertInstanceOf(Rfc5424SyslogMessage.class, 
SyslogConverter.toSyslogMessage(text));
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                
from("direct:unmarshal").unmarshal().syslog().to("mock:result");
+            }
+        };
+    }
+}

Reply via email to