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  }