Skip to content
Open
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
49 changes: 38 additions & 11 deletions src/fosslight_binary/_jar_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import hashlib
import logging
import os
import re
import tempfile
import zipfile
import defusedxml.ElementTree as ET
Expand All @@ -22,6 +23,7 @@
_CENTRAL_SEARCH_TIMEOUT = 2.5 # seconds – tight timeout for Search API (retried on timeout)
_MAX_RETRY = 3 # maximum Central API retry attempts per JAR
_central_network_warned = False # Flag to suppress repeated network-unavailable warnings within one run
_COORD_TOKEN = re.compile(r'[A-Za-z0-9._+-]+') # shape of a Maven groupId / artifactId / version
Comment thread
coderabbitai[bot] marked this conversation as resolved.


def _sha1_of_file(filepath):
Expand Down Expand Up @@ -123,10 +125,18 @@ def _search_central_by_sha1(sha1, timeout=None):
if not docs:
return {}, False
doc = docs[0]
groupId = doc.get("g", "")
artifactId = doc.get("a", "")
version = doc.get("v") or doc.get("latestVersion", "")

if not (groupId and artifactId and version):
logger.debug(f"Maven Central returned an incomplete document for {sha1}: {doc}")
return {}, False

return {
"groupId": doc.get("g", ""),
"artifactId": doc.get("a", ""),
"version": doc.get("v") or doc.get("latestVersion", ""),
"groupId": groupId,
"artifactId": artifactId,
"version": version,
}, False
except requests.exceptions.Timeout:
logger.debug(f"Maven Central SHA-1 search timed out ({sha1}) – will retry")
Expand Down Expand Up @@ -167,17 +177,27 @@ def _download_pom_to_tempfile(group_id, artifact_id, version, timeout=None):
return None, any_timeout


def _is_maven_coordinate(*parts):
"""True only if every part could be a Maven coordinate token.

MANIFEST.MF vendor fields are display names ("The Apache Software
Foundation", "Google, Inc.", "%bundleVendor"), not groupIds. Rejecting them
keeps bogus URLs out of the report and avoids HEAD requests that can only 404.
"""
return all(p and _COORD_TOKEN.fullmatch(p) for p in parts)


def _build_central_jar_url(group_id, artifact_id, version):
if not (group_id and artifact_id and version):
if not _is_maven_coordinate(group_id, artifact_id, version):
return ""
group_path = group_id.replace('.', '/')
return f"https://repo1.maven.org/maven2/{group_path}/{artifact_id}/{version}/{artifact_id}-{version}.jar"


def _exists_in_central(group_id, artifact_id, version):
if not (group_id and artifact_id and version):
return False
url = _build_central_jar_url(group_id, artifact_id, version)
if not url:
return False
try:
resp = requests.head(url, timeout=_REQUEST_TIMEOUT, allow_redirects=True)
return resp.status_code == 200
Expand All @@ -189,12 +209,13 @@ def _exists_in_central(group_id, artifact_id, version):
return False


def _process_one_jar(jar_path, rel_path, sha1, search_timeout=None, skip_central=False):
def _process_one_jar(jar_path, rel_path, sha1, search_timeout=None, skip_central_search=False):
groupId = artifactId = version = project_url = license_str = ''
confirmed_in_central = False
trusted_coordinates = False
source = ''

if skip_central:
if skip_central_search:
central_info = {}
timed_out = False
else:
Expand All @@ -219,6 +240,7 @@ def _process_one_jar(jar_path, rel_path, sha1, search_timeout=None, skip_central
groupId, artifactId, version, project_url = g2, a2, v2, url2
source = 'pom.xml'
confirmed_in_central = True
trusted_coordinates = True

if pom_tmp_path:
try:
Expand All @@ -239,6 +261,7 @@ def _process_one_jar(jar_path, rel_path, sha1, search_timeout=None, skip_central
groupId, artifactId, version = c_groupId, c_artifactId, c_version
source = 'Maven Central'
confirmed_in_central = True
trusted_coordinates = True

tmp_path, timed_out = _download_pom_to_tempfile(
groupId, artifactId, version, timeout=search_timeout)
Expand Down Expand Up @@ -276,6 +299,7 @@ def _process_one_jar(jar_path, rel_path, sha1, search_timeout=None, skip_central
if g2 or a2:
groupId, artifactId, version, project_url = g2, a2, v2, url2
source = 'pom.xml'
trusted_coordinates = True

if pom_tmp_path:
try:
Expand All @@ -291,21 +315,24 @@ def _process_one_jar(jar_path, rel_path, sha1, search_timeout=None, skip_central
os.remove(pom_tmp_path)
except Exception:
pass
if not confirmed_in_central and not skip_central:
confirmed_in_central = _exists_in_central(groupId, artifactId, version)

if not (groupId and artifactId):
g3, a3, v3, url3 = _read_manifest_from_jar(jar_path)
if g3 or a3:
groupId = g3
artifactId = a3
version = version or v3
confirmed_in_central = False
project_url = project_url or url3
source = 'MANIFEST.MF'
trusted_coordinates = False

if not (groupId or artifactId):
return None, False

if not confirmed_in_central and trusted_coordinates:
confirmed_in_central = _exists_in_central(groupId, artifactId, version)

oss_name = f"{groupId}:{artifactId}" if groupId and artifactId else (artifactId or groupId)
dl_url = _build_central_jar_url(groupId, artifactId, version) if confirmed_in_central else ""

Expand Down Expand Up @@ -374,7 +401,7 @@ def analyze_jar_file(path_to_find_bin, path_to_exclude):
logger.warning(
f"{rel_path}: Maven Central API timed out after {_MAX_RETRY} attempts"
" – falling back to JAR internals")
result, _ = _process_one_jar(jar_path, rel_path, sha1, skip_central=True)
result, _ = _process_one_jar(jar_path, rel_path, sha1, skip_central_search=True)
if result is not None:
_store_jar_result(jar_items, sha1, result)
continue
Expand Down
Loading