github.com/noqcks/syft@v0.0.0-20230920222752-a9e2c4e288e5/syft/pkg/cataloger/java/parse_java_manifest.go (about)

     1  package java
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"io"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"github.com/anchore/syft/internal/log"
    11  	"github.com/anchore/syft/syft/pkg"
    12  )
    13  
    14  const manifestGlob = "/META-INF/MANIFEST.MF"
    15  
    16  // parseJavaManifest takes MANIFEST.MF file content and returns sections of parsed key/value pairs.
    17  // For more information: https://docs.oracle.com/en/java/javase/11/docs/specs/jar/jar.html#jar-manifest
    18  //
    19  //nolint:funlen
    20  func parseJavaManifest(path string, reader io.Reader) (*pkg.JavaManifest, error) {
    21  	var manifest pkg.JavaManifest
    22  	var sections []map[string]string
    23  
    24  	currentSection := func() int {
    25  		return len(sections) - 1
    26  	}
    27  
    28  	var lastKey string
    29  	scanner := bufio.NewScanner(reader)
    30  
    31  	for scanner.Scan() {
    32  		line := scanner.Text()
    33  
    34  		// empty lines denote section separators
    35  		if strings.TrimSpace(line) == "" {
    36  			// we don't want to allocate a new section map that won't necessarily be used, do that once there is
    37  			// a non-empty line to process
    38  
    39  			// do not process line continuations after this
    40  			lastKey = ""
    41  
    42  			continue
    43  		}
    44  
    45  		if line[0] == ' ' {
    46  			// this is a continuation
    47  
    48  			if lastKey == "" {
    49  				log.Warnf("java manifest %q: found continuation with no previous key: %q", path, line)
    50  				continue
    51  			}
    52  
    53  			sections[currentSection()][lastKey] += strings.TrimSpace(line)
    54  
    55  			continue
    56  		}
    57  
    58  		// this is a new key-value pair
    59  		idx := strings.Index(line, ":")
    60  		if idx == -1 {
    61  			log.Warnf("java manifest %q: unable to split java manifest key-value pairs: %q", path, line)
    62  			continue
    63  		}
    64  
    65  		key := strings.TrimSpace(line[0:idx])
    66  		value := strings.TrimSpace(line[idx+1:])
    67  
    68  		if key == "" {
    69  			// don't attempt to add new keys or sections unless there is a non-empty key
    70  			continue
    71  		}
    72  
    73  		if lastKey == "" {
    74  			// we're entering a new section
    75  			sections = append(sections, make(map[string]string))
    76  		}
    77  
    78  		sections[currentSection()][key] = value
    79  
    80  		// keep track of key for potential future continuations
    81  		lastKey = key
    82  	}
    83  
    84  	if err := scanner.Err(); err != nil {
    85  		return nil, fmt.Errorf("unable to read java manifest: %w", err)
    86  	}
    87  
    88  	if len(sections) > 0 {
    89  		manifest.Main = sections[0]
    90  		if len(sections) > 1 {
    91  			manifest.NamedSections = make(map[string]map[string]string)
    92  			for i, s := range sections[1:] {
    93  				name, ok := s["Name"]
    94  				if !ok {
    95  					// per the manifest spec (https://docs.oracle.com/en/java/javase/11/docs/specs/jar/jar.html#jar-manifest)
    96  					// this should never happen. If it does, we want to know about it, but not necessarily stop
    97  					// cataloging entirely... for this reason we only log.
    98  					log.Warnf("java manifest section found without a name: %s", path)
    99  					name = strconv.Itoa(i)
   100  				} else {
   101  					delete(s, "Name")
   102  				}
   103  				manifest.NamedSections[name] = s
   104  			}
   105  		}
   106  	}
   107  
   108  	return &manifest, nil
   109  }
   110  
   111  func selectName(manifest *pkg.JavaManifest, filenameObj archiveFilename) string {
   112  	var name string
   113  	switch {
   114  	case filenameObj.name != "":
   115  		name = filenameObj.name
   116  	case manifest.Main["Name"] != "":
   117  		// Manifest original spec...
   118  		name = manifest.Main["Name"]
   119  	case manifest.Main["Bundle-Name"] != "":
   120  		// BND tooling...
   121  		name = manifest.Main["Bundle-Name"]
   122  	case manifest.Main["Short-Name"] != "":
   123  		// Jenkins...
   124  		name = manifest.Main["Short-Name"]
   125  	case manifest.Main["Extension-Name"] != "":
   126  		// Jenkins...
   127  		name = manifest.Main["Extension-Name"]
   128  	case manifest.Main["Implementation-Title"] != "":
   129  		// last ditch effort...
   130  		name = manifest.Main["Implementation-Title"]
   131  	}
   132  	return name
   133  }
   134  
   135  func selectVersion(manifest *pkg.JavaManifest, filenameObj archiveFilename) string {
   136  	if v := filenameObj.version; v != "" {
   137  		return v
   138  	}
   139  
   140  	if manifest == nil {
   141  		return ""
   142  	}
   143  
   144  	fieldNames := []string{
   145  		"Implementation-Version",
   146  		"Specification-Version",
   147  		"Plugin-Version",
   148  		"Bundle-Version",
   149  	}
   150  
   151  	for _, fieldName := range fieldNames {
   152  		if v := fieldValueFromManifest(*manifest, fieldName); v != "" {
   153  			return v
   154  		}
   155  	}
   156  
   157  	return ""
   158  }
   159  
   160  func selectLicenses(manifest *pkg.JavaManifest) []string {
   161  	result := []string{}
   162  	if manifest == nil {
   163  		return result
   164  	}
   165  
   166  	fieldNames := []string{
   167  		"Bundle-License",
   168  		"Plugin-License-Name",
   169  	}
   170  
   171  	for _, fieldName := range fieldNames {
   172  		if v := fieldValueFromManifest(*manifest, fieldName); v != "" {
   173  			result = append(result, v)
   174  		}
   175  	}
   176  
   177  	return result
   178  }
   179  
   180  func fieldValueFromManifest(manifest pkg.JavaManifest, fieldName string) string {
   181  	if value := manifest.Main[fieldName]; value != "" {
   182  		return value
   183  	}
   184  
   185  	for _, section := range manifest.NamedSections {
   186  		if value := section[fieldName]; value != "" {
   187  			return value
   188  		}
   189  	}
   190  
   191  	return ""
   192  }