You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by mi...@apache.org on 2020/10/16 13:02:30 UTC

[maven-enforcer] 01/01: [MENFORCER-361] Introduce RequireTextFileChecksum with line separator normalization

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

michaelo pushed a commit to branch MENFORCER-361
in repository https://gitbox.apache.org/repos/asf/maven-enforcer.git

commit 2babf83f8cb03b8565fa48fe429dbdd8f9fccee4
Author: Konrad Windszus <kw...@apache.org>
AuthorDate: Sun Sep 13 17:32:24 2020 +0200

    [MENFORCER-361] Introduce RequireTextFileChecksum with line separator normalization
    
    This closes #79
---
 enforcer-rules/pom.xml                             |   4 +
 .../plugins/enforcer/RequireFileChecksum.java      |  65 ++++----
 .../plugins/enforcer/RequireTextFileChecksum.java  | 102 ++++++++++++
 .../utils/NormalizeLineSeparatorReader.java        | 177 +++++++++++++++++++++
 .../src/site/apt/requireFileChecksum.apt.vm        |   7 +-
 ...cksum.apt.vm => requireTextFileChecksum.apt.vm} |  44 +++--
 .../plugins/enforcer/TestRequireFileChecksum.java  |   2 +
 .../enforcer/TestRequireTextFileChecksum.java      | 128 +++++++++++++++
 .../utils/TestNormalizeLineSeparatorReader.java    |  77 +++++++++
 pom.xml                                            |   7 +-
 10 files changed, 564 insertions(+), 49 deletions(-)

diff --git a/enforcer-rules/pom.xml b/enforcer-rules/pom.xml
index 03077da..3fb4426 100644
--- a/enforcer-rules/pom.xml
+++ b/enforcer-rules/pom.xml
@@ -67,6 +67,10 @@
       <artifactId>commons-codec</artifactId>
     </dependency>
     <dependency>
+      <groupId>commons-io</groupId>
+      <artifactId>commons-io</artifactId>
+    </dependency>
+    <dependency>
       <groupId>org.apache.maven.enforcer</groupId>
       <artifactId>enforcer-api</artifactId>
     </dependency>
diff --git a/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/RequireFileChecksum.java b/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/RequireFileChecksum.java
index bbed9e7..774a493 100644
--- a/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/RequireFileChecksum.java
+++ b/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/RequireFileChecksum.java
@@ -29,16 +29,17 @@ import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
 import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper;
 
 /**
- * Rule to validate a file to match the specified checksum.
+ * Rule to validate a binary file to match the specified checksum.
  *
  * @author Edward Samson
  * @author Lyubomyr Shaydariv
+ * @see RequireTextFileChecksum
  */
 public class RequireFileChecksum
     extends AbstractNonCacheableEnforcerRule
 {
 
-    private File file;
+    protected File file;
 
     private String checksum;
 
@@ -140,41 +141,47 @@ public class RequireFileChecksum
         this.nonexistentFileMessage = nonexistentFileMessage;
     }
 
-    private String calculateChecksum()
+    protected String calculateChecksum()
         throws EnforcerRuleException
     {
         try ( InputStream inputStream = new FileInputStream( this.file ) )
         {
-            String checksum;
-            if ( "md5".equals( this.type ) )
-            {
-                checksum = DigestUtils.md5Hex( inputStream );
-            }
-            else if ( "sha1".equals( this.type ) )
-            {
-                checksum = DigestUtils.shaHex( inputStream );
-            }
-            else if ( "sha256".equals( this.type ) )
-            {
-                checksum = DigestUtils.sha256Hex( inputStream );
-            }
-            else if ( "sha384".equals( this.type ) )
-            {
-                checksum = DigestUtils.sha384Hex( inputStream );
-            }
-            else if ( "sha512".equals( this.type ) )
-            {
-                checksum = DigestUtils.sha512Hex( inputStream );
-            }
-            else
-            {
-                throw new EnforcerRuleException( "Unsupported hash type: " + this.type );
-            }
-            return checksum;
+            return calculateChecksum( inputStream );
         }
         catch ( IOException e )
         {
             throw new EnforcerRuleException( "Unable to calculate checksum", e );
         }
     }
