Skip to content
Open
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
96 changes: 96 additions & 0 deletions src/packagedcode/jar_manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,72 @@ def parse_section(section):
return data


def split_osgi_header(value, separator=','):
"""
Split an OSGi header value by `separator` respecting double quotes.
"""
if not value:
return []
results = []
current = []
in_quotes = False
for char in value:
if char == '"':
in_quotes = not in_quotes
current.append(char)
elif char == separator and not in_quotes:
results.append(''.join(current).strip())
current = []
else:
current.append(char)
if current:
results.append(''.join(current).strip())
return [r for r in results if r]


def parse_osgi_dependencies(header_value, scope='import', is_runtime=True):
from packagedcode import models
from packageurl import PackageURL
dependencies = []
if not header_value:
return dependencies

parts = split_osgi_header(header_value, separator=',')
for part in parts:
segments = split_osgi_header(part, separator=';')
names = []
params = {}
for seg in segments:
if '=' in seg:
# This is a parameter
if ':=' in seg:
key, _, val = seg.partition(':=')
else:
key, _, val = seg.partition('=')
# strip quotes
val = val.strip().strip('"')
params[key.strip()] = val
else:
names.append(seg.strip())

for name in names:
is_optional = params.get('resolution') == 'optional'
version = params.get('bundle-version') or params.get('version')

purl = PackageURL(type='osgi', name=name).to_string()

dependencies.append(
models.DependentPackage(
purl=purl,
extracted_requirement=version,
scope=scope,
is_runtime=is_runtime,
is_optional=is_optional,
)
)
return dependencies


def get_normalized_java_manifest_data(manifest_mapping):
"""
Return a mapping of package-like data normalized from a mapping of the
Expand Down Expand Up @@ -348,6 +414,36 @@ def dget(s):
if doc_url:
package["extra_data"]['documentation_url'] = doc_url

dependencies = []
import_packages = dget('Import-Package')
if import_packages:
dependencies.extend(parse_osgi_dependencies(import_packages, scope='import', is_runtime=True))

require_bundle = dget('Require-Bundle')
if require_bundle:
dependencies.extend(parse_osgi_dependencies(require_bundle, scope='require', is_runtime=True))

if dependencies:
package['dependencies'] = [d.to_dict() for d in dependencies]

extra_data = package.get('extra_data', {})

export_packages = dget('Export-Package')
if export_packages:
extra_data['import_package'] = import_packages
extra_data['export_package'] = export_packages

provide_capability = dget('Provide-Capability')
if provide_capability:
extra_data['provide_capability'] = provide_capability

require_capability = dget('Require-Capability')
if require_capability:
extra_data['require_capability'] = require_capability

if extra_data:
package['extra_data'] = extra_data

return package


Expand Down
26 changes: 26 additions & 0 deletions src/packagedcode/maven.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,32 @@ class JavaOSGiManifestHandler(JavaJarManifestHandler):
default_package_type = 'osgi'


class OsgiBndHandler(MavenBasePackageHandler):
datasource_id = 'osgi_bnd'
path_patterns = ('*.bnd',)
default_package_type = 'osgi'
default_primary_language = 'Java'
description = 'OSGi bnd file'
documentation_url = 'https://bnd.bndtools.org/chapters/800-headers.html'

@classmethod
def parse(cls, location, package_only=False):
import javaproperties
from packagedcode.jar_manifest import get_normalized_java_manifest_data
with open(location) as props:
properties = javaproperties.load(props) or {}
if TRACE:
logger.debug(f'OsgiBndHandler.parse: properties: {properties!r}')
if properties:
manifest_data = get_normalized_java_manifest_data(properties)
if manifest_data:
manifest_data['datasource_id'] = cls.datasource_id
# get_normalized_java_manifest_data might set package_type
# to maven or jar, but for bnd files it must be osgi
manifest_data['type'] = cls.default_package_type
yield models.PackageData.from_data(manifest_data, package_only)


class MavenPomXmlHandler(MavenBasePackageHandler):
datasource_id = 'maven_pom'
# NOTE: Maven 1.x used project.xml
Expand Down
8 changes: 4 additions & 4 deletions src/packagedcode/npm.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,10 +380,10 @@ def update_dependencies_by_purl(
if '_' in metadata:
requirement, _extra = metadata.split('_')

if ':' in requirement and '@' in requirement:
if isinstance(requirement, str) and requirement.startswith('npm:') and '@' in requirement:
# dependencies with requirements like this are aliases and should be reported
aliased_package, _, constraint = requirement.rpartition('@')
_, _, aliased_package_name = aliased_package.rpartition(':')
_, _, aliased_package_name = aliased_package.partition('npm:')
sdns, _ , sdname = aliased_package_name.rpartition('/')
dep_purl = PackageURL(
type=cls.default_package_type,
Expand Down Expand Up @@ -1848,10 +1848,10 @@ def deps_mapper(deps, package, field_name, is_direct=True):
if not name:
continue

if ':' in requirement and '@' in requirement:
if isinstance(requirement, str) and requirement.startswith('npm:') and '@' in requirement:
# dependencies with requirements like this are aliases and should be reported
aliased_package, _, requirement = requirement.rpartition('@')
_, _, aliased_package_name = aliased_package.rpartition(':')
_, _, aliased_package_name = aliased_package.partition('npm:')
ns, _ , name = aliased_package_name.rpartition('/')

purl = PackageURL(type='npm', namespace=ns, name=name).to_string()
Expand Down
Loading