001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.codec.cli; 019 020import java.io.BufferedInputStream; 021import java.io.File; 022import java.io.IOException; 023import java.nio.charset.Charset; 024import java.security.MessageDigest; 025import java.util.Arrays; 026import java.util.Locale; 027import java.util.Objects; 028 029import org.apache.commons.codec.binary.Hex; 030import org.apache.commons.codec.digest.DigestUtils; 031import org.apache.commons.codec.digest.MessageDigestAlgorithms; 032 033/** 034 * A minimal command line to run digest over files, directories or a string. 035 * 036 * @see #main(String[]) 037 * @since 1.11 038 */ 039public class Digest { 040 041 private static final String EMPTY = ""; 042 043 /** 044 * Runs the digest algorithm in {@code args[0]} on the file in {@code args[1]}. If there is no {@code args[1]}, use standard input. 045 * 046 * <p> 047 * The algorithm can also be {@code ALL} or {@code *} to output one line for each known algorithm. 048 * </p> 049 * 050 * @param args {@code args[0]} is one of {@link MessageDigestAlgorithms} name, {@link MessageDigest} name, {@code ALL}, or {@code *}. {@code args[1+]} is a 051 * FILE/DIRECTORY/String. 052 * @throws IOException if an error occurs. 053 */ 054 public static void main(final String[] args) throws IOException { 055 new Digest(args).run(); 056 } 057 058 private final String algorithm; 059 private final String[] args; 060 private final String[] inputs; 061 062 private Digest(final String[] args) { 063 Objects.requireNonNull(args, "args"); 064 final int argsLength = args.length; 065 if (argsLength == 0) { 066 throw new IllegalArgumentException(String.format("Usage: java %s [algorithm] [FILE|DIRECTORY|string] ...", Digest.class.getName())); 067 } 068 this.args = args; 069 this.algorithm = args[0]; 070 this.inputs = argsLength > 1 ? Arrays.copyOfRange(args, 1, argsLength) : null; 071 } 072 073 private void println(final String prefix, final byte[] digest) { 074 println(prefix, digest, null); 075 } 076 077 private void println(final String prefix, final byte[] digest, final String fileName) { 078 // The standard appears to be to print 079 // hex, space, then either space or '*' followed by file name 080 // where '*' is used for binary files 081 // shasum(1) has a -b option which generates " *" separator 082 // we don't distinguish binary files at present 083 System.out.println(prefix + Hex.encodeHexString(digest) + (fileName != null ? " " + fileName : EMPTY)); 084 } 085 086 private void run() throws IOException { 087 final BufferedInputStream systemIn = inputs != null ? null : new BufferedInputStream(System.in); 088 if (algorithm.equalsIgnoreCase("ALL") || algorithm.equals("*")) { 089 run(systemIn, MessageDigestAlgorithms.values()); 090 return; 091 } 092 final MessageDigest messageDigest = DigestUtils.getDigest(algorithm, null); 093 if (messageDigest != null) { 094 run(systemIn, EMPTY, messageDigest); 095 } else { 096 run(systemIn, EMPTY, DigestUtils.getDigest(algorithm.toUpperCase(Locale.ROOT))); 097 } 098 } 099 100 private void run(final BufferedInputStream systemIn, final String prefix, final MessageDigest messageDigest) throws IOException { 101 if (inputs == null) { 102 println(prefix, DigestUtils.digest(messageDigest, systemIn)); 103 return; 104 } 105 for (final String source : inputs) { 106 final File file = new File(source); 107 if (file.isFile()) { 108 println(prefix, DigestUtils.digest(messageDigest, file), source); 109 } else if (file.isDirectory()) { 110 final File[] listFiles = file.listFiles(); 111 if (listFiles != null) { 112 run(prefix, messageDigest, listFiles); 113 } 114 } else { 115 // use the default charset for the command-line parameter 116 final byte[] bytes = source.getBytes(Charset.defaultCharset()); 117 println(prefix, DigestUtils.digest(messageDigest, bytes)); 118 } 119 } 120 } 121 122 private void run(final BufferedInputStream systemIn, final String prefix, final String messageDigestAlgorithm) throws IOException { 123 run(systemIn, prefix, DigestUtils.getDigest(messageDigestAlgorithm)); 124 } 125 126 private void run(final BufferedInputStream systemIn, final String[] digestAlgorithms) throws IOException { 127 for (final String messageDigestAlgorithm : digestAlgorithms) { 128 if (DigestUtils.isAvailable(messageDigestAlgorithm)) { 129 if (systemIn != null) { 130 // 1 GB arbitrary default. 131 systemIn.mark(Integer.getInteger(getClass().getName() + ".markReadLimit", 1_073_741_824)); 132 } 133 run(systemIn, messageDigestAlgorithm + " ", messageDigestAlgorithm); 134 if (systemIn != null) { 135 systemIn.reset(); 136 } 137 } 138 } 139 } 140 141 private void run(final String prefix, final MessageDigest messageDigest, final File[] files) throws IOException { 142 for (final File file : files) { 143 if (file.isFile()) { 144 println(prefix, DigestUtils.digest(messageDigest, file), file.getName()); 145 } 146 } 147 } 148 149 @Override 150 public String toString() { 151 return String.format("%s %s", super.toString(), Arrays.toString(args)); 152 } 153}