+
+    protected String calculateChecksum( InputStream inputStream )
+        throws IOException, EnforcerRuleException
+    {
+        String checksum;
+        if ( "md5".equals( this.type ) )
+        {
+            checksum = DigestUtils.md5Hex( inputStream );
+        }
+        else if ( "sha1".equals( this.type ) )
+        {
+            checksum = DigestUtils.shaHex( inputStream );
+        }
+        else if ( "sha256".equals( this.type ) )
+        {
+            checksum = DigestUtils.sha256Hex( inputStream );
+        }
+        else if ( "sha384".equals( this.type ) )
+        {
+            checksum = DigestUtils.sha384Hex( inputStream );
+        }
+        else if ( "sha512".equals( this.type ) )
+        {
+            checksum = DigestUtils.sha512Hex( inputStream );
+        }
+        else
+        {
+            throw new EnforcerRuleException( "Unsupported hash type: " + this.type );
+        }
+        return checksum;
+    }
 }
diff --git a/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/RequireTextFileChecksum.java b/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/RequireTextFileChecksum.java
new file mode 100644
index 0000000..42fb8a1
--- /dev/null
+++ b/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/RequireTextFileChecksum.java
@@ -0,0 +1,102 @@
+package org.apache.maven.plugins.enforcer;
+
+/*
+ * 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.
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+
+import org.apache.commons.io.input.ReaderInputStream;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
+import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper;
+import org.apache.maven.plugins.enforcer.utils.NormalizeLineSeparatorReader;
+import org.apache.maven.plugins.enforcer.utils.NormalizeLineSeparatorReader.LineSeparator;
+import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
+
+/**
+ * Rule to validate a text file to match the specified checksum.
+ *
+ * @author Konrad Windszus
+ * @see RequireFileChecksum
+ */
+public class RequireTextFileChecksum
+    extends RequireFileChecksum
+{
+    private NormalizeLineSeparatorReader.LineSeparator normalizeLineSeparatorTo = LineSeparator.UNIX;
+
+    Charset encoding;
+
+    public void setNormalizeLineSeparatorTo( NormalizeLineSeparatorReader.LineSeparator normalizeLineSeparatorTo )
+    {
+        this.normalizeLineSeparatorTo = normalizeLineSeparatorTo;
+    }
+
+    public void setEncoding( String encoding )
+    {
+        this.encoding = Charset.forName( encoding );
+    }
+
+    @Override
+    public void execute( EnforcerRuleHelper helper )
+        throws EnforcerRuleException
+    {
+        // set defaults
+        if ( encoding == null )
+        {
+            // https://maven.apache.org/plugins/maven-resources-plugin/examples/encoding.html
+            try
+            {
+                String encoding = (String) helper.evaluate( "${project.build.sourceEncoding}" );
+                if ( StringUtils.isBlank( encoding ) )
+                {
+                    encoding = System.getProperty( "file.encoding" );
+                    helper.getLog().warn( "File encoding has not been set, using platform encoding " + encoding
+                        + ". Build is platform dependent!" );
+                }
+                this.encoding = Charset.forName( encoding );
+            }
+            catch ( ExpressionEvaluationException e )
+            {
+                throw new EnforcerRuleException( "Unable to retrieve the project's build source encoding "
+                    + "(${project.build.sourceEncoding}): ", e );
+            }
+        }
+        super.execute( helper );
+    }
+
+    @Override
+    protected String calculateChecksum()
+        throws EnforcerRuleException
+    {
+        try ( Reader reader = new NormalizeLineSeparatorReader( Files.newBufferedReader( file.toPath(), encoding ),
+                                                                normalizeLineSeparatorTo );
+                        InputStream inputStream = new ReaderInputStream( reader, encoding ) )
+        {
+            return super.calculateChecksum( inputStream );
+        }
+        catch ( IOException e )
+        {
+            throw new EnforcerRuleException( "Unable to calculate checksum (with normalized line separators)", e );
+        }
+    }
+}
diff --git a/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/utils/NormalizeLineSeparatorReader.java b/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/utils/NormalizeLineSeparatorReader.java
new file mode 100644
index 0000000..eca36b4
--- /dev/null
+++ b/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/utils/NormalizeLineSeparatorReader.java
@@ -0,0 +1,177 @@
+package org.apache.maven.plugins.enforcer.utils;
+
+/*
+ * 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.
+ */
+
+import java.io.FilterReader;
+import java.io.IOException;
+import java.io.Reader;
+
+/**
+ * Converts Unix line separators to Windows ones and vice-versa.
+ */
+public class NormalizeLineSeparatorReader
+    extends FilterReader
+{
+
+    private static final int EOL = -1;
+
+    /**
+     * Type representing either Unix or Windows line separators
+     */
+    public enum LineSeparator
+    {
+        WINDOWS( "\r\n", null ), UNIX( "\n", '\r' );
+
+        private final char[] separatorChars;
+
+        private final Character notPrecededByChar;
+
+        LineSeparator( String separator, Character notPrecededByChar )
+        {
+            separatorChars = separator.toCharArray();
+            this.notPrecededByChar = notPrecededByChar;
+        }
+
+        enum MatchResult
+        {
+            NO_MATCH,
+            POTENTIAL_MATCH,
+            MATCH;
+        }
+
+        /**
+         * Checks if two given characters match the line separator represented by this object.
+         * @param currentCharacter the character to check against
+         * @param previousCharacter optional previous character (may be {@code null})
+         * @return one of {@link MatchResult}
+         */
+        public MatchResult matches( char currentCharacter, Character previousCharacter )
+        {
+            int len = separatorChars.length;
+            if ( currentCharacter == separatorChars[len - 1] )
+            {
+                if ( len > 1 )
+                {
+                    if ( previousCharacter == null || previousCharacter != separatorChars[len - 1] )
+                    {
+                        return MatchResult.NO_MATCH;
+                    }
+                }
+                if ( notPrecededByChar != null )
+                {
+                    if ( previousCharacter != null && notPrecededByChar == previousCharacter )
+                    {
+                        return MatchResult.NO_MATCH;
+                    }
+                }
+                return MatchResult.MATCH;
+            }
+            else if ( len > 1 && currentCharacter == separatorChars[len - 2] )
+            {
+                return MatchResult.POTENTIAL_MATCH;
+            }
+            return MatchResult.NO_MATCH;
+        }
+    };
+
+    final LineSeparator lineSeparator;
+
+    Character bufferedCharacter;
+
+    Character previousCharacter;
+
+    public NormalizeLineSeparatorReader( Reader reader, LineSeparator lineSeparator )
+    {
+        super( reader );
+        this.lineSeparator = lineSeparator;
+        bufferedCharacter = null;
+    }
+
+    @Override
+    public int read( char[] cbuf, int off, int len )
+        throws IOException
+    {
+        int n;
+        for ( n = off; n < off + len; n++ )
+        {
+            int readResult = read();
+            if ( readResult == EOL )
+            {
+                return n == 0 ? EOL : n;
+            }
+            else
+            {
+                cbuf[n] = (char) readResult;
+            }
+        }
+        return n;
+    }
+
+    @Override
+    public int read()
+        throws IOException
+    {
+        // spool buffered characters, if any
+        if ( bufferedCharacter != null )
+        {
+            char localBuffer = bufferedCharacter;
+            bufferedCharacter = null;
+            return localBuffer;
+        }
+        int readResult = super.read();
+        if ( readResult == EOL )
+        {
+            return readResult;
+        }
+        char currentCharacter = (char) readResult;
+        if ( lineSeparator == LineSeparator.UNIX )
+        {
+            switch ( LineSeparator.WINDOWS.matches( currentCharacter, previousCharacter ) )
+            {
+                case MATCH:
+                    return lineSeparator.separatorChars[0];
+                case POTENTIAL_MATCH:
+                    previousCharacter = currentCharacter;
+                    return read();
+                default:
+                    // fall-through
+            }
+        }
+        else
+        { // WINDOWS
+            // if current is unix, convert
+            switch ( LineSeparator.UNIX.matches( currentCharacter, previousCharacter ) )
+            {
+                case MATCH:
+                    bufferedCharacter = lineSeparator.separatorChars[1];
+                    // set buffered character and return current
+                    return lineSeparator.separatorChars[0];
+                case POTENTIAL_MATCH:
+                    // invalid option
+                    throw new IllegalStateException( "No potential matches expected for Unix line separator" );
+                default:
+                    // fall-through
+            }
+        }
+        previousCharacter = currentCharacter;
+        return currentCharacter;
+    }
+
+}
diff --git a/enforcer-rules/src/site/apt/requireFileChecksum.apt.vm b/enforcer-rules/src/site/apt/requireFileChecksum.apt.vm
index c4606cd..425f0e6 100644
--- a/enforcer-rules/src/site/apt/requireFileChecksum.apt.vm
+++ b/enforcer-rules/src/site/apt/requireFileChecksum.apt.vm
@@ -25,12 +25,15 @@
 
 Require Files Checksum
 
