Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for sha256, sha384, sha512. #56

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Configuration

### Algorithms

Supported hash algorithms are `md5` and `sha1`. The default is to only create `md5` checksum files. To configure this, modify the `algorithms` setting.
Supported hash algorithms are `md5`, `sha1`, `sha256`, `sha384` and `sha512`.. The default is to only create `md5` checksum files. To configure this, modify the `algorithms` setting.
For example, to also generate`sha1` checksum files:

```scala
Expand Down
170 changes: 170 additions & 0 deletions src/main/java/com/github/sbt/digest/util/ChecksumHelper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/*
* 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 com.github.sbt.digest.util;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import org.apache.ivy.util.FileUtil;

public final class ChecksumHelper {

private static final int BUFFER_SIZE = 2048;
private static Map<String, String> algorithms = new HashMap<>();
static {
algorithms.put("md5", "MD5");
algorithms.put("sha1", "SHA-1");
algorithms.put("sha256", "SHA-256");
algorithms.put("sha384", "SHA-384");
algorithms.put("sha512", "SHA-512");
}

/**
* Checks the checksum of the given file against the given checksumFile, and throws an
* IOException if the checksum is not compliant
*
* @param dest
* the file to test
* @param checksumFile
* the file containing the expected checksum
* @param algorithm
* the checksum algorithm to use
* @throws IOException
* if an IO problem occur whle reading files or if the checksum is not compliant
*/
public static void check(File dest, File checksumFile, String algorithm) throws IOException {
String csFileContent = FileUtil.readEntirely(
new BufferedReader(new FileReader(checksumFile))).trim().toLowerCase(Locale.US);
String expected;
if (csFileContent.indexOf(' ') > -1
&& (csFileContent.startsWith("md") || csFileContent.startsWith("sha"))) {
int lastSpaceIndex = csFileContent.lastIndexOf(' ');
expected = csFileContent.substring(lastSpaceIndex + 1);
} else {
int spaceIndex = csFileContent.indexOf(' ');
if (spaceIndex != -1) {
expected = csFileContent.substring(0, spaceIndex);

// IVY-1155: support some strange formats like this one:
// http://repo2.maven.org/maven2/org/apache/pdfbox/fontbox/0.8.0-incubator/fontbox-0.8.0-incubator.jar.md5
if (expected.endsWith(":")) {
StringBuffer result = new StringBuffer();
char[] chars = csFileContent.substring(spaceIndex + 1).toCharArray();
for (int i = 0; i < chars.length; i++) {
if (!Character.isWhitespace(chars[i])) {
result.append(chars[i]);
}
}
expected = result.toString();
}
} else {
expected = csFileContent;
}
}

String computed = computeAsString(dest, algorithm).trim().toLowerCase(Locale.US);
if (!expected.equals(computed)) {
throw new IOException("invalid " + algorithm + ": expected=" + expected + " computed="
+ computed);
}
}

public static String computeAsString(File f, String algorithm) throws IOException {
return byteArrayToHexString(compute(f, algorithm));
}

private static byte[] compute(File f, String algorithm) throws IOException {
InputStream is = new FileInputStream(f);

try {
MessageDigest md = getMessageDigest(algorithm);
md.reset();

byte[] buf = new byte[BUFFER_SIZE];
int len = 0;
while ((len = is.read(buf)) != -1) {
md.update(buf, 0, len);
}
return md.digest();
} finally {
is.close();
}
}

public static boolean isKnownAlgorithm(String algorithm) {
return algorithms.containsKey(algorithm);
}

private static MessageDigest getMessageDigest(String algorithm) {
String mdAlgorithm = (String) algorithms.get(algorithm);
if (mdAlgorithm == null) {
throw new IllegalArgumentException("unknown algorithm " + algorithm);
}
try {
return MessageDigest.getInstance(mdAlgorithm);
} catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException("unknown algorithm " + algorithm);
}
}

// byte to hex string converter
private static final char[] CHARS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f'};

/**
* Convert a byte[] array to readable string format. This makes the "hex" readable!
*
* @return result String buffer in String format
* @param in
* byte[] buffer to convert to string format
*/
public static String byteArrayToHexString(byte[] in) {
byte ch = 0x00;

if (in == null || in.length <= 0) {
return null;
}

StringBuffer out = new StringBuffer(in.length * 2);

//CheckStyle:MagicNumber OFF
for (int i = 0; i < in.length; i++) {
ch = (byte) (in[i] & 0xF0); // Strip off high nibble
ch = (byte) (ch >>> 4); // shift the bits down
ch = (byte) (ch & 0x0F); // must do this is high order bit is on!

out.append(CHARS[(int) ch]); // convert the nibble to a String Character
ch = (byte) (in[i] & 0x0F); // Strip off low nibble
out.append(CHARS[(int) ch]); // convert the nibble to a String Character
}
//CheckStyle:MagicNumber ON

return out.toString();
}

private ChecksumHelper() {
}
}
6 changes: 3 additions & 3 deletions src/main/scala/com/typesafe/sbt/digest/SbtDigest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import com.typesafe.sbt.web.{PathMapping, SbtWeb}
import com.typesafe.sbt.web.js.JS
import com.typesafe.sbt.web.pipeline.Pipeline
import sbt.Keys._
import org.apache.ivy.util.ChecksumHelper
import com.github.sbt.digest.util.ChecksumHelper
import sbt.Task

object Import {
Expand Down Expand Up @@ -64,7 +64,7 @@ object SbtDigest extends AutoPlugin {

object DigestStage {

val DigestAlgorithms = Seq("md5", "sha1")
val DigestAlgorithms = Seq("md5", "sha1", "sha256", "sha384", "sha512")

sealed trait DigestMapping {
def originalPath: String
Expand Down Expand Up @@ -193,7 +193,7 @@ object SbtDigest extends AutoPlugin {
}

/**
* Compute a checksum for a file. Supported algorithms are "md5" and "sha1".
* Compute a checksum for a file. Supported algorithms are "md5", "sha1", "sha256", "sha384" and "sha512".
*/
def computeChecksum(file: File, algorithm: String): String = {
ChecksumHelper.computeAsString(file, algorithm)
Expand Down
22 changes: 22 additions & 0 deletions src/sbt-test/digest/sha256/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
val root = (project in file(".")).enablePlugins(SbtWeb)

pipelineStages := Seq(digest)

// also create sha256 files

DigestKeys.algorithms += "sha256"

// for checking that the produced pipeline mappings are correct

val expected = Set(
"css", "css/a.css", "css/a.css.md5", "css/a.css.sha256", "css/46a3aa6d97cccb6b28233d8e55ce4350-a.css", "css/7d2cfc655a208a03f1fdc5c8afdfa9721a65fb743ca37f565b6af09de3d356b5-a.css",
"js", "js/a.js", "js/a.js.md5", "js/a.js.sha256", "js/2d4ecd06cd4648614f3f4f1bfc262512-a.js", "js/1ac0e4aa4363591314c2d539063cb5d8ac123088451795b1340ad7e4ac1d8204-a.js"
)

val checkMappings = taskKey[Unit]("check the pipeline mappings")

checkMappings := {
val mappings = WebKeys.pipeline.value
val paths = (mappings map (_._2)).toSet
if (paths != expected) sys.error(s"Expected $expected but pipeline paths are $paths")
}
1 change: 1 addition & 0 deletions src/sbt-test/digest/sha256/project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
addSbtPlugin("com.github.sbt" % "sbt-digest" % sys.props("project.version"))
1 change: 1 addition & 0 deletions src/sbt-test/digest/sha256/src/main/public/css/a.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
body { background: #000000 }
1 change: 1 addition & 0 deletions src/sbt-test/digest/sha256/src/main/public/js/a.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
var test = function () {}
16 changes: 16 additions & 0 deletions src/sbt-test/digest/sha256/test
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
> checkMappings
> webStage

$ exists target/web/digest/css/a.css.md5
$ exists target/web/digest/css/a.css.sha256

$ exists target/web/digest/js/a.js.md5
$ exists target/web/digest/js/a.js.sha256

$ exists target/web/stage/css/a.css
$ exists target/web/stage/css/a.css.md5
$ exists target/web/stage/css/a.css.sha256

$ exists target/web/stage/js/a.js
$ exists target/web/stage/js/a.js.md5
$ exists target/web/stage/js/a.js.sha256
22 changes: 22 additions & 0 deletions src/sbt-test/digest/sha384/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
val root = (project in file(".")).enablePlugins(SbtWeb)

pipelineStages := Seq(digest)

// also create sha256 files

DigestKeys.algorithms += "sha384"

// for checking that the produced pipeline mappings are correct

val expected = Set(
"css", "css/a.css", "css/a.css.md5", "css/a.css.sha384", "css/46a3aa6d97cccb6b28233d8e55ce4350-a.css", "css/efa4b005ec16da2e6124ab6fb1c7a11329f7eda45d3d9972b27995b94a09c048c54a408308115d7118d21ee18878e4e9-a.css",
"js", "js/a.js", "js/a.js.md5", "js/a.js.sha384", "js/2d4ecd06cd4648614f3f4f1bfc262512-a.js", "js/dd3b40496deaa6e6ca76b6a6bb145f966839ed08d812cfbc297c312f31c819c96afa750b34a2bfad337a0622172307e4-a.js"
)

val checkMappings = taskKey[Unit]("check the pipeline mappings")

checkMappings := {
val mappings = WebKeys.pipeline.value
val paths = (mappings map (_._2)).toSet
if (paths != expected) sys.error(s"Expected $expected but pipeline paths are $paths")
}
1 change: 1 addition & 0 deletions src/sbt-test/digest/sha384/project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
addSbtPlugin("com.github.sbt" % "sbt-digest" % sys.props("project.version"))
1 change: 1 addition & 0 deletions src/sbt-test/digest/sha384/src/main/public/css/a.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
body { background: #000000 }
1 change: 1 addition & 0 deletions src/sbt-test/digest/sha384/src/main/public/js/a.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
var test = function () {}
16 changes: 16 additions & 0 deletions src/sbt-test/digest/sha384/test
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
> checkMappings
> webStage

$ exists target/web/digest/css/a.css.md5
$ exists target/web/digest/css/a.css.sha384

$ exists target/web/digest/js/a.js.md5
$ exists target/web/digest/js/a.js.sha384

$ exists target/web/stage/css/a.css
$ exists target/web/stage/css/a.css.md5
$ exists target/web/stage/css/a.css.sha384

$ exists target/web/stage/js/a.js
$ exists target/web/stage/js/a.js.md5
$ exists target/web/stage/js/a.js.sha384
22 changes: 22 additions & 0 deletions src/sbt-test/digest/sha512/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
val root = (project in file(".")).enablePlugins(SbtWeb)

pipelineStages := Seq(digest)

// also create sha256 files

DigestKeys.algorithms += "sha512"

// for checking that the produced pipeline mappings are correct

val expected = Set(
"css", "css/a.css", "css/a.css.md5", "css/a.css.sha512", "css/46a3aa6d97cccb6b28233d8e55ce4350-a.css", "css/58b6708177852235ee2a17000347395ea371b7456c907e4fc47d7b42694ae01dad0f946bf6def6fc0164e6aef68de50d0c64edc45b46805079c3ab98e837f33e-a.css",
"js", "js/a.js", "js/a.js.md5", "js/a.js.sha512", "js/2d4ecd06cd4648614f3f4f1bfc262512-a.js", "js/346677e51f420a89577ec4f66a0eec2872a6fef3f0f54f45a61115fdc2f650e59c97916618ba79671f977cbd51f1557bc37338451d31cec2380d274480c9509f-a.js"
)

val checkMappings = taskKey[Unit]("check the pipeline mappings")

checkMappings := {
val mappings = WebKeys.pipeline.value
val paths = (mappings map (_._2)).toSet
if (paths != expected) sys.error(s"Expected $expected but pipeline paths are $paths")
}
1 change: 1 addition & 0 deletions src/sbt-test/digest/sha512/project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
addSbtPlugin("com.github.sbt" % "sbt-digest" % sys.props("project.version"))
1 change: 1 addition & 0 deletions src/sbt-test/digest/sha512/src/main/public/css/a.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
body { background: #000000 }
1 change: 1 addition & 0 deletions src/sbt-test/digest/sha512/src/main/public/js/a.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
var test = function () {}
16 changes: 16 additions & 0 deletions src/sbt-test/digest/sha512/test
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
> checkMappings
> webStage

$ exists target/web/digest/css/a.css.md5
$ exists target/web/digest/css/a.css.sha512

$ exists target/web/digest/js/a.js.md5
$ exists target/web/digest/js/a.js.sha512

$ exists target/web/stage/css/a.css
$ exists target/web/stage/css/a.css.md5
$ exists target/web/stage/css/a.css.sha512

$ exists target/web/stage/js/a.js
$ exists target/web/stage/js/a.js.md5
$ exists target/web/stage/js/a.js.sha512