github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/os/apk/apkutil/apkutil.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 apkutil provides utilities for reading APK package records. 16 package apkutil 17 18 import ( 19 "bufio" 20 "fmt" 21 "io" 22 "strings" 23 ) 24 25 // KeyValue represents a key-value pair from an APK record. 26 type KeyValue struct { 27 Key string 28 Value string 29 } 30 31 // Scanner provides a convenient way to read APK package records from an APK database. 32 // For details on the file format, see: 33 // https://wiki.alpinelinux.org/wiki/Apk_spec 34 type Scanner struct { 35 scanner *bufio.Scanner 36 err error 37 eof bool 38 cur []KeyValue 39 } 40 41 // NewScanner returns a new [Scanner] to read from r. 42 func NewScanner(r io.Reader) *Scanner { 43 return &Scanner{ 44 scanner: bufio.NewScanner(r), 45 } 46 } 47 48 // Scan reads from the scanner a single APK record. 49 func (s *Scanner) Scan() bool { 50 // return false if an error occurred or EOF has been reached 51 if s.err != nil || s.eof { 52 return false 53 } 54 55 s.cur = nil 56 57 for s.scanner.Scan() { 58 line := s.scanner.Text() 59 60 if line != "" { 61 key, val, found := strings.Cut(line, ":") 62 63 if !found { 64 s.err = fmt.Errorf("invalid line: %q", line) 65 return false 66 } 67 68 s.cur = append(s.cur, KeyValue{Key: key, Value: val}) 69 continue 70 } 71 72 // line is empty, so we check if we have filled out data in group, 73 // this avoids double empty lines returning early 74 if len(s.cur) > 0 { 75 return true 76 } 77 } 78 79 s.err = s.scanner.Err() 80 s.eof = s.err == nil 81 82 // return true only if the EOF has been reached and record has something to return. 83 // otherwise either an error occurred, or eof is reached with no more records, both require 84 // no additional parsing of records. 85 return s.eof && len(s.cur) > 0 86 } 87 88 // Record returns the most recent APK record generated by a call to [Scanner.Scan] 89 // For keys with multiple values, it returns the last one to maintain compatibility. 90 func (s *Scanner) Record() map[string]string { 91 res := make(map[string]string) 92 for _, kv := range s.cur { 93 res[kv.Key] = kv.Value 94 } 95 return res 96 } 97 98 // RecordMultiValue returns the most recent APK record generated by a call to [Scanner.Scan] 99 // with all values for repeated keys. 100 func (s *Scanner) RecordMultiValue() map[string][]string { 101 res := make(map[string][]string) 102 for _, kv := range s.cur { 103 res[kv.Key] = append(res[kv.Key], kv.Value) 104 } 105 return res 106 } 107 108 // FullRecord returns the full record with ordered key-value pairs. 109 func (s *Scanner) FullRecord() []KeyValue { 110 return s.cur 111 } 112 113 // Err returns the first non-EOF error that was encountered by the [Scanner]. 114 func (s *Scanner) Err() error { 115 return s.err 116 }