-  This rule checks that the specified file has an given checksum.
+  This rule checks that the specified file has an given checksum. For text files
+  use {{{./requireTextFileChecksum.html}Require Text Files Checksum}} instead.
 
 
    The following parameters are supported by this rule:
 
-   * message - an optional message to the user if the rule fails.
+   * message - an optional message to the user if the rule fails. If not set a default message will be used.
+
+   * nonexistentFileMessage - an optional message to the user if the file is missing. If not set a default message will be used.
 
    * file - A file to check.
 
diff --git a/enforcer-rules/src/site/apt/requireFileChecksum.apt.vm b/enforcer-rules/src/site/apt/requireTextFileChecksum.apt.vm
similarity index 65%
copy from enforcer-rules/src/site/apt/requireFileChecksum.apt.vm
copy to enforcer-rules/src/site/apt/requireTextFileChecksum.apt.vm
index c4606cd..c058f29 100644
--- a/enforcer-rules/src/site/apt/requireFileChecksum.apt.vm
+++ b/enforcer-rules/src/site/apt/requireTextFileChecksum.apt.vm
@@ -16,21 +16,25 @@
 ~~ under the License.
 
   ------
-  Require Files Checksum
+  Require Text Files Checksum
   ------
-  Edward Samson, Lyubomyr Shaydariv
+  Konrad Windszus
   ------
