go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/core/resources/versions/deb/version.go (about) 1 // Copyright 2017 clair authors 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 dpkg implements a versionfmt.Parser for version numbers used in dpkg 16 // based software packages. 17 package deb 18 19 import ( 20 "errors" 21 "strconv" 22 "strings" 23 "unicode" 24 ) 25 26 // ParserName is the name by which the dpkg parser is registered. 27 const ParserName = "dpkg" 28 29 type version struct { 30 epoch int 31 version string 32 revision string 33 } 34 35 const ( 36 // MinVersion is a special package version which is always sorted first. 37 MinVersion = "#MINV#" 38 39 // MaxVersion is a special package version which is always sorted last. 40 MaxVersion = "#MAXV#" 41 ) 42 43 var ( 44 minVersion = version{version: MinVersion} 45 maxVersion = version{version: MaxVersion} 46 47 versionAllowedSymbols = []rune{'.', '-', '+', '~', ':', '_'} 48 revisionAllowedSymbols = []rune{'.', '+', '~', '_'} 49 ) 50 51 // newVersion function parses a string into a Version struct which can be compared 52 // 53 // The implementation is based on http://man.he.net/man5/deb-version 54 // on https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version 55 // 56 // It uses the dpkg-1.17.25's algorithm (lib/parsehelp.c) 57 func newVersion(str string) (version, error) { 58 var v version 59 60 // Trim leading and trailing space 61 str = strings.TrimSpace(str) 62 63 if len(str) == 0 { 64 return version{}, errors.New("Version string is empty") 65 } 66 67 // Max/Min versions 68 if str == maxVersion.String() { 69 return maxVersion, nil 70 } 71 if str == minVersion.String() { 72 return minVersion, nil 73 } 74 75 // Find epoch 76 sepepoch := strings.Index(str, ":") 77 if sepepoch > -1 { 78 intepoch, err := strconv.Atoi(str[:sepepoch]) 79 if err == nil { 80 v.epoch = intepoch 81 } else { 82 return version{}, errors.New("epoch in version is not a number") 83 } 84 if intepoch < 0 { 85 return version{}, errors.New("epoch in version is negative") 86 } 87 } else { 88 v.epoch = 0 89 } 90 91 // Find version / revision 92 seprevision := strings.LastIndex(str, "-") 93 if seprevision > -1 { 94 v.version = str[sepepoch+1 : seprevision] 95 v.revision = str[seprevision+1:] 96 } else { 97 v.version = str[sepepoch+1:] 98 v.revision = "" 99 } 100 // Verify format 101 if len(v.version) == 0 { 102 return version{}, errors.New("No version") 103 } 104 105 for i := 0; i < len(v.version); i = i + 1 { 106 r := rune(v.version[i]) 107 if !unicode.IsDigit(r) && !unicode.IsLetter(r) && !containsRune(versionAllowedSymbols, r) { 108 return version{}, errors.New("invalid character in version") 109 } 110 } 111 112 for i := 0; i < len(v.revision); i = i + 1 { 113 r := rune(v.revision[i]) 114 if !unicode.IsDigit(r) && !unicode.IsLetter(r) && !containsRune(revisionAllowedSymbols, r) { 115 return version{}, errors.New("invalid character in revision") 116 } 117 } 118 119 return v, nil 120 } 121 122 type Parser struct{} 123 124 func (p Parser) Valid(str string) bool { 125 _, err := newVersion(str) 126 return err == nil 127 } 128 129 func (p Parser) InRange(versionA, rangeB string) (bool, error) { 130 cmp, err := p.Compare(versionA, rangeB) 131 if err != nil { 132 return false, err 133 } 134 return cmp < 0, nil 135 } 136 137 func (p Parser) GetFixedIn(fixedIn string) (string, error) { 138 return fixedIn, nil 139 } 140 141 // Compare function compares two Debian-like package version 142 // 143 // The implementation is based on http://man.he.net/man5/deb-version 144 // on https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version 145 // 146 // It uses the dpkg-1.17.25's algorithm (lib/version.c) 147 func (p Parser) Compare(a, b string) (int, error) { 148 v1, err := newVersion(a) 149 if err != nil { 150 return 0, err 151 } 152 153 v2, err := newVersion(b) 154 if err != nil { 155 return 0, err 156 } 157 158 // Quick check 159 if v1 == v2 { 160 return 0, nil 161 } 162 163 // Max/Min comparison 164 if v1 == minVersion || v2 == maxVersion { 165 return -1, nil 166 } 167 if v2 == minVersion || v1 == maxVersion { 168 return 1, nil 169 } 170 171 // Compare epochs 172 if v1.epoch > v2.epoch { 173 return 1, nil 174 } 175 if v1.epoch < v2.epoch { 176 return -1, nil 177 } 178 179 // Compare version 180 rc := verrevcmp(v1.version, v2.version) 181 if rc != 0 { 182 return signum(rc), nil 183 } 184 185 // Compare revision 186 return signum(verrevcmp(v1.revision, v2.revision)), nil 187 } 188 189 // String returns the string representation of a Version. 190 func (v version) String() (s string) { 191 if v.epoch != 0 { 192 s = strconv.Itoa(v.epoch) + ":" 193 } 194 s += v.version 195 if v.revision != "" { 196 s += "-" + v.revision 197 } 198 return 199 } 200 201 func verrevcmp(t1, t2 string) int { 202 t1, rt1 := nextRune(t1) 203 t2, rt2 := nextRune(t2) 204 205 for rt1 != nil || rt2 != nil { 206 firstDiff := 0 207 208 for (rt1 != nil && !unicode.IsDigit(*rt1)) || (rt2 != nil && !unicode.IsDigit(*rt2)) { 209 ac := 0 210 bc := 0 211 if rt1 != nil { 212 ac = order(*rt1) 213 } 214 if rt2 != nil { 215 bc = order(*rt2) 216 } 217 218 if ac != bc { 219 return ac - bc 220 } 221 222 t1, rt1 = nextRune(t1) 223 t2, rt2 = nextRune(t2) 224 } 225 for rt1 != nil && *rt1 == '0' { 226 t1, rt1 = nextRune(t1) 227 } 228 for rt2 != nil && *rt2 == '0' { 229 t2, rt2 = nextRune(t2) 230 } 231 for rt1 != nil && unicode.IsDigit(*rt1) && rt2 != nil && unicode.IsDigit(*rt2) { 232 if firstDiff == 0 { 233 firstDiff = int(*rt1) - int(*rt2) 234 } 235 t1, rt1 = nextRune(t1) 236 t2, rt2 = nextRune(t2) 237 } 238 if rt1 != nil && unicode.IsDigit(*rt1) { 239 return 1 240 } 241 if rt2 != nil && unicode.IsDigit(*rt2) { 242 return -1 243 } 244 if firstDiff != 0 { 245 return firstDiff 246 } 247 } 248 249 return 0 250 } 251 252 // order compares runes using a modified ASCII table 253 // so that letters are sorted earlier than non-letters 254 // and so that tildes sorts before anything 255 func order(r rune) int { 256 if unicode.IsDigit(r) { 257 return 0 258 } 259 260 if unicode.IsLetter(r) { 261 return int(r) 262 } 263 264 if r == '~' { 265 return -1 266 } 267 268 return int(r) + 256 269 } 270 271 func nextRune(str string) (string, *rune) { 272 if len(str) >= 1 { 273 r := rune(str[0]) 274 return str[1:], &r 275 } 276 return str, nil 277 } 278 279 func containsRune(s []rune, e rune) bool { 280 for _, a := range s { 281 if a == e { 282 return true 283 } 284 } 285 return false 286 } 287 288 func signum(a int) int { 289 switch { 290 case a < 0: 291 return -1 292 case a > 0: 293 return +1 294 } 295 296 return 0 297 }