diff --git a/pom.xml b/pom.xml index ab81976..6e5a01f 100644 --- a/pom.xml +++ b/pom.xml @@ -73,7 +73,7 @@ 4.6 2.9.2 5.0.2 - 2.12.0 + 2.28.2 B1606F22 true diff --git a/src/main/java/com/github/jcustenborder/cef/CEFParserImpl.java b/src/main/java/com/github/jcustenborder/cef/CEFParserImpl.java index 969a3b6..774273b 100644 --- a/src/main/java/com/github/jcustenborder/cef/CEFParserImpl.java +++ b/src/main/java/com/github/jcustenborder/cef/CEFParserImpl.java @@ -37,33 +37,19 @@ class CEFParserImpl implements CEFParser { private static final Logger log = LoggerFactory.getLogger(CEFParserImpl.class); private static final Pattern PATTERN_CEF_PREFIX = Pattern.compile("^((?.+)\\s+(?\\S+)\\s+).*?(?CEF:\\d+)|^.*?(?CEF:\\d+)"); private static final Pattern PATTERN_OSSEC_PREFIX = Pattern.compile("^(?[A-Za-z]+\\s+\\d{1,2}\\s+\\d{1,2}:\\d{2}:\\d{2})\\s+(?:ASM:)?(?CEF:\\d+)"); - private static final Pattern PATTERN_CEF_MAIN = Pattern.compile("(? DATE_FORMATS = Arrays.asList( - "MMM dd yyyy HH:mm:ss.SSS zzz", - "MMM dd yyyy HH:mm:ss.SSS", - "MMM dd yyyy HH:mm:ss zzz", - "MMM dd yyyy HH:mm:ss", - "MMM dd HH:mm:ss.SSS zzz", - "MMM dd HH:mm:ss.SSS", - "MMM dd HH:mm:ss zzz", - "MMM dd HH:mm:ss", - "yyyy-MM-dd'T'HH:mm:ss.SSSZ", - "yyyy-MM-dd'T'HH:mm:ssZ", - "yyyy-MM-dd'T'HH:mm:ss.SSS zzz", - "yyyy-MM-dd'T'HH:mm:ss zzz", - "yyyy-MM-dd'T'HH:mm:ss.SSS ZZZ", - "yyyy-MM-dd'T'HH:mm:ss ZZZ", - "yyyy-MM-dd'T'HH:mm:ss.SSS XXX", - "yyyy-MM-dd'T'HH:mm:ss XXX", - "yyyy-MM-dd'T'HH:mm:ssXXX", - "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" - ); + final MessageFactory messageFactory; + private DateParser dateParser; public CEFParserImpl(MessageFactory messageFactory) { + this(messageFactory, new DateParserImpl()); + } + + public CEFParserImpl(MessageFactory messageFactory, DateParser dateParser) { this.messageFactory = messageFactory; + this.dateParser = dateParser; } @Override @@ -98,8 +84,8 @@ public Message parse(final String event, final TimeZone timeZone, final Locale l final int cefstartIndex; if (isOssec) { - final String df = "MMM dd HH:mm:ss"; - final SimpleDateFormat dateFormat = new SimpleDateFormat(df); + final String dateFormatPattern = "MMM dd HH:mm:ss"; + final SimpleDateFormat dateFormat = new SimpleDateFormat(dateFormatPattern); dateFormat.setTimeZone(timeZone); final Date timestamp; try { @@ -110,7 +96,7 @@ public Message parse(final String event, final TimeZone timeZone, final Locale l calendar.set(Calendar.YEAR, thisYear); timestamp = calendar.getTime(); } catch (ParseException e) { - log.trace("parse() - Could not parse '{}' with '{}'.", timestampText, df); + log.trace("parse() - Could not parse '{}' with '{}'.", timestampText, dateFormatPattern); throw new IllegalStateException("Could not parse timestamp. '" + timestampText + "'"); } @@ -119,49 +105,13 @@ public Message parse(final String event, final TimeZone timeZone, final Locale l cefstartIndex = ossecPrefixMatcher.start("cs"); } else if (timestampText != null && !timestampText.isEmpty() && host != null && !host.isEmpty()) { - Long longTimestamp = null; + final Date timestamp; try { - longTimestamp = Long.parseLong(timestampText); - } catch (NumberFormatException e) { - log.trace("Unable to parse timestamp '{}'", timestampText); + timestamp = this.dateParser.parseDate(timestampText, timeZone, locale); + } catch (IllegalArgumentException e) { + throw new IllegalStateException(e); } - Date timestamp = null; - if (null != longTimestamp) { - log.trace("parse() - Detected timestamp is stored as a long."); - timestamp = new Date(longTimestamp); - } else { - log.trace("parse() - Trying to parse the timestamp."); - // SimpleDateFormat is not threadsafe so we have to create them each time. - for (String df : DATE_FORMATS) { - SimpleDateFormat dateFormat = new SimpleDateFormat(df, locale); - dateFormat.setTimeZone(timeZone); - try { - log.trace("parse() - Trying to parse '{}' with format '{}'", timestampText, df); - timestamp = dateFormat.parse(timestampText); - final boolean alterYear = !df.contains("yyyy"); - - if (alterYear) { - log.trace("parse() - date format '{}' does not specify the year. Might need to alter the year.", df); - Calendar calendar = Calendar.getInstance(timeZone); - int thisYear = calendar.get(Calendar.YEAR); - calendar.setTime(timestamp); - final int year = calendar.get(Calendar.YEAR); - if (1970 == year) { - log.trace("parse() - altering year from {} to {}", year, thisYear); - calendar.set(Calendar.YEAR, thisYear); - timestamp = calendar.getTime(); - } - } - break; - } catch (ParseException e) { - log.trace("parse() - Could not parse '{}' with '{}'.", timestampText, df); - } - } - if (null == timestamp) { - throw new IllegalStateException("Could not parse timestamp. '" + timestampText + "'"); - } - } log.trace("parse() - timestamp = {}, {}", timestamp.getTime(), timestamp); builder.timestamp(timestamp); builder.host(host); diff --git a/src/main/java/com/github/jcustenborder/cef/DateParser.java b/src/main/java/com/github/jcustenborder/cef/DateParser.java new file mode 100644 index 0000000..abf0ee6 --- /dev/null +++ b/src/main/java/com/github/jcustenborder/cef/DateParser.java @@ -0,0 +1,38 @@ +/** + * Copyright © 2017 Jeremy Custenborder (jcustenborder@gmail.com) + * + * Licensed 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 com.github.jcustenborder.cef; + +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +/** + * This interface attempts to parse a string containing a date and time. + */ +public interface DateParser { + + /** + * Attempts to parse supplied dateTime string. + * @param timestampText Text representing a valid timestamp + * @param timeZone A valid time zone. + * @param locale The parsed date + * @return + * @throws IllegalArgumentException + */ + Date parseDate(final String timestampText, final TimeZone timeZone, final Locale locale); + +} diff --git a/src/main/java/com/github/jcustenborder/cef/DateParserImpl.java b/src/main/java/com/github/jcustenborder/cef/DateParserImpl.java new file mode 100644 index 0000000..981619e --- /dev/null +++ b/src/main/java/com/github/jcustenborder/cef/DateParserImpl.java @@ -0,0 +1,97 @@ +/** + * Copyright © 2017 Jeremy Custenborder (jcustenborder@gmail.com) + * + * Licensed 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 com.github.jcustenborder.cef; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.TimeZone; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DateParserImpl implements DateParser { + private static final List DATE_FORMATS = Arrays.asList( + "MMM dd yyyy HH:mm:ss.SSS zzz", + "MMM dd yyyy HH:mm:ss.SSS", + "MMM dd yyyy HH:mm:ss zzz", + "MMM dd yyyy HH:mm:ss", + "MMM dd HH:mm:ss.SSS zzz", + "MMM dd HH:mm:ss.SSS", + "MMM dd HH:mm:ss zzz", + "MMM dd HH:mm:ss", + "yyyy-MM-dd'T'HH:mm:ss.SSSZ", + "yyyy-MM-dd'T'HH:mm:ssZ", + "yyyy-MM-dd'T'HH:mm:ss.SSS zzz", + "yyyy-MM-dd'T'HH:mm:ss zzz", + "yyyy-MM-dd'T'HH:mm:ss.SSS ZZZ", + "yyyy-MM-dd'T'HH:mm:ss ZZZ", + "yyyy-MM-dd'T'HH:mm:ss.SSS XXX", + "yyyy-MM-dd'T'HH:mm:ss XXX", + "yyyy-MM-dd'T'HH:mm:ssXXX", + "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" + ); + private static final Logger log = LoggerFactory.getLogger(DateParserImpl.class); + + @Override + public Date parseDate(String timestampText, final TimeZone timeZone, final Locale locale) { + try { + Long longTimestamp = Long.parseLong(timestampText); + log.trace("parse() - Detected timestamp is stored as a long."); + + return new Date(longTimestamp); + } catch (NumberFormatException e) { + log.trace("Unable to parse timestamp '{}'", timestampText); + } + + log.trace("parse() - Trying to parse the timestamp."); + // SimpleDateFormat is not threadsafe so we have to create them each time. + for (String pattern : DATE_FORMATS) { + SimpleDateFormat dateFormat = new SimpleDateFormat(pattern, locale); + dateFormat.setTimeZone(timeZone); + try { + log.trace("parse() - Trying to parse '{}' with format '{}'", timestampText, pattern); + Date timestamp = dateFormat.parse(timestampText); + final boolean alterYear = !pattern.contains("yyyy"); + + if (alterYear) { + log.trace("parse() - date format '{}' does not specify the year. Might need to alter the year.", pattern); + Calendar calendar = Calendar.getInstance(timeZone); + int thisYear = calendar.get(Calendar.YEAR); + calendar.setTime(timestamp); + final int year = calendar.get(Calendar.YEAR); + if (1970 == year) { + log.trace("parse() - altering year from {} to {}", year, thisYear); + calendar.set(Calendar.YEAR, thisYear); + + return calendar.getTime(); + } + } + + return timestamp; + } catch (ParseException e) { + log.trace("parse() - Could not parse '{}' with '{}'.", timestampText, pattern); + } + } + + throw new IllegalArgumentException("Could not parse timestamp. '" + timestampText + "'"); + } +} \ No newline at end of file diff --git a/src/test/java/com/github/jcustenborder/cef/MessageAssertions.java b/src/test/java/com/github/jcustenborder/cef/MessageAssertions.java index 5dd3213..dc3ae1a 100644 --- a/src/test/java/com/github/jcustenborder/cef/MessageAssertions.java +++ b/src/test/java/com/github/jcustenborder/cef/MessageAssertions.java @@ -19,6 +19,8 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import java.util.Date; + public class MessageAssertions { public static void assertMessage(Message expected, Message actual) { if (null == expected) { @@ -27,6 +29,14 @@ public static void assertMessage(Message expected, Message actual) { assertNotNull(actual, "actual should not be null."); } + // Force actual year to match the year that the tests were recorded + final Date expectedTimestamp = expected.timestamp(), actualTimestamp = actual.timestamp(); + if (expectedTimestamp != null && actualTimestamp != null + && expectedTimestamp.getYear() != actualTimestamp.getYear()) { + assertEquals(actualTimestamp.getYear(), new Date().getYear(), "year does not match"); + actual.timestamp().setYear(expected.timestamp().getYear()); + } + assertEquals(expected.timestamp(), actual.timestamp(), "timestamp() does not match"); assertEquals(expected.host(), actual.host(), "host() does not match"); assertEquals(expected.cefVersion(), actual.cefVersion(), "cefVersion() does not match");