@@ -343,3 +343,179 @@ def __gt__(self, other):
343343 if not isinstance (other , self .__class__ ):
344344 return NotImplemented
345345 return gentoo .vercmp (self .value , other .value ) > 0
346+
347+
348+ @attr .s (frozen = True , order = False , eq = False , hash = True )
349+ class LegacyOpensslVersion (Version ):
350+ """
351+ Represent an Legacy Openssl Version.
352+
353+ For example::
354+ >>> LegacyOpensslVersion("1.0.1f")
355+ LegacyOpensslVersion(string='1.0.1f')
356+ >>> LegacyOpensslVersion("1.0.2ac")
357+ LegacyOpensslVersion(string='1.0.2ac')
358+ >>> LegacyOpensslVersion("1.0.2a")
359+ LegacyOpensslVersion(string='1.0.2a')
360+ >>> LegacyOpensslVersion("3.0.2")
361+ Traceback (most recent call last):
362+ ...
363+ univers.versions.InvalidVersion: '3.0.2' is not a valid <class 'univers.versions.LegacyOpensslVersion'>
364+ """
365+
366+ @classmethod
367+ def is_valid (cls , string ):
368+ return bool (cls .parse (string ))
369+
370+ @classmethod
371+ def parse (cls , string ):
372+ """
373+ Return a four-tuple of (major, minor, build, patch) version segments where
374+ major, minor, build are integers and patch is a string possibly empty.
375+ Return False if this is not a valid LegacyOpensslVersion.
376+
377+ For example::
378+ >>> LegacyOpensslVersion.parse("1.0.1f")
379+ (1, 0, 1, 'f')
380+ >>> LegacyOpensslVersion.parse("1.0.2ac")
381+ (1, 0, 2, 'ac')
382+ >>> LegacyOpensslVersion.parse("2.0.2az")
383+ False
384+ """
385+
386+ # All known legacy base OpenSSL versions
387+ all_legacy_base = (
388+ "0.9.1" ,
389+ "0.9.2" ,
390+ "0.9.3" ,
391+ "0.9.4" ,
392+ "0.9.5" ,
393+ "0.9.6" ,
394+ "0.9.7" ,
395+ "0.9.8" ,
396+ "1.0.0" ,
397+ "1.0.1" ,
398+ "1.0.2" ,
399+ "1.1.0" ,
400+ "1.1.1" ,
401+ )
402+ if not string .startswith (all_legacy_base ):
403+ return False
404+
405+ segments = string .split ("." )
406+ if not len (segments ) == 3 :
407+ return False
408+ major , minor , build = segments
409+ major = int (major )
410+ minor = int (minor )
411+ if build .isdigit ():
412+ build = int (build )
413+ patch = ""
414+ else :
415+ patch = build [1 :]
416+ build = int (build [0 ])
417+ if patch [0 ].isdigit ():
418+ return False
419+ return major , minor , build , patch
420+
421+ @classmethod
422+ def build_value (cls , string ):
423+ return cls .parse (string )
424+
425+ def __str__ (self ):
426+ return f"{ self .value [0 ]} .{ self .value [1 ]} .{ self .value [2 ]} { self .value [3 ]} "
427+
428+
429+ @attr .s (frozen = True , order = False , eq = False , hash = True )
430+ class OpensslVersion (Version ):
431+ """
432+ Internally tracks two types of openssl versions
433+ - LegacyOpensslVersion for versions before version 3.0.0 such as 1.0.1g
434+ - Semver for versions from 3.0.0 and up
435+ For example::
436+ >>> old = OpensslVersion("1.1.0f")
437+ >>> new = OpensslVersion("3.0.1")
438+ >>> assert old == OpensslVersion(string="1.1.0f")
439+ >>> assert new == OpensslVersion(string="3.0.1")
440+ >>> assert old.value == LegacyOpensslVersion(string="1.1.0f")
441+ >>> assert new.value == SemverVersion(string="3.0.1")
442+ >>> OpensslVersion("1.2.4fg")
443+ Traceback (most recent call last):
444+ ...
445+ univers.versions.InvalidVersion: '1.2.4fg' is not a valid <class 'univers.versions.OpensslVersion'>
446+ """
447+
448+ @classmethod
449+ def is_valid (cls , string ):
450+ return cls .is_valid_new (string ) or cls .is_valid_legacy (string )
451+
452+ @classmethod
453+ def build_value (cls , string ):
454+ """
455+ Return a wrapped version "value" object depending on
456+ whether version is legacy or semver.
457+ """
458+ if cls .is_valid_legacy (string ):
459+ return LegacyOpensslVersion (string )
460+ if cls .is_valid_new (string ):
461+ return SemverVersion (string )
462+
463+ @classmethod
464+ def is_valid_new (cls , string ):
465+ """
466+ Check the validity of new Openssl Version.
467+
468+ For example::
469+ >>> OpensslVersion.is_valid_new("1.0.1f")
470+ False
471+ >>> OpensslVersion.is_valid_new("3.0.0")
472+ True
473+ >>> OpensslVersion.is_valid_new("3.0.2")
474+ True
475+ """
476+ if SemverVersion .is_valid (string ):
477+ sem = semantic_version .Version .coerce (string )
478+ return sem .major >= 3
479+
480+ @classmethod
481+ def is_valid_legacy (cls , string ):
482+ return LegacyOpensslVersion .is_valid (string )
483+
484+ def __eq__ (self , other ):
485+ if not isinstance (other , self .__class__ ):
486+ return NotImplemented
487+ if not isinstance (other .value , self .value .__class__ ):
488+ return NotImplemented
489+ return self .value .__eq__ (other .value )
490+
491+ def __lt__ (self , other ):
492+ if not isinstance (other , self .__class__ ):
493+ return NotImplemented
494+ if isinstance (other .value , self .value .__class__ ):
495+ return self .value .__lt__ (other .value )
496+ # By construction legacy version is always behind Semver
497+ return isinstance (self .value , LegacyOpensslVersion )
498+
499+ def __gt__ (self , other ):
500+ if not isinstance (other , self .__class__ ):
501+ return NotImplemented
502+ if isinstance (other .value , self .value .__class__ ):
503+ return self .value .__gt__ (other .value )
504+ # By construction semver version is always ahead of legacy
505+ return isinstance (self .value , SemverVersion )
506+
507+ def __le__ (self , other ):
508+ if not isinstance (other , self .__class__ ):
509+ return NotImplemented
510+ if isinstance (other .value , self .value .__class__ ):
511+ return self .value .__le__ (other .value )
512+ # version value are of diff type, then legacy one is always behind semver
513+ return isinstance (self .value , LegacyOpensslVersion )
514+
515+ def __ge__ (self , other ):
516+ if not isinstance (other , self .__class__ ):
517+ return NotImplemented
518+ if isinstance (other .value , self .value .__class__ ):
519+ return self .value .__ge__ (other .value )
520+ # version value are of diff type, then semver one is always ahead of legacy
521+ return isinstance (self .value , SemverVersion )
0 commit comments