Skip to content
Merged
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
8 changes: 4 additions & 4 deletions examples/write_tv.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from spdx.review import Review
from spdx.package import Package
from spdx.file import File, FileType
from spdx.checksum import Algorithm
from spdx.checksum import Checksum
from spdx.utils import SPDXNone, NoAssert, UnKnown

doc = Document()
Expand All @@ -36,7 +36,7 @@
testfile1.type = FileType.BINARY
testfile1.spdx_id = "TestFilet#SPDXRef-FILE"
testfile1.comment = "This is a test file."
testfile1.chksum = Algorithm("SHA1", "c537c5d99eca5333f23491d47ededd083fefb7ad")
testfile1.chksum = Checksum("SHA1", "c537c5d99eca5333f23491d47ededd083fefb7ad")
testfile1.conc_lics = License.from_identifier("BSD-2-Clause")
testfile1.add_lics(License.from_identifier("BSD-2-Clause"))
testfile1.copyright = SPDXNone()
Expand All @@ -48,7 +48,7 @@
testfile2.type = FileType.SOURCE
testfile2.spdx_id = "TestFile2#SPDXRef-FILE"
testfile2.comment = "This is a test file."
testfile2.chksum = Algorithm("SHA1", "bb154f28d1cf0646ae21bb0bec6c669a2b90e113")
testfile2.chksum = Checksum("SHA1", "bb154f28d1cf0646ae21bb0bec6c669a2b90e113")
testfile2.conc_lics = License.from_identifier("Apache-2.0")
testfile2.add_lics(License.from_identifier("Apache-2.0"))
testfile2.copyright = NoAssert()
Expand All @@ -62,7 +62,7 @@
package.file_name = "twt.jar"
package.spdx_id = 'TestPackage#SPDXRef-PACKAGE'
package.download_location = "http://www.tagwritetest.test/download"
package.checksum = Algorithm("SHA1", "c537c5d99eca5333f23491d47ededd083fefb7ad")
package.checksum = Checksum("SHA1", "c537c5d99eca5333f23491d47ededd083fefb7ad")
package.homepage = SPDXNone()
package.verif_code = "4e3211c67a2d28fced849ee1bb76e7391b93feba"
license_set = LicenseConjunction(
Expand Down
63 changes: 60 additions & 3 deletions spdx/checksum.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,71 @@
# 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.
import re
from enum import Enum, auto
from typing import Optional


class Algorithm(object):
class ChecksumAlgorithm(Enum):
SHA1 = auto()
SHA224 = auto()
SHA256 = auto()
SHA384 = auto()
SHA512 = auto()
SHA3_256 = auto()
SHA3_384 = auto()
SHA3_512 = auto()
BLAKE2B_256 = auto()
BLAKE2B_384 = auto()
BLAKE2B_512 = auto()
BLAKE3 = auto()
MD2 = auto()
MD4 = auto()
MD5 = auto()
MD6 = auto()
ADLER32 = auto()

def algorithm_to_rdf_representation(self) -> str:
if self.name.startswith("BLAKE2B"):
return "checksumAlgorithm_" + self.name.replace("_", "").lower()
else:
return "checksumAlgorithm_" + self.name.lower()

@classmethod
def checksum_from_rdf(cls, identifier: str) -> 'ChecksumAlgorithm':
identifier = identifier.split('_', 1)[-1].upper()
blake_checksum = re.compile(r"^(BLAKE2B)(256|384|512)$", re.UNICODE)
match = blake_checksum.match(identifier)
if match:
identifier = match[1] + '_' + match[2]
if identifier not in ChecksumAlgorithm.__members__:
raise ValueError(f"Invalid algorithm for checksum: {identifier}")
return ChecksumAlgorithm[identifier]

@classmethod
def checksum_algorithm_from_string(cls, identifier: str) -> 'ChecksumAlgorithm':
identifier.replace("-", "_").upper()
if identifier not in ChecksumAlgorithm.__members__:
raise ValueError(f"Invalid algorithm for checksum: {identifier}")
return ChecksumAlgorithm[identifier]


class Checksum(object):
"""Generic checksum algorithm."""

def __init__(self, identifier, value):
def __init__(self, identifier: ChecksumAlgorithm, value: str):
self.identifier = identifier
self.value = value

@classmethod
def checksum_from_string(cls, value: str) -> 'Checksum':
CHECKSUM_RE = re.compile("(ADLER32|BLAKE2b-256|BLAKE2b-384|BLAKE2b-512|BLAKE3|MD2|MD4|MD5|MD6|" \
"SHA1|SHA224|SHA256|SHA384|SHA512|SHA3-256|SHA3-384|SHA3-512):\\s*([a-fA-F0-9]*)")
match = CHECKSUM_RE.match(value)
if match is None or match.group(1) is None or match.group(2) is None:
raise ValueError(f"Invalid checksum: {value}")
identifier = ChecksumAlgorithm.checksum_algorithm_from_string(match.group(1))
return Checksum(identifier=identifier, value=match.group(2))

def to_tv(self):
return "{0}: {1}".format(self.identifier, self.value)
return "{0}: {1}".format(self.identifier.name, self.value)
7 changes: 4 additions & 3 deletions spdx/cli_tools/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ def main(file, force):
"Package Download Location: {0}".format(package.download_location)
)
print("Package Homepage: {0}".format(package.homepage))
if package.checksum:
print("Package Checksum: {0}".format(package.checksum.value))
for checksum in doc.package.checksums.values():
print("Package Checksum: {0} {1}".format(checksum.identifier.name, checksum.value))
print("Package Attribution Text: {0}".format(package.attribution_text))
print("Package verification code: {0}".format(package.verif_code))
print(
Expand Down Expand Up @@ -82,7 +82,8 @@ def main(file, force):
print("\tFile name: {0}".format(f.name))
for file_type in f.file_types:
print("\tFile type: {0}".format(file_type.name))
print("\tFile Checksum: {0}".format(f.chksum.value))
for file_checksum in f.checksums.values():
print("\tFile Checksum: {0} {1}".format(file_checksum.identifier.name, file_checksum.value))
print("\tFile license concluded: {0}".format(f.conc_lics))
print(
"\tFile license info in file: {0}".format(
Expand Down
12 changes: 6 additions & 6 deletions spdx/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,25 +34,25 @@ class ExternalDocumentRef(object):
"""

def __init__(
self, external_document_id=None, spdx_document_uri=None, check_sum=None
self, external_document_id=None, spdx_document_uri=None, checksum=None
):
self.external_document_id = external_document_id
self.spdx_document_uri = spdx_document_uri
self.check_sum = check_sum
self.checksum = checksum

def __eq__(self, other):
return (
isinstance(other, ExternalDocumentRef)
and self.external_document_id == other.external_document_id
and self.spdx_document_uri == other.spdx_document_uri
and self.check_sum == other.check_sum
and self.checksum == other.checksum
)

def __lt__(self, other):
return (self.external_document_id, self.spdx_document_uri, self.check_sum) < (
return (self.external_document_id, self.spdx_document_uri, self.checksum) < (
other.external_document_id,
other.spdx_document_uri,
other.check_sum,
other.checksum,
)

def validate(self, messages: ErrorMessages) -> ErrorMessages:
Expand All @@ -74,7 +74,7 @@ def validate_spdx_doc_uri(self, messages):
messages.append("ExternalDocumentRef has no SPDX Document URI.")

def validate_checksum(self, messages):
if not self.check_sum:
if not self.checksum:
messages.append("ExternalDocumentRef has no Checksum.")


Expand Down
72 changes: 45 additions & 27 deletions spdx/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
# limitations under the License.

from enum import Enum, auto
import warnings
from functools import total_ordering
import hashlib

from spdx import checksum
from spdx import utils
from spdx.license import License
from spdx import utils
from spdx.checksum import Checksum, ChecksumAlgorithm
from spdx.parsers.builderexceptions import SPDXValueError
from spdx.parsers.loggers import ErrorMessages


class FileType(Enum):
Expand Down Expand Up @@ -58,7 +60,8 @@ class File(object):
referenced by other elements. Mandatory, one. Type: str.
- comment: File comment str, Optional zero or one.
- file_types: list of file types. Cardinality 0..*
- chksum: SHA1, Mandatory one.
- checksums: Dict with checksum.ChecksumAlgorithm as key and checksum.Checksum as value,
there must be a SHA1 hash, at least.
- conc_lics: Mandatory one. license.License or utils.NoAssert or utils.SPDXNone.
- licenses_in_file: list of licenses found in file, mandatory one or more.
document.License or utils.SPDXNone or utils.NoAssert.
Expand All @@ -74,12 +77,12 @@ class File(object):
-attribution_text: optional string.
"""

def __init__(self, name, spdx_id=None, chksum=None):
def __init__(self, name, spdx_id=None):
self.name = name
self.spdx_id = spdx_id
self.comment = None
self.file_types = []
self.checksums = [chksum]
self.checksums = {}
self.conc_lics = None
self.licenses_in_file = []
self.license_comment = None
Expand All @@ -99,16 +102,23 @@ def __lt__(self, other):
return self.name < other.name

@property
def chksum(self):
def checksum(self):
"""
Backwards compatibility, return first checksum.
Backwards compatibility, return SHA1 checksum.
"""
# NOTE Package.check_sum but File.chk_sum
return self.checksums[0]
warnings.warn("This property is deprecated. Use get_checksum instead.")
return self.get_checksum(ChecksumAlgorithm.SHA1)

@chksum.setter
def chksum(self, value):
self.checksums[0] = value
@checksum.setter
def checksum(self, value):
"""
Backwards compatibility, set checksum.
"""
warnings.warn("This property is deprecated. Use set_checksum instead.")
if isinstance(value, str):
self.set_checksum(Checksum("SHA1", value))
elif isinstance(value, Checksum):
self.set_checksum(value)

def add_lics(self, lics):
self.licenses_in_file.append(lics)
Expand All @@ -135,7 +145,7 @@ def validate(self, messages):
messages.push_context(self.name)
self.validate_concluded_license(messages)
self.validate_file_types(messages)
self.validate_checksum(messages)
self.validate_checksums(messages)
self.validate_licenses_in_file(messages)
self.validate_copyright(messages)
self.validate_artifacts(messages)
Expand Down Expand Up @@ -202,29 +212,37 @@ def validate_file_types(self, messages):
messages.append(f"{file_type} is not of type FileType.")
return messages

def validate_checksum(self, messages):
if not isinstance(self.chksum, checksum.Algorithm):
messages.append(
"File checksum must be instance of spdx.checksum.Algorithm"
)
else:
if not self.chksum.identifier == "SHA1":
messages.append("File checksum algorithm must be SHA1")
def validate_checksums(self, messages: ErrorMessages):
for checksum in self.checksums.values():
if not isinstance(checksum, Checksum):
messages.append("File checksum must be instance of spdx.checksum.Checksum.")

return messages
if self.get_checksum(ChecksumAlgorithm.SHA1) is None:
messages.append("At least one file checksum algorithm must be SHA1")

def calc_chksum(self):
def calculate_checksum(self, hash_algorithm='SHA1'):
if hash_algorithm not in ChecksumAlgorithm.__members__:
raise ValueError
BUFFER_SIZE = 65536

file_sha1 = hashlib.sha1()
file_hash = hashlib.new(hash_algorithm.lower())
with open(self.name, "rb") as file_handle:
while True:
data = file_handle.read(BUFFER_SIZE)
if not data:
break
file_sha1.update(data)
file_hash.update(data)

return file_hash.hexdigest()

def get_checksum(self, hash_algorithm: ChecksumAlgorithm = ChecksumAlgorithm.SHA1) -> Checksum:
return self.checksums[hash_algorithm]

def set_checksum(self, new_checksum: Checksum):
if not isinstance(new_checksum, Checksum):
raise SPDXValueError

return file_sha1.hexdigest()
self.checksums[new_checksum.identifier] = new_checksum

def has_optional_field(self, field):
return bool (getattr(self, field, None))
return bool(getattr(self, field, None))
Loading