toolkit.model.37.0.0.source-code.Hash.kt Maven / Gradle / Ivy
/*
* Copyright (C) 2017 The ORT Project Authors (see )
*
* Licensed 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
*
* https://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.
*
* SPDX-License-Identifier: Apache-2.0
* License-Filename: LICENSE
*/
package org.ossreviewtoolkit.model
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.fasterxml.jackson.databind.util.StdConverter
import java.io.File
import kotlin.io.encoding.Base64
/**
* A class that bundles a hash algorithm with its hash value.
*/
data class Hash(
/**
* The value calculated using the hash algorithm.
*/
@JsonSerialize(converter = StringLowercaseConverter::class)
val value: String,
/**
* The algorithm used to calculate the hash value.
*/
val algorithm: HashAlgorithm
) {
companion object {
/**
* A constant to specify that no hash value (and thus also no hash algorithm) is provided.
*/
val NONE = Hash(HashAlgorithm.NONE.toString(), HashAlgorithm.NONE)
/**
* Create a [Hash] instance from a known hash [value]. If the [HashAlgorithm] cannot be determined, the original
* [value] is returned with [HashAlgorithm.UNKNOWN], or with [HashAlgorithm.NONE] if the value is blank.
*/
fun create(value: String): Hash {
val splitValue = value.split('-')
return if (splitValue.size == 2) {
// Support Subresource Integrity (SRI) hashes, see
// https://w3c.github.io/webappsec-subresource-integrity/
Hash(
value = Base64.decode(splitValue.last()).toHexString(),
algorithm = HashAlgorithm.fromString(splitValue.first())
)
} else {
Hash(value.lowercase(), HashAlgorithm.create(value))
}
}
}
init {
require(value.length == algorithm.size || algorithm == HashAlgorithm.UNKNOWN) {
"'$value' is not a $algorithm hash."
}
}
/**
* Construct a [Hash] instance from hash [value] and [algorithm] strings.
*/
constructor(value: String, algorithm: String) : this(value, HashAlgorithm.fromString(algorithm))
/**
* Return the hash in Support Subresource Integrity (SRI) format.
*/
fun toSri() = algorithm.name.lowercase() + "-" + Base64.encode(value.hexToByteArray())
/**
* Verify that the [file] matches this hash.
*/
fun verify(file: File): Boolean {
require(algorithm in HashAlgorithm.VERIFIABLE) {
"Cannot verify algorithm '$algorithm'. Supported algorithms are ${HashAlgorithm.VERIFIABLE}."
}
return algorithm.calculate(file).equals(value, ignoreCase = true)
}
/**
* Verify that the provided [hash] matches this hash.
*/
fun verify(hash: Hash): Boolean = algorithm == hash.algorithm && value.equals(hash.value, ignoreCase = true)
}
private class StringLowercaseConverter : StdConverter() {
override fun convert(value: String): String = value.lowercase()
}