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 }