Skip to content

Commit 96616b0

Browse files
authored
Merge pull request #56 from mebur/main
Add support for sha256, sha384, sha512.
2 parents 8f49c73 + 3f3f4e4 commit 96616b0

File tree

18 files changed

+323
-5
lines changed

18 files changed

+323
-5
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ Configuration
4242

4343
### Algorithms
4444

45-
Supported hash algorithms are `md5` and `sha1`. The default is to only create `md5` checksum files. To configure this, modify the `algorithms` setting.
45+
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.
4646
For example, to also generate`sha1` checksum files:
4747

4848
```scala
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
package com.github.sbt.digest.util;
19+
20+
import java.io.BufferedReader;
21+
import java.io.File;
22+
import java.io.FileInputStream;
23+
import java.io.FileReader;
24+
import java.io.IOException;
25+
import java.io.InputStream;
26+
import java.security.MessageDigest;
27+
import java.security.NoSuchAlgorithmException;
28+
import java.util.HashMap;
29+
import java.util.Locale;
30+
import java.util.Map;
31+
import org.apache.ivy.util.FileUtil;
32+
33+
public final class ChecksumHelper {
34+
35+
private static final int BUFFER_SIZE = 2048;
36+
private static final Map<String, String> algorithms = new HashMap<>();
37+
static {
38+
algorithms.put("md5", "MD5");
39+
algorithms.put("sha1", "SHA-1");
40+
41+
// higher versions of JRE support these algorithms
42+
// https://docs.oracle.com/javase/6/docs/technotes/guides/security/StandardNames.html#MessageDigest
43+
// conditionally add them
44+
if (isAlgorithmSupportedInJRE("SHA-256")) {
45+
algorithms.put("SHA-256", "SHA-256");
46+
}
47+
if (isAlgorithmSupportedInJRE("SHA-512")) {
48+
algorithms.put("SHA-512", "SHA-512");
49+
}
50+
if (isAlgorithmSupportedInJRE("SHA-384")) {
51+
algorithms.put("SHA-384", "SHA-384");
52+
}
53+
54+
}
55+
56+
private static boolean isAlgorithmSupportedInJRE(final String algorithm) {
57+
if (algorithm == null) {
58+
return false;
59+
}
60+
try {
61+
MessageDigest.getInstance(algorithm);
62+
return true;
63+
} catch (NoSuchAlgorithmException e) {
64+
return false;
65+
}
66+
}
67+
68+
/**
69+
* Checks the checksum of the given file against the given checksumFile, and throws an
70+
* IOException if the checksum is not compliant
71+
*
72+
* @param dest
73+
* the file to test
74+
* @param checksumFile
75+
* the file containing the expected checksum
76+
* @param algorithm
77+
* the checksum algorithm to use
78+
* @throws IOException
79+
* if an IO problem occur while reading files or if the checksum is not compliant
80+
*/
81+
public static void check(File dest, File checksumFile, String algorithm) throws IOException {
82+
String csFileContent = FileUtil
83+
.readEntirely(new BufferedReader(new FileReader(checksumFile))).trim()
84+
.toLowerCase(Locale.US);
85+
String expected;
86+
if (csFileContent.indexOf(' ') > -1
87+
&& (csFileContent.startsWith("md") || csFileContent.startsWith("sha"))) {
88+
int lastSpaceIndex = csFileContent.lastIndexOf(' ');
89+
expected = csFileContent.substring(lastSpaceIndex + 1);
90+
} else {
91+
int spaceIndex = csFileContent.indexOf(' ');
92+
if (spaceIndex != -1) {
93+
expected = csFileContent.substring(0, spaceIndex);
94+
95+
// IVY-1155: support some strange formats like this one:
96+
// https://repo1.maven.org/maven2/org/apache/pdfbox/fontbox/0.8.0-incubator/fontbox-0.8.0-incubator.jar.md5
97+
if (expected.endsWith(":")) {
98+
StringBuilder result = new StringBuilder();
99+
for (char ch : csFileContent.substring(spaceIndex + 1).toCharArray()) {
100+
if (!Character.isWhitespace(ch)) {
101+
result.append(ch);
102+
}
103+
}
104+
expected = result.toString();
105+
}
106+
} else {
107+
expected = csFileContent;
108+
}
109+
}
110+
111+
String computed = computeAsString(dest, algorithm).trim().toLowerCase(Locale.US);
112+
if (!expected.equals(computed)) {
113+
throw new IOException("invalid " + algorithm + ": expected=" + expected + " computed="
114+
+ computed);
115+
}
116+
}
117+
118+
public static String computeAsString(File f, String algorithm) throws IOException {
119+
return byteArrayToHexString(compute(f, algorithm));
120+
}
121+
122+
private static byte[] compute(File f, String algorithm) throws IOException {
123+
124+
try (InputStream is = new FileInputStream(f)) {
125+
MessageDigest md = getMessageDigest(algorithm);
126+
md.reset();
127+
128+
byte[] buf = new byte[BUFFER_SIZE];
129+
int len = 0;
130+
while ((len = is.read(buf)) != -1) {
131+
md.update(buf, 0, len);
132+
}
133+
return md.digest();
134+
}
135+
}
136+
137+
public static boolean isKnownAlgorithm(String algorithm) {
138+
return algorithms.containsKey(algorithm);
139+
}
140+
141+
private static MessageDigest getMessageDigest(String algorithm) {
142+
String mdAlgorithm = algorithms.get(algorithm);
143+
if (mdAlgorithm == null) {
144+
throw new IllegalArgumentException("unknown algorithm " + algorithm);
145+
}
146+
try {
147+
return MessageDigest.getInstance(mdAlgorithm);
148+
} catch (NoSuchAlgorithmException e) {
149+
throw new IllegalArgumentException("unknown algorithm " + algorithm);
150+
}
151+
}
152+
153+
// byte to hex string converter
154+
private static final char[] CHARS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a',
155+
'b', 'c', 'd', 'e', 'f'};
156+
157+
/**
158+
* Convert a byte[] array to readable string format. This makes the "hex" readable!
159+
*
160+
* @return result String buffer in String format
161+
* @param in
162+
* byte[] buffer to convert to string format
163+
*/
164+
public static String byteArrayToHexString(byte[] in) {
165+
byte ch = 0x00;
166+
167+
if (in == null || in.length <= 0) {
168+
return null;
169+
}
170+
171+
StringBuilder out = new StringBuilder(in.length * 2);
172+
173+
// CheckStyle:MagicNumber OFF
174+
for (byte bt : in) {
175+
ch = (byte) (bt & 0xF0); // Strip off high nibble
176+
ch = (byte) (ch >>> 4); // shift the bits down
177+
ch = (byte) (ch & 0x0F); // must do this is high order bit is on!
178+
179+
out.append(CHARS[ch]); // convert the nibble to a String Character
180+
ch = (byte) (bt & 0x0F); // Strip off low nibble
181+
out.append(CHARS[ch]); // convert the nibble to a String Character
182+
}
183+
// CheckStyle:MagicNumber ON
184+
185+
return out.toString();
186+
}
187+
188+
private ChecksumHelper() {
189+
}
190+
}

src/main/scala/com/typesafe/sbt/digest/SbtDigest.scala

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import com.typesafe.sbt.web.{PathMapping, SbtWeb}
55
import com.typesafe.sbt.web.js.JS
66
import com.typesafe.sbt.web.pipeline.Pipeline
77
import sbt.Keys._
8-
import org.apache.ivy.util.ChecksumHelper
8+
import com.github.sbt.digest.util.ChecksumHelper
99
import sbt.Task
1010

1111
object Import {
@@ -64,7 +64,7 @@ object SbtDigest extends AutoPlugin {
6464

6565
object DigestStage {
6666

67-
val DigestAlgorithms = Seq("md5", "sha1")
67+
val DigestAlgorithms = Seq("md5", "sha1", "sha256", "sha384", "sha512")
6868

6969
sealed trait DigestMapping {
7070
def originalPath: String
@@ -193,10 +193,15 @@ object SbtDigest extends AutoPlugin {
193193
}
194194

195195
/**
196-
* Compute a checksum for a file. Supported algorithms are "md5" and "sha1".
196+
* Compute a checksum for a file. Supported algorithms are "md5", "sha1", "sha256", "sha384" and "sha512".
197197
*/
198198
def computeChecksum(file: File, algorithm: String): String = {
199-
ChecksumHelper.computeAsString(file, algorithm)
199+
ChecksumHelper.computeAsString(file, algorithm match {
200+
case "sha256" => "SHA-256"
201+
case "sha384" => "SHA-384"
202+
case "sha512" => "SHA-512"
203+
case alg: String => alg
204+
})
200205
}
201206

202207
/**
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
val root = (project in file(".")).enablePlugins(SbtWeb)
2+
3+
pipelineStages := Seq(digest)
4+
5+
// also create sha256 files
6+
7+
DigestKeys.algorithms += "sha256"
8+
9+
// for checking that the produced pipeline mappings are correct
10+
11+
val expected = Set(
12+
"css", "css/a.css", "css/a.css.md5", "css/a.css.sha256", "css/46a3aa6d97cccb6b28233d8e55ce4350-a.css", "css/7d2cfc655a208a03f1fdc5c8afdfa9721a65fb743ca37f565b6af09de3d356b5-a.css",
13+
"js", "js/a.js", "js/a.js.md5", "js/a.js.sha256", "js/2d4ecd06cd4648614f3f4f1bfc262512-a.js", "js/1ac0e4aa4363591314c2d539063cb5d8ac123088451795b1340ad7e4ac1d8204-a.js"
14+
)
15+
16+
val checkMappings = taskKey[Unit]("check the pipeline mappings")
17+
18+
checkMappings := {
19+
val mappings = WebKeys.pipeline.value
20+
val paths = (mappings map (_._2)).toSet
21+
if (paths != expected) sys.error(s"Expected $expected but pipeline paths are $paths")
22+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
addSbtPlugin("com.github.sbt" % "sbt-digest" % sys.props("project.version"))
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
body { background: #000000 }
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
var test = function () {}

src/sbt-test/digest/sha256/test

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
> checkMappings
2+
> webStage
3+
4+
$ exists target/web/digest/css/a.css.md5
5+
$ exists target/web/digest/css/a.css.sha256
6+
7+
$ exists target/web/digest/js/a.js.md5
8+
$ exists target/web/digest/js/a.js.sha256
9+
10+
$ exists target/web/stage/css/a.css
11+
$ exists target/web/stage/css/a.css.md5
12+
$ exists target/web/stage/css/a.css.sha256
13+
14+
$ exists target/web/stage/js/a.js
15+
$ exists target/web/stage/js/a.js.md5
16+
$ exists target/web/stage/js/a.js.sha256
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
val root = (project in file(".")).enablePlugins(SbtWeb)
2+
3+
pipelineStages := Seq(digest)
4+
5+
// also create sha256 files
6+
7+
DigestKeys.algorithms += "sha384"
8+
9+
// for checking that the produced pipeline mappings are correct
10+
11+
val expected = Set(
12+
"css", "css/a.css", "css/a.css.md5", "css/a.css.sha384", "css/46a3aa6d97cccb6b28233d8e55ce4350-a.css", "css/efa4b005ec16da2e6124ab6fb1c7a11329f7eda45d3d9972b27995b94a09c048c54a408308115d7118d21ee18878e4e9-a.css",
13+
"js", "js/a.js", "js/a.js.md5", "js/a.js.sha384", "js/2d4ecd06cd4648614f3f4f1bfc262512-a.js", "js/dd3b40496deaa6e6ca76b6a6bb145f966839ed08d812cfbc297c312f31c819c96afa750b34a2bfad337a0622172307e4-a.js"
14+
)
15+
16+
val checkMappings = taskKey[Unit]("check the pipeline mappings")
17+
18+
checkMappings := {
19+
val mappings = WebKeys.pipeline.value
20+
val paths = (mappings map (_._2)).toSet
21+
if (paths != expected) sys.error(s"Expected $expected but pipeline paths are $paths")
22+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
addSbtPlugin("com.github.sbt" % "sbt-digest" % sys.props("project.version"))

0 commit comments

Comments
 (0)