package org.dcache.util;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.Maps.EntryTransformer;
import com.google.common.collect.Ordering;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Base64;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import static com.google.common.base.Strings.nullToEmpty;
import static com.google.common.collect.Collections2.transform;
import static com.google.common.collect.Maps.transformEntries;
import static org.dcache.util.ChecksumType.*;
/**
* Class containing utility methods for operating on checksum values
*/
public class Checksums
{
private static final Logger _log = LoggerFactory.getLogger(Checksums.class);
private static final Splitter.MapSplitter RFC3230_SPLITTER =
Splitter.on(',').omitEmptyStrings().trimResults().
withKeyValueSeparator(Splitter.on('=').limit(2));
private static final EntryTransformer<String,String,Checksum>
RFC3230_TO_CHECKSUM = (type, value) -> {
try {
/*
* These names are defined in RFC-3230 and
* http://www.iana.org/assignments/http-dig-alg/http-dig-alg.xml
*/
switch(type.toLowerCase()) {
case "adler32":
return new Checksum(ChecksumType.ADLER32, value);
case "md5":
byte[] bytes = Base64.getDecoder().decode(value);
return new Checksum(ChecksumType.MD5_TYPE, bytes);
default:
_log.debug("Unsupported checksum type {}", type);
return null;
}
} catch(IllegalArgumentException e) {
_log.debug("Value \"{}\" is invalid for type {}", value,
type);
return null;
}
};
private static final Ordering<ChecksumType> PREFERRED_CHECKSUM_TYPE_ORDERING =
Ordering.explicit(MD5_TYPE, ADLER32, MD4_TYPE);
private static final Ordering<Checksum> PREFERRED_CHECKSUM_ORDERING =
PREFERRED_CHECKSUM_TYPE_ORDERING.onResultOf(Checksum::getType);
/**
* This Function maps an instance of Checksum to the corresponding
* fragment of an RFC 3230 response.
*/
private static final Function<Checksum,String> TO_RFC3230_FRAGMENT =
f -> {
byte[] bytes = f.getBytes();
switch(f.getType()) {
case ADLER32:
return "adler32=" + Checksum.bytesToHexString(bytes);
case MD4_TYPE:
return null;
case MD5_TYPE:
return "md5=" + Base64.getEncoder().encodeToString(bytes);
default:
return null;
}
};
/**
* This Function maps a collection of Checksum objects to the corresponding
* RFC 3230 string. For further details, see:
*
* http://tools.ietf.org/html/rfc3230
* http://www.iana.org/assignments/http-dig-alg/http-dig-alg.xml
*/
public static final Function<Collection<Checksum>,String> TO_RFC3230 =
checksums -> Joiner.on(',').skipNulls().join(transform(checksums, TO_RFC3230_FRAGMENT));
private Checksums()
{
// prevent instantiation
}
/**
* Parse the RFC-3230 Digest response header value. If there is no
* understandable checksum or null is supplied then an empty set is
* returned.
* @param digest The Digest header value
* @return the decoded checksum values
*/
public static Set<Checksum> decodeRfc3230(String digest)
{
Map<String,String> parts = RFC3230_SPLITTER.split(nullToEmpty(digest));
Map<String,Checksum> checksums = transformEntries(parts,
RFC3230_TO_CHECKSUM);
return checksums.values().stream().filter(Objects::nonNull).collect(Collectors.toSet());
}
public static Ordering<Checksum> preferrredOrder()
{
return PREFERRED_CHECKSUM_ORDERING;
}
}