Return a short lower cased hash string from a ``purl`` string or object. The PURL is normalized and we drop its version, qualifiers and subpath. This function takes a normalized PURL string and a ``_bit_count`` argument defaulting to 0 bits which represents 2**0 = 1 possible hash v
(purl: Union[PackageURL, str], _bit_count: int = 0)
| 341 | |
| 342 | |
| 343 | def get_purl_hash(purl: Union[PackageURL, str], _bit_count: int = 0) -> str: |
| 344 | """ |
| 345 | Return a short lower cased hash string from a ``purl`` string or object. The PURL is normalized |
| 346 | and we drop its version, qualifiers and subpath. |
| 347 | |
| 348 | This function takes a normalized PURL string and a ``_bit_count`` argument defaulting to 0 bits |
| 349 | which represents 2**0 = 1 possible hash value. It returns a fixed length short hash string |
| 350 | that is left-padded with zeros. |
| 351 | |
| 352 | The hash length is derived from the bit_count and the number of bits-per-byte stored in an hex |
| 353 | encoding of this bits count. For 10 bits, this means up to 3 characters. |
| 354 | |
| 355 | The function is carefully designed to be portable across tech stacks and easy to implement in |
| 356 | many programming languages: |
| 357 | |
| 358 | - the hash is computed using sha256 which is available is all common language, |
| 359 | - the hash is using simple lowercased HEX encoding, |
| 360 | - we use simple arithmetics on integer with modulo. |
| 361 | |
| 362 | The processing goes through these steps: |
| 363 | |
| 364 | First, a SHA256 hash computed on the PURL bytes encoded as UTF-8. |
| 365 | |
| 366 | Then, the hash digest bytes are converted to an integer, which is reduced modulo the largest |
| 367 | possible value for the bit_count. |
| 368 | |
| 369 | Finally, this number is converted to hex, left-padded with zero up to the hash_length, and |
| 370 | returned as a lowercase string. |
| 371 | |
| 372 | For example:: |
| 373 | |
| 374 | The hash does not change with version or qualifiers:: |
| 375 | >>> get_purl_hash("pkg:pypi/univers@30.12.0", 7) |
| 376 | '09' |
| 377 | >>> get_purl_hash("pkg:pypi/univers@10.12.0", 7) |
| 378 | '09' |
| 379 | >>> get_purl_hash("pkg:pypi/univers@30.12.0?foo=bar#sub/path", 7) |
| 380 | '09' |
| 381 | |
| 382 | The hash is left padded with zero if it:: |
| 383 | >>> get_purl_hash("pkg:pypi/expressionss", 7) |
| 384 | '57' |
| 385 | |
| 386 | We normalize the PURL. Here pypi normalization always uses dash for underscore :: |
| 387 | |
| 388 | >>> get_purl_hash("pkg:pypi/license_expression", 7) |
| 389 | '50' |
| 390 | >>> get_purl_hash("pkg:pypi/license-expression", 7) |
| 391 | '50' |
| 392 | |
| 393 | Originally from: |
| 394 | https://github.com/nexB/purldb/pull/235/files#diff-a1fd023bd42d73f56019d540f38be711255403547add15108540d70f9948dd40R154 |
| 395 | """ |
| 396 | |
| 397 | core_purl = get_core_purl(purl).to_string() |
| 398 | # compute the hash from a UTF-8 encoded string |
| 399 | purl_bytes = core_purl.encode("utf-8") |
| 400 | hash_bytes = sha256(purl_bytes).digest() |
no test coverage detected