github.com/google/osv-scalibr@v0.4.1/binary/proto/package.go (about)

     1  // Copyright 2025 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package proto
    16  
    17  import (
    18  	"fmt"
    19  	"reflect"
    20  
    21  	"github.com/docker/docker/api/types/container"
    22  	"github.com/google/osv-scalibr/converter"
    23  	"github.com/google/osv-scalibr/inventory/vex"
    24  	"github.com/google/osv-scalibr/log"
    25  
    26  	"github.com/google/osv-scalibr/extractor"
    27  	ctrdfs "github.com/google/osv-scalibr/extractor/filesystem/containers/containerd"
    28  	"github.com/google/osv-scalibr/extractor/filesystem/containers/podman"
    29  	archivemeta "github.com/google/osv-scalibr/extractor/filesystem/language/java/archive/metadata"
    30  	"github.com/google/osv-scalibr/extractor/filesystem/language/java/javalockfile"
    31  	"github.com/google/osv-scalibr/extractor/filesystem/language/python/requirements"
    32  	"github.com/google/osv-scalibr/extractor/filesystem/language/python/setup"
    33  	chromeextensions "github.com/google/osv-scalibr/extractor/filesystem/misc/chrome/extensions"
    34  	"github.com/google/osv-scalibr/extractor/filesystem/misc/vscodeextensions"
    35  	"github.com/google/osv-scalibr/extractor/filesystem/os/homebrew"
    36  	modulemeta "github.com/google/osv-scalibr/extractor/filesystem/os/kernel/module/metadata"
    37  	vmlinuzmeta "github.com/google/osv-scalibr/extractor/filesystem/os/kernel/vmlinuz/metadata"
    38  	"github.com/google/osv-scalibr/extractor/filesystem/osv"
    39  	cdxmeta "github.com/google/osv-scalibr/extractor/filesystem/sbom/cdx/metadata"
    40  	spdxmeta "github.com/google/osv-scalibr/extractor/filesystem/sbom/spdx/metadata"
    41  	ctrdruntime "github.com/google/osv-scalibr/extractor/standalone/containers/containerd/containerdmetadata"
    42  	"github.com/google/osv-scalibr/extractor/standalone/containers/docker"
    43  	winmetadata "github.com/google/osv-scalibr/extractor/standalone/windows/common/metadata"
    44  	"github.com/google/osv-scalibr/purl"
    45  	"github.com/google/uuid"
    46  
    47  	spb "github.com/google/osv-scalibr/binary/proto/scan_result_go_proto"
    48  	"google.golang.org/protobuf/types/known/timestamppb"
    49  )
    50  
    51  // MetadataProtoSetter is an interface for metadata structs that can set themselves on a Package proto.
    52  type MetadataProtoSetter interface {
    53  	SetProto(p *spb.Package)
    54  }
    55  
    56  // --- Struct to Proto
    57  
    58  // PackageToProto converts a Package struct to a Package proto.
    59  func PackageToProto(pkg *extractor.Package) (*spb.Package, error) {
    60  	if pkg == nil {
    61  		return nil, nil
    62  	}
    63  
    64  	p := converter.ToPURL(pkg)
    65  
    66  	var exps []*spb.PackageExploitabilitySignal
    67  	for _, exp := range pkg.ExploitabilitySignals {
    68  		expProto, err := PackageVEXToProto(exp)
    69  		if err != nil {
    70  			log.Errorf("Failed to convert PackageExploitabilitySignal to proto: %v", err)
    71  			continue
    72  		}
    73  		exps = append(exps, expProto)
    74  	}
    75  
    76  	var cii *spb.Package_ContainerImageMetadataIndexes
    77  
    78  	if pkg.LayerMetadata != nil && pkg.LayerMetadata.ParentContainer != nil {
    79  		cii = &spb.Package_ContainerImageMetadataIndexes{
    80  			ContainerImageIndex: int32(pkg.LayerMetadata.ParentContainer.Index),
    81  			LayerIndex:          int32(pkg.LayerMetadata.Index),
    82  		}
    83  	}
    84  
    85  	id, err := uuid.NewRandom()
    86  	if err != nil {
    87  		return nil, fmt.Errorf("failed to generate UUID for %q package %q version %q: %w", pkg.Ecosystem().String(), pkg.Name, pkg.Version, err)
    88  	}
    89  
    90  	packageProto := &spb.Package{
    91  		Id:                            id.String(),
    92  		Name:                          pkg.Name,
    93  		Version:                       pkg.Version,
    94  		SourceCode:                    sourceCodeIdentifierToProto(pkg.SourceCode),
    95  		Purl:                          purlToProto(p),
    96  		Ecosystem:                     pkg.Ecosystem().String(),
    97  		Locations:                     pkg.Locations,
    98  		Plugins:                       pkg.Plugins,
    99  		ExploitabilitySignals:         exps,
   100  		ContainerImageMetadataIndexes: cii,
   101  		Licenses:                      pkg.Licenses,
   102  	}
   103  	setProtoMetadata(pkg.Metadata, packageProto)
   104  	return packageProto, nil
   105  }
   106  
   107  func sourceCodeIdentifierToProto(s *extractor.SourceCodeIdentifier) *spb.SourceCodeIdentifier {
   108  	if s == nil {
   109  		return nil
   110  	}
   111  	return &spb.SourceCodeIdentifier{
   112  		Repo:   s.Repo,
   113  		Commit: s.Commit,
   114  	}
   115  }
   116  
   117  func setProtoMetadata(meta any, p *spb.Package) {
   118  	if meta == nil {
   119  		return
   120  	}
   121  
   122  	if p == nil {
   123  		return
   124  	}
   125  
   126  	if m, ok := meta.(MetadataProtoSetter); ok {
   127  		m.SetProto(p)
   128  		return
   129  	}
   130  
   131  	// Fallback to switch statement for types not yet implementing MetadataProtoSetter
   132  	// TODO: b/421456154 - Remove this switch statement once all metadata types implement MetadataProtoSetter.
   133  	switch m := meta.(type) {
   134  	case *homebrew.Metadata:
   135  		p.Metadata = &spb.Package_HomebrewMetadata{
   136  			HomebrewMetadata: &spb.HomebrewPackageMetadata{},
   137  		}
   138  	case *modulemeta.Metadata:
   139  		p.Metadata = &spb.Package_KernelModuleMetadata{
   140  			KernelModuleMetadata: &spb.KernelModuleMetadata{
   141  				PackageName:                    m.PackageName,
   142  				PackageVersion:                 m.PackageVersion,
   143  				PackageVermagic:                m.PackageVermagic,
   144  				PackageSourceVersionIdentifier: m.PackageSourceVersionIdentifier,
   145  				OsId:                           m.OSID,
   146  				OsVersionCodename:              m.OSVersionCodename,
   147  				OsVersionId:                    m.OSVersionID,
   148  				PackageAuthor:                  m.PackageAuthor},
   149  		}
   150  	case *vmlinuzmeta.Metadata:
   151  		p.Metadata = &spb.Package_VmlinuzMetadata{
   152  			VmlinuzMetadata: &spb.VmlinuzMetadata{
   153  				Name:              m.Name,
   154  				Version:           m.Version,
   155  				Architecture:      m.Architecture,
   156  				ExtendedVersion:   m.ExtendedVersion,
   157  				Format:            m.Format,
   158  				SwapDevice:        m.SwapDevice,
   159  				RootDevice:        m.RootDevice,
   160  				VideoMode:         m.VideoMode,
   161  				OsId:              m.OSID,
   162  				OsVersionCodename: m.OSVersionCodename,
   163  				OsVersionId:       m.OSVersionID,
   164  				RwRootFs:          m.RWRootFS,
   165  			},
   166  		}
   167  	case *ctrdfs.Metadata:
   168  		p.Metadata = &spb.Package_ContainerdContainerMetadata{
   169  			ContainerdContainerMetadata: &spb.ContainerdContainerMetadata{
   170  				NamespaceName: m.Namespace,
   171  				ImageName:     m.ImageName,
   172  				ImageDigest:   m.ImageDigest,
   173  				Runtime:       m.Runtime,
   174  				Id:            m.ID,
   175  				PodName:       m.PodName,
   176  				PodNamespace:  m.PodNamespace,
   177  				Pid:           int32(m.PID),
   178  				Snapshotter:   m.Snapshotter,
   179  				SnapshotKey:   m.SnapshotKey,
   180  				LowerDir:      m.LowerDir,
   181  				UpperDir:      m.UpperDir,
   182  				WorkDir:       m.WorkDir,
   183  			},
   184  		}
   185  	case *ctrdruntime.Metadata:
   186  		p.Metadata = &spb.Package_ContainerdRuntimeContainerMetadata{
   187  			ContainerdRuntimeContainerMetadata: &spb.ContainerdRuntimeContainerMetadata{
   188  				NamespaceName: m.Namespace,
   189  				ImageName:     m.ImageName,
   190  				ImageDigest:   m.ImageDigest,
   191  				Runtime:       m.Runtime,
   192  				Id:            m.ID,
   193  				Pid:           int32(m.PID),
   194  				RootfsPath:    m.RootFS,
   195  			},
   196  		}
   197  	case *spdxmeta.Metadata:
   198  		p.Metadata = &spb.Package_SpdxMetadata{
   199  			SpdxMetadata: &spb.SPDXPackageMetadata{
   200  				Purl: purlToProto(m.PURL),
   201  				Cpes: m.CPEs,
   202  			},
   203  		}
   204  	case *cdxmeta.Metadata:
   205  		p.Metadata = &spb.Package_CdxMetadata{
   206  			CdxMetadata: &spb.CDXPackageMetadata{
   207  				Purl: purlToProto(m.PURL),
   208  				Cpes: m.CPEs,
   209  			},
   210  		}
   211  	case *archivemeta.Metadata:
   212  		p.Metadata = &spb.Package_JavaArchiveMetadata{
   213  			JavaArchiveMetadata: &spb.JavaArchiveMetadata{
   214  				ArtifactId: m.ArtifactID,
   215  				GroupId:    m.GroupID,
   216  				Sha1:       m.SHA1,
   217  			},
   218  		}
   219  	case *javalockfile.Metadata:
   220  		p.Metadata = &spb.Package_JavaLockfileMetadata{
   221  			JavaLockfileMetadata: &spb.JavaLockfileMetadata{
   222  				ArtifactId:   m.ArtifactID,
   223  				GroupId:      m.GroupID,
   224  				IsTransitive: m.IsTransitive,
   225  			},
   226  		}
   227  	case *osv.Metadata:
   228  		p.Metadata = &spb.Package_OsvMetadata{
   229  			OsvMetadata: &spb.OSVPackageMetadata{
   230  				PurlType:  m.PURLType,
   231  				Commit:    m.Commit,
   232  				Ecosystem: m.Ecosystem,
   233  				CompareAs: m.CompareAs,
   234  			},
   235  		}
   236  	case *requirements.Metadata:
   237  		p.Metadata = &spb.Package_PythonRequirementsMetadata{
   238  			PythonRequirementsMetadata: &spb.PythonRequirementsMetadata{
   239  				HashCheckingModeValues: m.HashCheckingModeValues,
   240  				VersionComparator:      m.VersionComparator,
   241  				Requirement:            m.Requirement,
   242  			},
   243  		}
   244  	case *setup.Metadata:
   245  		p.Metadata = &spb.Package_PythonSetupMetadata{
   246  			PythonSetupMetadata: &spb.PythonSetupMetadata{
   247  				VersionComparator: m.VersionComparator,
   248  			},
   249  		}
   250  	case *winmetadata.OSVersion:
   251  		p.Metadata = &spb.Package_WindowsOsVersionMetadata{
   252  			WindowsOsVersionMetadata: &spb.WindowsOSVersion{
   253  				Product:     m.Product,
   254  				FullVersion: m.FullVersion,
   255  			},
   256  		}
   257  	case *chromeextensions.Metadata:
   258  		p.Metadata = &spb.Package_ChromeExtensionsMetadata{
   259  			ChromeExtensionsMetadata: &spb.ChromeExtensionsMetadata{
   260  				Name:                 m.Name,
   261  				Description:          m.Description,
   262  				AuthorEmail:          m.AuthorEmail,
   263  				HostPermissions:      m.HostPermissions,
   264  				ManifestVersion:      int32(m.ManifestVersion),
   265  				MinimumChromeVersion: m.MinimumChromeVersion,
   266  				Permissions:          m.Permissions,
   267  				UpdateUrl:            m.UpdateURL,
   268  			},
   269  		}
   270  	case *vscodeextensions.Metadata:
   271  		p.Metadata = &spb.Package_VscodeExtensionsMetadata{
   272  			VscodeExtensionsMetadata: &spb.VSCodeExtensionsMetadata{
   273  				Id:                   m.ID,
   274  				PublisherId:          m.PublisherID,
   275  				PublisherDisplayName: m.PublisherDisplayName,
   276  				TargetPlatform:       m.TargetPlatform,
   277  				Updated:              m.Updated,
   278  				IsPreReleaseVersion:  m.IsPreReleaseVersion,
   279  				InstalledTimestamp:   m.InstalledTimestamp,
   280  			},
   281  		}
   282  	case *podman.Metadata:
   283  		exposedPorts := map[uint32]*spb.Protocol{}
   284  		for p, protocols := range m.ExposedPorts {
   285  			exposedPorts[uint32(p)] = &spb.Protocol{Names: protocols}
   286  		}
   287  		p.Metadata = &spb.Package_PodmanMetadata{
   288  			PodmanMetadata: &spb.PodmanMetadata{
   289  				ExposedPorts:  exposedPorts,
   290  				Pid:           int32(m.PID),
   291  				NamespaceName: m.NameSpace,
   292  				StartedTime:   timestamppb.New(m.StartedTime),
   293  				FinishedTime:  timestamppb.New(m.FinishedTime),
   294  				Status:        m.Status,
   295  				ExitCode:      m.ExitCode,
   296  				Exited:        m.Exited,
   297  			},
   298  		}
   299  	case *docker.Metadata:
   300  		ports := make([]*spb.DockerPort, 0, len(m.Ports))
   301  		for _, p := range m.Ports {
   302  			ports = append(ports, &spb.DockerPort{
   303  				Ip:          p.IP,
   304  				PrivatePort: uint32(p.PrivatePort),
   305  				PublicPort:  uint32(p.PublicPort),
   306  				Type:        p.Type,
   307  			})
   308  		}
   309  		p.Metadata = &spb.Package_DockerContainersMetadata{
   310  			DockerContainersMetadata: &spb.DockerContainersMetadata{
   311  				ImageName:   m.ImageName,
   312  				ImageDigest: m.ImageDigest,
   313  				Id:          m.ID,
   314  				Ports:       ports,
   315  			},
   316  		}
   317  	}
   318  }
   319  
   320  func purlToProto(p *purl.PackageURL) *spb.Purl {
   321  	if p == nil {
   322  		return nil
   323  	}
   324  	return &spb.Purl{
   325  		Purl:       p.String(),
   326  		Type:       p.Type,
   327  		Namespace:  p.Namespace,
   328  		Name:       p.Name,
   329  		Version:    p.Version,
   330  		Qualifiers: qualifiersToProto(p.Qualifiers),
   331  		Subpath:    p.Subpath,
   332  	}
   333  }
   334  
   335  func qualifiersToProto(qs purl.Qualifiers) []*spb.Qualifier {
   336  	result := make([]*spb.Qualifier, 0, len(qs))
   337  	for _, q := range qs {
   338  		result = append(result, &spb.Qualifier{Key: q.Key, Value: q.Value})
   339  	}
   340  	return result
   341  }
   342  
   343  // --- Proto to Struct
   344  
   345  // PackageToStruct converts a Package proto to a Package struct.
   346  func PackageToStruct(pkgProto *spb.Package) (*extractor.Package, error) {
   347  	if pkgProto == nil {
   348  		return nil, nil
   349  	}
   350  
   351  	var locations []string
   352  	locations = append(locations, pkgProto.GetLocations()...)
   353  
   354  	// TODO - b/421463494: Remove this once windows PURLs are corrected.
   355  	ptype := pkgProto.GetPurl().GetType()
   356  	if pkgProto.GetPurl().GetType() == purl.TypeGeneric && pkgProto.GetPurl().GetNamespace() == "microsoft" {
   357  		ptype = "windows"
   358  	}
   359  
   360  	var exps []*vex.PackageExploitabilitySignal
   361  	for _, exp := range pkgProto.GetExploitabilitySignals() {
   362  		expStruct, err := PackageVEXToStruct(exp)
   363  		if err != nil {
   364  			log.Errorf("Failed to convert PackageExploitabilitySignal to struct: %v", err)
   365  			continue
   366  		}
   367  		exps = append(exps, expStruct)
   368  	}
   369  
   370  	pkg := &extractor.Package{
   371  		Name:                  pkgProto.GetName(),
   372  		Version:               pkgProto.GetVersion(),
   373  		SourceCode:            sourceCodeIdentifierToStruct(pkgProto.GetSourceCode()),
   374  		Locations:             locations,
   375  		PURLType:              ptype,
   376  		Plugins:               pkgProto.GetPlugins(),
   377  		ExploitabilitySignals: exps,
   378  		Metadata:              metadataToStruct(pkgProto),
   379  		Licenses:              pkgProto.GetLicenses(),
   380  	}
   381  	return pkg, nil
   382  }
   383  
   384  func sourceCodeIdentifierToStruct(s *spb.SourceCodeIdentifier) *extractor.SourceCodeIdentifier {
   385  	if s == nil {
   386  		return nil
   387  	}
   388  	return &extractor.SourceCodeIdentifier{
   389  		Repo:   s.Repo,
   390  		Commit: s.Commit,
   391  	}
   392  }
   393  
   394  func metadataToStruct(md *spb.Package) any {
   395  	if md.GetMetadata() == nil {
   396  		return nil
   397  	}
   398  
   399  	t := reflect.TypeOf(md.GetMetadata())
   400  	if converter, ok := metadataTypeToStructConverter[t]; ok {
   401  		return converter(md)
   402  	}
   403  
   404  	// TODO: b/421456154 - Remove this switch statement once all metadata types implement MetadataProtoSetter.
   405  	switch md.GetMetadata().(type) {
   406  	case *spb.Package_HomebrewMetadata:
   407  		return &homebrew.Metadata{}
   408  	case *spb.Package_KernelModuleMetadata:
   409  		return &modulemeta.Metadata{
   410  			PackageName:                    md.GetKernelModuleMetadata().GetPackageName(),
   411  			PackageVersion:                 md.GetKernelModuleMetadata().GetPackageVersion(),
   412  			PackageVermagic:                md.GetKernelModuleMetadata().GetPackageVermagic(),
   413  			PackageSourceVersionIdentifier: md.GetKernelModuleMetadata().GetPackageSourceVersionIdentifier(),
   414  			OSID:                           md.GetKernelModuleMetadata().GetOsId(),
   415  			OSVersionCodename:              md.GetKernelModuleMetadata().GetOsVersionCodename(),
   416  			OSVersionID:                    md.GetKernelModuleMetadata().GetOsVersionId(),
   417  			PackageAuthor:                  md.GetKernelModuleMetadata().GetPackageAuthor(),
   418  		}
   419  	case *spb.Package_VmlinuzMetadata:
   420  		return &vmlinuzmeta.Metadata{
   421  			Name:              md.GetVmlinuzMetadata().GetName(),
   422  			Version:           md.GetVmlinuzMetadata().GetVersion(),
   423  			Architecture:      md.GetVmlinuzMetadata().GetArchitecture(),
   424  			ExtendedVersion:   md.GetVmlinuzMetadata().GetExtendedVersion(),
   425  			Format:            md.GetVmlinuzMetadata().GetFormat(),
   426  			SwapDevice:        md.GetVmlinuzMetadata().GetSwapDevice(),
   427  			RootDevice:        md.GetVmlinuzMetadata().GetRootDevice(),
   428  			VideoMode:         md.GetVmlinuzMetadata().GetVideoMode(),
   429  			OSID:              md.GetVmlinuzMetadata().GetOsId(),
   430  			OSVersionCodename: md.GetVmlinuzMetadata().GetOsVersionCodename(),
   431  			OSVersionID:       md.GetVmlinuzMetadata().GetOsVersionId(),
   432  			RWRootFS:          md.GetVmlinuzMetadata().GetRwRootFs(),
   433  		}
   434  	case *spb.Package_ContainerdContainerMetadata:
   435  		return &ctrdfs.Metadata{
   436  			Namespace:    md.GetContainerdContainerMetadata().GetNamespaceName(),
   437  			ImageName:    md.GetContainerdContainerMetadata().GetImageName(),
   438  			ImageDigest:  md.GetContainerdContainerMetadata().GetImageDigest(),
   439  			Runtime:      md.GetContainerdContainerMetadata().GetRuntime(),
   440  			ID:           md.GetContainerdContainerMetadata().GetId(),
   441  			PodName:      md.GetContainerdContainerMetadata().GetPodName(),
   442  			PodNamespace: md.GetContainerdContainerMetadata().GetPodNamespace(),
   443  			PID:          int(md.GetContainerdContainerMetadata().GetPid()),
   444  			Snapshotter:  md.GetContainerdContainerMetadata().GetSnapshotter(),
   445  			SnapshotKey:  md.GetContainerdContainerMetadata().GetSnapshotKey(),
   446  			LowerDir:     md.GetContainerdContainerMetadata().GetLowerDir(),
   447  			UpperDir:     md.GetContainerdContainerMetadata().GetUpperDir(),
   448  			WorkDir:      md.GetContainerdContainerMetadata().GetWorkDir(),
   449  		}
   450  	case *spb.Package_ContainerdRuntimeContainerMetadata:
   451  		return &ctrdruntime.Metadata{
   452  			Namespace:   md.GetContainerdRuntimeContainerMetadata().GetNamespaceName(),
   453  			ImageName:   md.GetContainerdRuntimeContainerMetadata().GetImageName(),
   454  			ImageDigest: md.GetContainerdRuntimeContainerMetadata().GetImageDigest(),
   455  			Runtime:     md.GetContainerdRuntimeContainerMetadata().GetRuntime(),
   456  			ID:          md.GetContainerdRuntimeContainerMetadata().GetId(),
   457  			PID:         int(md.GetContainerdRuntimeContainerMetadata().GetPid()),
   458  			RootFS:      md.GetContainerdRuntimeContainerMetadata().GetRootfsPath(),
   459  		}
   460  	case *spb.Package_SpdxMetadata:
   461  		return &spdxmeta.Metadata{
   462  			PURL: purlToStruct(md.GetSpdxMetadata().GetPurl()),
   463  			CPEs: md.GetSpdxMetadata().GetCpes(),
   464  		}
   465  	case *spb.Package_CdxMetadata:
   466  		return &cdxmeta.Metadata{
   467  			PURL: purlToStruct(md.GetCdxMetadata().GetPurl()),
   468  			CPEs: md.GetCdxMetadata().GetCpes(),
   469  		}
   470  	case *spb.Package_JavaArchiveMetadata:
   471  		return &archivemeta.Metadata{
   472  			ArtifactID: md.GetJavaArchiveMetadata().GetArtifactId(),
   473  			GroupID:    md.GetJavaArchiveMetadata().GetGroupId(),
   474  			SHA1:       md.GetJavaArchiveMetadata().GetSha1(),
   475  		}
   476  	case *spb.Package_JavaLockfileMetadata:
   477  		return &javalockfile.Metadata{
   478  			ArtifactID:   md.GetJavaLockfileMetadata().GetArtifactId(),
   479  			GroupID:      md.GetJavaLockfileMetadata().GetGroupId(),
   480  			IsTransitive: md.GetJavaLockfileMetadata().GetIsTransitive(),
   481  		}
   482  	case *spb.Package_OsvMetadata:
   483  		return &osv.Metadata{
   484  			PURLType:  md.GetOsvMetadata().GetPurlType(),
   485  			Commit:    md.GetOsvMetadata().GetCommit(),
   486  			Ecosystem: md.GetOsvMetadata().GetEcosystem(),
   487  			CompareAs: md.GetOsvMetadata().GetCompareAs(),
   488  		}
   489  	case *spb.Package_PythonRequirementsMetadata:
   490  		return &requirements.Metadata{
   491  			HashCheckingModeValues: md.GetPythonRequirementsMetadata().GetHashCheckingModeValues(),
   492  			VersionComparator:      md.GetPythonRequirementsMetadata().GetVersionComparator(),
   493  			Requirement:            md.GetPythonRequirementsMetadata().GetRequirement(),
   494  		}
   495  	case *spb.Package_PythonSetupMetadata:
   496  		return &setup.Metadata{
   497  			VersionComparator: md.GetPythonSetupMetadata().GetVersionComparator(),
   498  		}
   499  	case *spb.Package_WindowsOsVersionMetadata:
   500  		return &winmetadata.OSVersion{
   501  			Product:     md.GetWindowsOsVersionMetadata().GetProduct(),
   502  			FullVersion: md.GetWindowsOsVersionMetadata().GetFullVersion(),
   503  		}
   504  	case *spb.Package_ChromeExtensionsMetadata:
   505  		return &chromeextensions.Metadata{
   506  			Name:                 md.GetChromeExtensionsMetadata().GetName(),
   507  			Description:          md.GetChromeExtensionsMetadata().GetDescription(),
   508  			AuthorEmail:          md.GetChromeExtensionsMetadata().GetAuthorEmail(),
   509  			HostPermissions:      md.GetChromeExtensionsMetadata().GetHostPermissions(),
   510  			ManifestVersion:      int(md.GetChromeExtensionsMetadata().GetManifestVersion()),
   511  			MinimumChromeVersion: md.GetChromeExtensionsMetadata().GetMinimumChromeVersion(),
   512  			Permissions:          md.GetChromeExtensionsMetadata().GetPermissions(),
   513  			UpdateURL:            md.GetChromeExtensionsMetadata().GetUpdateUrl(),
   514  		}
   515  	case *spb.Package_VscodeExtensionsMetadata:
   516  		return &vscodeextensions.Metadata{
   517  			ID:                   md.GetVscodeExtensionsMetadata().GetId(),
   518  			PublisherID:          md.GetVscodeExtensionsMetadata().GetPublisherId(),
   519  			PublisherDisplayName: md.GetVscodeExtensionsMetadata().GetPublisherDisplayName(),
   520  			TargetPlatform:       md.GetVscodeExtensionsMetadata().GetTargetPlatform(),
   521  			Updated:              md.GetVscodeExtensionsMetadata().GetUpdated(),
   522  			IsPreReleaseVersion:  md.GetVscodeExtensionsMetadata().GetIsPreReleaseVersion(),
   523  			InstalledTimestamp:   md.GetVscodeExtensionsMetadata().GetInstalledTimestamp(),
   524  		}
   525  	case *spb.Package_PodmanMetadata:
   526  		exposedPorts := map[uint16][]string{}
   527  		for p, protocol := range md.GetPodmanMetadata().GetExposedPorts() {
   528  			for _, name := range protocol.GetNames() {
   529  				exposedPorts[uint16(p)] = append(exposedPorts[uint16(p)], name)
   530  			}
   531  		}
   532  		return &podman.Metadata{
   533  			ExposedPorts: exposedPorts,
   534  			PID:          int(md.GetPodmanMetadata().GetPid()),
   535  			NameSpace:    md.GetPodmanMetadata().GetNamespaceName(),
   536  			StartedTime:  md.GetPodmanMetadata().GetStartedTime().AsTime(),
   537  			FinishedTime: md.GetPodmanMetadata().GetFinishedTime().AsTime(),
   538  			Status:       md.GetPodmanMetadata().GetStatus(),
   539  			ExitCode:     md.GetPodmanMetadata().GetExitCode(),
   540  			Exited:       md.GetPodmanMetadata().GetExited(),
   541  		}
   542  	case *spb.Package_DockerContainersMetadata:
   543  		var ports []container.Port
   544  		for _, p := range md.GetDockerContainersMetadata().GetPorts() {
   545  			ports = append(ports, container.Port{
   546  				IP:          p.GetIp(),
   547  				PrivatePort: uint16(p.GetPrivatePort()),
   548  				PublicPort:  uint16(p.GetPublicPort()),
   549  				Type:        p.GetType(),
   550  			})
   551  		}
   552  		return &docker.Metadata{
   553  			ImageName:   md.GetDockerContainersMetadata().GetImageName(),
   554  			ImageDigest: md.GetDockerContainersMetadata().GetImageDigest(),
   555  			ID:          md.GetDockerContainersMetadata().GetId(),
   556  			Ports:       ports,
   557  		}
   558  	}
   559  
   560  	return nil
   561  }
   562  
   563  func purlToStruct(p *spb.Purl) *purl.PackageURL {
   564  	if p == nil {
   565  		return nil
   566  	}
   567  
   568  	// There's no guarantee that the PURL fields will match the PURL string.
   569  	// Use the fields if the string is blank or invalid.
   570  	// Elese, compare the string and fields, prioritizing the fields.
   571  	pfs := purlFromString(p.GetPurl())
   572  	if pfs == nil {
   573  		return &purl.PackageURL{
   574  			Type:       p.GetType(),
   575  			Namespace:  p.GetNamespace(),
   576  			Name:       p.GetName(),
   577  			Version:    p.GetVersion(),
   578  			Qualifiers: qualifiersToStruct(p.GetQualifiers()),
   579  			Subpath:    p.GetSubpath(),
   580  		}
   581  	}
   582  
   583  	// Prioritize fields from the PURL proto over the PURL string.
   584  	ptype := pfs.Type
   585  	if p.GetType() != "" {
   586  		ptype = p.GetType()
   587  	}
   588  	namespace := pfs.Namespace
   589  	if p.GetNamespace() != "" {
   590  		namespace = p.GetNamespace()
   591  	}
   592  	name := pfs.Name
   593  	if p.GetName() != "" {
   594  		name = p.GetName()
   595  	}
   596  	version := pfs.Version
   597  	if p.GetVersion() != "" {
   598  		version = p.GetVersion()
   599  	}
   600  	qualifiers := pfs.Qualifiers
   601  	if len(p.GetQualifiers()) > 0 {
   602  		qualifiers = qualifiersToStruct(p.GetQualifiers())
   603  	}
   604  	subpath := pfs.Subpath
   605  	if p.GetSubpath() != "" {
   606  		subpath = p.GetSubpath()
   607  	}
   608  
   609  	// TODO - b/421463494: Remove this once windows PURLs are corrected.
   610  	if ptype == purl.TypeGeneric && namespace == "microsoft" {
   611  		ptype = "windows"
   612  		namespace = ""
   613  	}
   614  
   615  	return &purl.PackageURL{
   616  		Type:       ptype,
   617  		Namespace:  namespace,
   618  		Name:       name,
   619  		Version:    version,
   620  		Qualifiers: qualifiers,
   621  		Subpath:    subpath,
   622  	}
   623  }
   624  
   625  func purlFromString(s string) *purl.PackageURL {
   626  	if s == "" {
   627  		return nil
   628  	}
   629  	p, err := purl.FromString(s)
   630  	if err != nil {
   631  		log.Errorf("failed to parse PURL string %q: %v", s, err)
   632  		return nil
   633  	}
   634  	if len(p.Qualifiers) == 0 {
   635  		p.Qualifiers = nil
   636  	}
   637  	return &p
   638  }
   639  
   640  func qualifiersToStruct(qs []*spb.Qualifier) purl.Qualifiers {
   641  	if len(qs) == 0 {
   642  		return nil
   643  	}
   644  	qsmap := map[string]string{}
   645  	for _, q := range qs {
   646  		qsmap[q.GetKey()] = q.GetValue()
   647  	}
   648  	return purl.QualifiersFromMap(qsmap)
   649  }