-  February 2016
+  October 2020
   ------
 
-Require Files Checksum
-
-  This rule checks that the specified file has an given checksum.
+Require Text Files Checksum
 
+  This rule checks that the specified text file has an given checksum. For binary files
+  use {{{./requireFileChecksum.html}Require Files Checksum}} instead.
+  To make sure the checksum is the same on all platforms the text file's line separators are normalized
+  to Unix (<<<\n>>>, default) or optionally Windows (<<<\r\n>>>).
 
    The following parameters are supported by this rule:
 
-   * message - an optional message to the user if the rule fails.
+   * message - an optional message to the user if the rule fails. If not set a default message will be used.
+
+   * nonexistentFileMessage - an optional message to the user if the file is missing. If not set a default message will be used.
 
    * file - A file to check.
 
@@ -38,6 +42,10 @@ Require Files Checksum
 
    * type - Type of hashing algorithm to calculate the checksum. May be one of "md5", "sha1", "sha256", "sha384", or "sha512".
 
+   * normalizeLineSeparatorTo - optionally specifies to which line separators to normalize prior to checksum calculation. Either "WINDOWS" or "UNIX". By default "UNIX".
+
+   * encoding - the character encoding used by the file. One of the {{{https://docs.oracle.com/javase/7/docs/api/java/nio/charset/Charset.html#standard}Default Java Charset}} names. By default set to <<<$\{project.build.sourceEncoding\}>>>
+
    []
 
 
@@ -60,31 +68,33 @@ Require Files Checksum
             </goals>
             <configuration>
               <rules>
-                <requireFileChecksum>
+                <requireTextFileChecksum>
                   <file>${project.build.outputDirectory}/foo.txt</file>
                   <checksum>d41d8cd98f00b204e9800998ecf8427e</checksum>
                   <type>md5</type>
-                </requireFileChecksum>
-                <requireFileChecksum>
+                </requireTextFileChecksum>
+                <requireTextFileChecksum>
                   <file>${project.build.outputDirectory}/bar.txt</file>
                   <checksum>da39a3ee5e6b4b0d3255bfef95601890afd80709</checksum>
                   <type>sha1</type>
-                </requireFileChecksum>
-                <requireFileChecksum>
+                  <encoding>UTF-8</encoding>
+                </requireTextFileChecksum>
+                <requireTextFileChecksum>
                   <file>${project.build.outputDirectory}/baz.txt</file>
                   <checksum>e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855</checksum>
                   <type>sha256</type>
-                </requireFileChecksum>
-                <requireFileChecksum>
+                  <normalizeLineSeparatorTo>WINDOWS</normalizeLineSeparatorTo>
+                </requireTextFileChecksum>
+                <requireTextFileChecksum>
                   <file>${project.build.outputDirectory}/qux.txt</file>
                   <checksum>38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b</checksum>
                   <type>sha384</type>
-                </requireFileChecksum>
-                <requireFileChecksum>
+                </requireTextFileChecksum>
+                <requireTextFileChecksum>
                   <file>${project.build.outputDirectory}/quux.txt</file>
                   <checksum>cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e</checksum>
                   <type>sha512</type>
-                </requireFileChecksum>
+                </requireTextFileChecksum>
               </rules>
               <fail>true</fail>
             </configuration>
diff --git a/enforcer-rules/src/test/java/org/apache/maven/plugins/enforcer/TestRequireFileChecksum.java b/enforcer-rules/src/test/java/org/apache/maven/plugins/enforcer/TestRequireFileChecksum.java
index c7075ce..201c5aa 100644
--- a/enforcer-rules/src/test/java/org/apache/maven/plugins/enforcer/TestRequireFileChecksum.java
+++ b/enforcer-rules/src/test/java/org/apache/maven/plugins/enforcer/TestRequireFileChecksum.java
@@ -21,8 +21,10 @@ package org.apache.maven.plugins.enforcer;
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 
 import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
+import org.apache.maven.plugins.enforcer.utils.NormalizeLineSeparatorReader.LineSeparator;
 import org.codehaus.plexus.util.FileUtils;
 import org.junit.Rule;
 import org.junit.Test;
diff --git a/enforcer-rules/src/test/java/org/apache/maven/plugins/enforcer/TestRequireTextFileChecksum.java b/enforcer-rules/src/test/java/org/apache/maven/plugins/enforcer/TestRequireTextFileChecksum.java
new file mode 100644
index 0000000..04fe219
--- /dev/null
+++ b/enforcer-rules/src/test/java/org/apache/maven/plugins/enforcer/TestRequireTextFileChecksum.java
@@ -0,0 +1,128 @@
+package org.apache.maven.plugins.enforcer;
+
+/*
+ * 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.
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
+import org.apache.maven.plugins.enforcer.utils.NormalizeLineSeparatorReader.LineSeparator;
+import org.codehaus.plexus.util.FileUtils;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * Test the "RequireTextFileChecksum" rule
+ *
+ */
+public class TestRequireTextFileChecksum
+{
+
+    private RequireTextFileChecksum rule = new RequireTextFileChecksum();
+
+    @Rule
+    public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+    @Test
+    public void testFileChecksumMd5NormalizedFromUnixToWindows()
+        throws IOException, EnforcerRuleException
+    {
+        File f = temporaryFolder.newFile();
+        FileUtils.fileWrite( f, "line1\nline2\n" );
+
+        rule.setFile( f );
+        rule.setChecksum( "c6242222cf6ccdb15a43e0e5b1a08810" );
+        rule.setType( "md5" );
+        rule.setNormalizeLineSeparatorTo( LineSeparator.WINDOWS );
+        rule.setEncoding( StandardCharsets.US_ASCII.name() );
+
+        rule.execute( EnforcerTestUtils.getHelper() );
+    }
+
+    @Test
+    public void testFileChecksumMd5NormalizedFromWindowsToWindows()
+        throws IOException, EnforcerRuleException
+    {
+        File f = temporaryFolder.newFile();
+        FileUtils.fileWrite( f, "line1\r\nline2\r\n" );
+
+        rule.setFile( f );
+        rule.setChecksum( "c6242222cf6ccdb15a43e0e5b1a08810" );
+        rule.setType( "md5" );
+        rule.setNormalizeLineSeparatorTo( LineSeparator.WINDOWS );
+        rule.setEncoding( StandardCharsets.US_ASCII.name() );
+
+        rule.execute( EnforcerTestUtils.getHelper() );
+    }
+
+    @Test
+    public void testFileChecksumMd5NormalizedFromWindowsToUnix()
+        throws IOException, EnforcerRuleException
+    {
+        File f = temporaryFolder.newFile();
+        FileUtils.fileWrite( f, "line1\r\nline2\r\n" );
+
+        rule.setFile( f );
+        rule.setChecksum( "4fcc82a88ee38e0aa16c17f512c685c9" );
+        rule.setType( "md5" );
+        rule.setNormalizeLineSeparatorTo( LineSeparator.UNIX );
+        rule.setEncoding( StandardCharsets.US_ASCII.name() );
+
+        rule.execute( EnforcerTestUtils.getHelper() );
+    }
+
+    @Test
+    public void testFileChecksumMd5NormalizedFromUnixToUnix()
+        throws IOException, EnforcerRuleException
+    {
+        File f = temporaryFolder.newFile();
+        FileUtils.fileWrite( f, "line1\nline2\n" );
+
+        rule.setFile( f );
+        rule.setChecksum( "4fcc82a88ee38e0aa16c17f512c685c9" );
+        rule.setType( "md5" );
+        rule.setNormalizeLineSeparatorTo( LineSeparator.UNIX );
+        rule.setEncoding( StandardCharsets.US_ASCII.name() );
+
+        rule.execute( EnforcerTestUtils.getHelper() );
+    }
+
+    @Test
+    public void testFileChecksumMd5NormalizedWithMissingFileCharsetParameter()
+        throws IOException, EnforcerRuleException
+    {
+        File f = temporaryFolder.newFile();
+        FileUtils.fileWrite( f, "line1\nline2\n" );
+
+        rule.setFile( f );
+        rule.setChecksum( "4fcc82a88ee38e0aa16c17f512c685c9" );
+        rule.setType( "md5" );
+        rule.setNormalizeLineSeparatorTo( LineSeparator.UNIX );
+
+        rule.execute( EnforcerTestUtils.getHelper() );
+        // name is not unique therefore compare generated charset
+        Assert.assertEquals( Charset.forName( System.getProperty( "file.encoding" ) ), rule.encoding );
+    }
+
+}
diff --git a/enforcer-rules/src/test/java/org/apache/maven/plugins/enforcer/utils/TestNormalizeLineSeparatorReader.java b/enforcer-rules/src/test/java/org/apache/maven/plugins/enforcer/utils/TestNormalizeLineSeparatorReader.java
new file mode 100644
index 0000000..5cfbb1d
--- /dev/null
+++ b/enforcer-rules/src/test/java/org/apache/maven/plugins/enforcer/utils/TestNormalizeLineSeparatorReader.java
@@ -0,0 +1,77 @@
+package org.apache.maven.plugins.enforcer.utils;
+
+/*
+ * 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.
+ */
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.maven.plugins.enforcer.utils.NormalizeLineSeparatorReader.LineSeparator;
+
+public class TestNormalizeLineSeparatorReader
+    extends TestCase
+{
+    private final static String UNIX_MULTILINE_STRING = "line1\nline2\n\n";
+
+    private final static String WINDOWS_MULTILINE_STRING = "line1\r\nline2\r\n\r\n";
+
+    public void testUnixToWindows()
+        throws IOException
+    {
+        try ( Reader reader =
+            new NormalizeLineSeparatorReader( new StringReader( UNIX_MULTILINE_STRING ), LineSeparator.WINDOWS ) )
+        {
+            assertEquals( WINDOWS_MULTILINE_STRING, IOUtils.toString( reader ) );
+        }
+    }
+
+    public void testUnixToUnix()
+        throws IOException
+    {
+        try ( Reader reader =
+            new NormalizeLineSeparatorReader( new StringReader( UNIX_MULTILINE_STRING ), LineSeparator.UNIX ) )
+        {
+            assertEquals( UNIX_MULTILINE_STRING, IOUtils.toString( reader ) );
+        }
+    }
+
+    public void testWindowsToUnix()
+        throws IOException
+    {
+        try ( Reader reader =
+            new NormalizeLineSeparatorReader( new StringReader( WINDOWS_MULTILINE_STRING ), LineSeparator.UNIX ) )
+        {
+            assertEquals( UNIX_MULTILINE_STRING, IOUtils.toString( reader ) );
+        }
+    }
+
+    public void testWindowsToWindows()
+        throws IOException
+    {
+        try ( Reader reader =
+            new NormalizeLineSeparatorReader( new StringReader( WINDOWS_MULTILINE_STRING ), LineSeparator.WINDOWS ) )
+        {
+            assertEquals( WINDOWS_MULTILINE_STRING, IOUtils.toString( reader ) );
+        }
+    }
+}
diff --git a/pom.xml b/pom.xml
index d5aa796..1d1ea38 100644
--- a/pom.xml
+++ b/pom.xml
@@ -133,7 +133,7 @@
       <dependency>
         <groupId>org.apache.commons</groupId>
         <artifactId>commons-lang3</artifactId>
-        <version>3.8.1</version> <!-- commons-lang3 >= 3.8.1 require at least Java 8 -->
+        <version>3.8.1</version> <!-- commons-lang3 > 3.8.1 requires at least Java 8 -->
       </dependency>
       <dependency>
         <groupId>commons-codec</groupId>
@@ -141,6 +141,11 @@
         <version>1.14</version>
       </dependency>
       <dependency>
+        <groupId>commons-io</groupId>
+        <artifactId>commons-io</artifactId>
+        <version>2.5</version><!-- same version as used by maven-shared-utils -->
+      </dependency>
+      <dependency>
         <groupId>org.apache.maven.plugin-testing</groupId>
         <artifactId>maven-plugin-testing-harness</artifactId>
         <version>2.1</version>