os-normalizer 0.3.2__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of os-normalizer might be problematic. Click here for more details.
- {os_normalizer-0.3.2 → os_normalizer-0.4.0}/CHANGELOG.md +15 -2
- {os_normalizer-0.3.2 → os_normalizer-0.4.0}/PKG-INFO +1 -1
- {os_normalizer-0.3.2 → os_normalizer-0.4.0}/os_normalizer/constants.py +43 -3
- {os_normalizer-0.3.2 → os_normalizer-0.4.0}/os_normalizer/parsers/__init__.py +3 -4
- {os_normalizer-0.3.2 → os_normalizer-0.4.0}/os_normalizer/parsers/linux.py +15 -5
- {os_normalizer-0.3.2 → os_normalizer-0.4.0}/os_normalizer/parsers/network/fortinet.py +1 -3
- {os_normalizer-0.3.2 → os_normalizer-0.4.0}/os_normalizer/parsers/network/netgear.py +2 -6
- {os_normalizer-0.3.2 → os_normalizer-0.4.0}/os_normalizer/parsers/windows.py +70 -21
- {os_normalizer-0.3.2 → os_normalizer-0.4.0}/pyproject.toml +1 -1
- {os_normalizer-0.3.2 → os_normalizer-0.4.0}/.gitignore +0 -0
- {os_normalizer-0.3.2 → os_normalizer-0.4.0}/.python-version +0 -0
- {os_normalizer-0.3.2 → os_normalizer-0.4.0}/LICENSE +0 -0
- {os_normalizer-0.3.2 → os_normalizer-0.4.0}/README.md +0 -0
- {os_normalizer-0.3.2 → os_normalizer-0.4.0}/RELEASING.md +0 -0
- {os_normalizer-0.3.2 → os_normalizer-0.4.0}/os_normalizer/__init__.py +0 -0
- {os_normalizer-0.3.2 → os_normalizer-0.4.0}/os_normalizer/cpe.py +0 -0
- {os_normalizer-0.3.2 → os_normalizer-0.4.0}/os_normalizer/helpers.py +0 -0
- {os_normalizer-0.3.2 → os_normalizer-0.4.0}/os_normalizer/models.py +0 -0
- {os_normalizer-0.3.2 → os_normalizer-0.4.0}/os_normalizer/os_normalizer.py +0 -0
- {os_normalizer-0.3.2 → os_normalizer-0.4.0}/os_normalizer/parsers/bsd.py +0 -0
- {os_normalizer-0.3.2 → os_normalizer-0.4.0}/os_normalizer/parsers/macos.py +0 -0
- {os_normalizer-0.3.2 → os_normalizer-0.4.0}/os_normalizer/parsers/mobile.py +0 -0
- {os_normalizer-0.3.2 → os_normalizer-0.4.0}/os_normalizer/parsers/network/__init__.py +0 -0
- {os_normalizer-0.3.2 → os_normalizer-0.4.0}/os_normalizer/parsers/network/cisco.py +0 -0
- {os_normalizer-0.3.2 → os_normalizer-0.4.0}/os_normalizer/parsers/network/huawei.py +0 -0
- {os_normalizer-0.3.2 → os_normalizer-0.4.0}/os_normalizer/parsers/network/juniper.py +0 -0
|
@@ -3,7 +3,19 @@
|
|
|
3
3
|
All notable changes to this project are documented here.
|
|
4
4
|
This file adheres to Keep a Changelog and Semantic Versioning.
|
|
5
5
|
|
|
6
|
-
## [
|
|
6
|
+
## [0.3.3] - 2025-09-21
|
|
7
|
+
|
|
8
|
+
- Added: `tests/case_utils.py` to share parametrization helpers across suites.
|
|
9
|
+
- Added: Platform-specific suites for clearer test changes.
|
|
10
|
+
- Removed: Legacy `tests/test_os_normalizer.py` harness now that coverage lives beside each platform.
|
|
11
|
+
|
|
12
|
+
## [0.3.2] - 2025-09-11
|
|
13
|
+
|
|
14
|
+
- Added: More `pyproject.toml` metadata (description, keywords, classifiers, project URLs).
|
|
15
|
+
- Added: `LICENSE` (MIT) and referenced it from project metadata.
|
|
16
|
+
- Added: `RELEASING.md` with step-by-step TestPyPI/PyPI instructions.
|
|
17
|
+
- Changed: Switched to Hatchling build backend via `[build-system]` in `pyproject.toml`.
|
|
18
|
+
- Changed: Exclude dev artifacts from sdist (`tests/`, caches, lockfiles, egg-info).
|
|
7
19
|
|
|
8
20
|
## [0.3.1] - 2025-09-09
|
|
9
21
|
|
|
@@ -28,7 +40,8 @@ This file adheres to Keep a Changelog and Semantic Versioning.
|
|
|
28
40
|
|
|
29
41
|
- Initial release.
|
|
30
42
|
|
|
31
|
-
[Unreleased]: https://github.com/johnscillieri/os-normalizer/compare/v0.3.
|
|
43
|
+
[Unreleased]: https://github.com/johnscillieri/os-normalizer/compare/v0.3.3...HEAD
|
|
44
|
+
[0.3.3]: https://github.com/johnscillieri/os-normalizer/compare/v0.3.2...v0.3.3
|
|
32
45
|
[0.3.1]: https://github.com/johnscillieri/os-normalizer/compare/v0.3.0...v0.3.1
|
|
33
46
|
[0.3.0]: https://github.com/johnscillieri/os-normalizer/compare/v0.2.0...v0.3.0
|
|
34
47
|
[0.2.0]: https://github.com/johnscillieri/os-normalizer/compare/v0.1.0...v0.2.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: os-normalizer
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Normalize raw OS strings/metadata into structured data (family, product, version, arch).
|
|
5
5
|
Project-URL: Homepage, https://github.com/johnscillieri/os-normalizer
|
|
6
6
|
Project-URL: Repository, https://github.com/johnscillieri/os-normalizer
|
|
@@ -17,8 +17,27 @@ ARCH_SYNONYMS = {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
# Windows build map (build number range -> product name, marketing channel)
|
|
20
|
+
# Notes:
|
|
21
|
+
# - This focuses on common client builds; server detection is handled separately
|
|
22
|
+
# and this map is not applied if a server product was already detected.
|
|
23
|
+
# - Marketing/channel labels use common public naming where applicable
|
|
24
|
+
# (e.g., 21H2/22H2 for Windows 10/11, RTM/SPx for older releases).
|
|
20
25
|
WINDOWS_BUILD_MAP = [
|
|
21
|
-
# Windows 10
|
|
26
|
+
# NT era (pre-Windows 10)
|
|
27
|
+
(1381, 1381, "Windows NT 4.0", "RTM"),
|
|
28
|
+
(2195, 2195, "Windows 2000", "RTM"),
|
|
29
|
+
(2600, 2600, "Windows XP", "RTM"),
|
|
30
|
+
# NT 5.2 builds are ambiguous (XP x64 vs Server 2003); keep consistent label
|
|
31
|
+
(3790, 3790, "Windows XP x64/Server 2003", "RTM"),
|
|
32
|
+
# Vista/7/8/8.1
|
|
33
|
+
(6000, 6000, "Windows Vista", "RTM"),
|
|
34
|
+
(6001, 6001, "Windows Vista", "SP1"),
|
|
35
|
+
(6002, 6002, "Windows Vista", "SP2"),
|
|
36
|
+
(7600, 7600, "Windows 7", "RTM"),
|
|
37
|
+
(7601, 7601, "Windows 7", "SP1"),
|
|
38
|
+
(9200, 9200, "Windows 8", "RTM"),
|
|
39
|
+
(9600, 9600, "Windows 8.1", "RTM"),
|
|
40
|
+
# Windows 10 (builds and marketing versions)
|
|
22
41
|
(10240, 10240, "Windows 10", "1507"),
|
|
23
42
|
(10586, 10586, "Windows 10", "1511"),
|
|
24
43
|
(14393, 14393, "Windows 10", "1607"),
|
|
@@ -30,7 +49,7 @@ WINDOWS_BUILD_MAP = [
|
|
|
30
49
|
(19041, 19045, "Windows 10", "2004/20H2/21H1/21H2/22H2"),
|
|
31
50
|
# Windows 11
|
|
32
51
|
(22000, 22000, "Windows 11", "21H2"),
|
|
33
|
-
(22621,
|
|
52
|
+
(22621, 22621, "Windows 11", "22H2"),
|
|
34
53
|
(22631, 25999, "Windows 11", "23H2"),
|
|
35
54
|
(26100, 26199, "Windows 11", "24H2"),
|
|
36
55
|
]
|
|
@@ -80,8 +99,29 @@ MACOS_DARWIN_MAP = {
|
|
|
80
99
|
22: ("macOS", "13", "Ventura"),
|
|
81
100
|
23: ("macOS", "14", "Sonoma"),
|
|
82
101
|
24: ("macOS", "15", "Sequoia"),
|
|
102
|
+
25: ("macOS", "26", "Tahoe"),
|
|
83
103
|
}
|
|
84
104
|
|
|
105
|
+
# Windows Server build map (build number range -> product name, marketing channel)
|
|
106
|
+
# This is consulted only when the input looks server-like or when an explicit
|
|
107
|
+
# Windows Server product is already detected. Client mapping will not override
|
|
108
|
+
# explicit server detections.
|
|
109
|
+
WINDOWS_SERVER_BUILD_MAP = [
|
|
110
|
+
# Legacy server releases aligned with Vista/7/8/8.1
|
|
111
|
+
(3790, 3790, "Windows Server 2003", "RTM"),
|
|
112
|
+
(6001, 6001, "Windows Server 2008", "RTM"), # 6001 corresponds to 2008 RTM
|
|
113
|
+
(6002, 6002, "Windows Server 2008", "SP2"),
|
|
114
|
+
(7600, 7600, "Windows Server 2008 R2", "RTM"),
|
|
115
|
+
(7601, 7601, "Windows Server 2008 R2", "SP1"),
|
|
116
|
+
(9200, 9200, "Windows Server 2012", "RTM"),
|
|
117
|
+
(9600, 9600, "Windows Server 2012 R2", "RTM"),
|
|
118
|
+
# NT 10.0 based server releases
|
|
119
|
+
(14393, 14393, "Windows Server 2016", "1607"),
|
|
120
|
+
(17763, 17763, "Windows Server 2019", "1809"),
|
|
121
|
+
(20348, 20348, "Windows Server 2022", "21H2"),
|
|
122
|
+
# Windows Server 2025 (vNext) uses the 26100 train alongside client 24H2
|
|
123
|
+
(26100, 26199, "Windows Server 2025", "24H2"),
|
|
124
|
+
]
|
|
125
|
+
|
|
85
126
|
# Cisco train names (used for codename detection)
|
|
86
127
|
CISCO_TRAIN_NAMES = {"Everest", "Fuji", "Gibraltar", "Amsterdam", "Denali"}
|
|
87
|
-
|
|
@@ -6,11 +6,10 @@ from .bsd import parse_bsd
|
|
|
6
6
|
from .network import parse_network
|
|
7
7
|
|
|
8
8
|
__all__ = [
|
|
9
|
-
"
|
|
10
|
-
"parse_macos",
|
|
9
|
+
"parse_bsd",
|
|
11
10
|
"parse_linux",
|
|
11
|
+
"parse_macos",
|
|
12
12
|
"parse_mobile",
|
|
13
|
-
"parse_bsd",
|
|
14
13
|
"parse_network",
|
|
14
|
+
"parse_windows",
|
|
15
15
|
]
|
|
16
|
-
|
|
@@ -18,7 +18,7 @@ LINUX_VER_FALLBACK_RE = re.compile(
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
def parse_linux(text: str, data: dict[str, Any], p: OSData) -> OSData:
|
|
21
|
-
"""Populate an OSData instance with Linux
|
|
21
|
+
"""Populate an OSData instance with Linux-specific details."""
|
|
22
22
|
p.kernel_name = "linux"
|
|
23
23
|
|
|
24
24
|
osrel = _coerce_os_release(data.get("os_release")) if isinstance(data, dict) else None
|
|
@@ -37,7 +37,7 @@ def parse_linux(text: str, data: dict[str, Any], p: OSData) -> OSData:
|
|
|
37
37
|
return p
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
def _coerce_os_release(obj: Any) ->
|
|
40
|
+
def _coerce_os_release(obj: Any) -> dict[str, Any] | None:
|
|
41
41
|
if isinstance(obj, str):
|
|
42
42
|
return parse_os_release(obj)
|
|
43
43
|
if isinstance(obj, dict):
|
|
@@ -45,7 +45,7 @@ def _coerce_os_release(obj: Any) -> Optional[dict[str, Any]]:
|
|
|
45
45
|
return None
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
def _extract_kernel_version(text: str) ->
|
|
48
|
+
def _extract_kernel_version(text: str) -> str | None:
|
|
49
49
|
m = KERNEL_RE.search(text)
|
|
50
50
|
if m:
|
|
51
51
|
return m.group(2)
|
|
@@ -66,6 +66,14 @@ def _apply_os_release(osrel: dict[str, Any], p: OSData) -> None:
|
|
|
66
66
|
|
|
67
67
|
p.pretty_name = osrel.get("PRETTY_NAME") or osrel.get("NAME")
|
|
68
68
|
|
|
69
|
+
if not p.codename and p.pretty_name:
|
|
70
|
+
# Fallback: try to extract codename from parenthetical in pretty name (e.g. "Debian ... (buster)")
|
|
71
|
+
m = re.search(r"\(([^)]+)\)", str(p.pretty_name))
|
|
72
|
+
if m:
|
|
73
|
+
candidate = m.group(1).strip()
|
|
74
|
+
if candidate:
|
|
75
|
+
p.codename = candidate.title()
|
|
76
|
+
|
|
69
77
|
_apply_version_id(osrel.get("VERSION_ID"), p)
|
|
70
78
|
|
|
71
79
|
vcode = osrel.get("VERSION_CODENAME")
|
|
@@ -78,7 +86,9 @@ def _apply_os_release(osrel: dict[str, Any], p: OSData) -> None:
|
|
|
78
86
|
p.vendor = _vendor_for_distro(p.distro) if p.distro else p.vendor
|
|
79
87
|
|
|
80
88
|
name = osrel.get("NAME")
|
|
81
|
-
p.product = (
|
|
89
|
+
p.product = (
|
|
90
|
+
(name if name else (p.distro or "Linux")).replace('"', "") if isinstance(name, str) else (p.distro or "Linux")
|
|
91
|
+
)
|
|
82
92
|
|
|
83
93
|
# Precision from version parts
|
|
84
94
|
if p.version_patch is not None:
|
|
@@ -103,7 +113,7 @@ def _apply_version_id(vid: Any, p: OSData) -> None:
|
|
|
103
113
|
p.version_patch = int(parts[2])
|
|
104
114
|
|
|
105
115
|
|
|
106
|
-
def _vendor_for_distro(distro:
|
|
116
|
+
def _vendor_for_distro(distro: str | None) -> str | None:
|
|
107
117
|
vendor_by_distro = {
|
|
108
118
|
"ubuntu": "Canonical",
|
|
109
119
|
"debian": "Debian",
|
|
@@ -30,9 +30,7 @@ def parse_fortinet(text: str, p: OSData) -> OSData:
|
|
|
30
30
|
if len(nums) >= 3:
|
|
31
31
|
p.version_patch = int(nums[2])
|
|
32
32
|
p.version_build = v
|
|
33
|
-
p.precision = (
|
|
34
|
-
"patch" if p.version_patch is not None else ("minor" if p.version_minor is not None else "major")
|
|
35
|
-
)
|
|
33
|
+
p.precision = "patch" if p.version_patch is not None else ("minor" if p.version_minor is not None else "major")
|
|
36
34
|
|
|
37
35
|
bld = FORTI_BUILD_RE.search(text)
|
|
38
36
|
if bld:
|
|
@@ -6,9 +6,7 @@ from os_normalizer.helpers import update_confidence
|
|
|
6
6
|
from os_normalizer.models import OSData
|
|
7
7
|
|
|
8
8
|
NETGEAR_RE = re.compile(r"\bnetgear\b|\bfirmware\b", re.IGNORECASE)
|
|
9
|
-
NETGEAR_VER_RE = re.compile(
|
|
10
|
-
r"\bV(\d+\.\d+\.\d+(?:\.\d+)?(?:_\d+\.\d+\.\d+)?)\b", re.IGNORECASE
|
|
11
|
-
)
|
|
9
|
+
NETGEAR_VER_RE = re.compile(r"\bV(\d+\.\d+\.\d+(?:\.\d+)?(?:_\d+\.\d+\.\d+)?)\b", re.IGNORECASE)
|
|
12
10
|
NETGEAR_MODEL_RE = re.compile(r"\b([RN][0-9]{3,4}[A-Z]?)\b", re.IGNORECASE)
|
|
13
11
|
|
|
14
12
|
|
|
@@ -29,9 +27,7 @@ def parse_netgear(text: str, p: OSData) -> OSData:
|
|
|
29
27
|
if len(nums) >= 3:
|
|
30
28
|
p.version_patch = int(nums[2])
|
|
31
29
|
p.version_build = v
|
|
32
|
-
p.precision = (
|
|
33
|
-
"patch" if p.version_patch is not None else ("minor" if p.version_minor is not None else "major")
|
|
34
|
-
)
|
|
30
|
+
p.precision = "patch" if p.version_patch is not None else ("minor" if p.version_minor is not None else "major")
|
|
35
31
|
|
|
36
32
|
mdl = NETGEAR_MODEL_RE.search(text)
|
|
37
33
|
if mdl:
|
|
@@ -10,6 +10,7 @@ from typing import Any, Optional
|
|
|
10
10
|
|
|
11
11
|
from os_normalizer.constants import (
|
|
12
12
|
WINDOWS_BUILD_MAP,
|
|
13
|
+
WINDOWS_SERVER_BUILD_MAP,
|
|
13
14
|
WINDOWS_NT_CLIENT_MAP,
|
|
14
15
|
WINDOWS_NT_SERVER_MAP,
|
|
15
16
|
)
|
|
@@ -18,14 +19,18 @@ from os_normalizer.models import OSData
|
|
|
18
19
|
|
|
19
20
|
# Regex patterns used only by the Windows parser
|
|
20
21
|
WIN_EDITION_RE = re.compile(
|
|
21
|
-
r"\b(professional|enterprise|home|education|ltsc|datacenter)\b",
|
|
22
|
+
r"\b(professional|pro|enterprise|home|education|ltsc|datacenter|standard)\b",
|
|
22
23
|
re.IGNORECASE,
|
|
23
24
|
)
|
|
24
25
|
WIN_SP_RE = re.compile(r"\bSP\s?([0-9]+)\b", re.IGNORECASE)
|
|
25
26
|
WIN_BUILD_RE = re.compile(r"\bbuild\s?(\d{4,6})\b", re.IGNORECASE)
|
|
26
27
|
WIN_NT_RE = re.compile(r"\bnt\s?(\d+)\.(\d+)\b", re.IGNORECASE)
|
|
27
28
|
WIN_FULL_NT_BUILD_RE = re.compile(r"\b(10)\.(0)\.(\d+)(?:\.(\d+))?\b")
|
|
28
|
-
|
|
29
|
+
WIN_GENERIC_VERSION_RE = re.compile(r"\b(\d+)\.(\d+)\.(\d{3,6})(?:\.(\d+))?\b")
|
|
30
|
+
WIN_CHANNEL_RE = re.compile(
|
|
31
|
+
r"\b(24H2|23H2|22H2|21H2|21H1|20H2|2004|1909|1903|1809|1803|1709|1703|1607|1511|1507)\b",
|
|
32
|
+
re.IGNORECASE,
|
|
33
|
+
)
|
|
29
34
|
|
|
30
35
|
|
|
31
36
|
def parse_windows(text: str, data: dict[str, Any], p: OSData) -> OSData:
|
|
@@ -47,7 +52,7 @@ def parse_windows(text: str, data: dict[str, Any], p: OSData) -> OSData:
|
|
|
47
52
|
_apply_full_kernel_and_channel(text, p)
|
|
48
53
|
|
|
49
54
|
# 5) Build number + marketing channel (fallback when only 'build 22631' is present)
|
|
50
|
-
_apply_build_mapping(text, p)
|
|
55
|
+
_apply_build_mapping(text, p, server_like)
|
|
51
56
|
|
|
52
57
|
# 6) Precision and version_major if applicable
|
|
53
58
|
_finalize_precision_and_version(p)
|
|
@@ -59,6 +64,9 @@ def parse_windows(text: str, data: dict[str, Any], p: OSData) -> OSData:
|
|
|
59
64
|
|
|
60
65
|
|
|
61
66
|
def _detect_product_from_text(t: str) -> str:
|
|
67
|
+
# Normalize common typos before matching
|
|
68
|
+
t = t.replace("windws", "windows")
|
|
69
|
+
|
|
62
70
|
if "windows 11" in t or "win11" in t:
|
|
63
71
|
return "Windows 11"
|
|
64
72
|
if "windows 10" in t or "win10" in t:
|
|
@@ -75,19 +83,23 @@ def _detect_product_from_text(t: str) -> str:
|
|
|
75
83
|
return "Windows 98"
|
|
76
84
|
|
|
77
85
|
# Server explicit names
|
|
78
|
-
if "windows server
|
|
86
|
+
if "windows server 2012 r2" in t or "windows 2012 r2" in t or "win2k12r2" in t or "win2012r2" in t:
|
|
87
|
+
return "Windows Server 2012 R2"
|
|
88
|
+
if "windows server 2022" in t or "windows 2022" in t or "win2k22" in t or "win2022" in t:
|
|
79
89
|
return "Windows Server 2022"
|
|
80
|
-
if "windows server 2019" in t or "win2k19" in t or "win2019" in t:
|
|
90
|
+
if "windows server 2019" in t or "windows 2019" in t or "win2k19" in t or "win2019" in t:
|
|
81
91
|
return "Windows Server 2019"
|
|
82
|
-
if "windows server 2016" in t or "win2k16" in t or "win2016" in t:
|
|
92
|
+
if "windows server 2016" in t or "windows 2016" in t or "win2k16" in t or "win2016" in t:
|
|
83
93
|
return "Windows Server 2016"
|
|
84
|
-
if "windows server 2012" in t or "win2k12" in t or "win2012" in t:
|
|
94
|
+
if "windows server 2012" in t or "windows 2012" in t or "win2k12" in t or "win2012" in t:
|
|
85
95
|
return "Windows Server 2012"
|
|
86
|
-
if "windows server 2008" in t or "
|
|
96
|
+
if "windows server 2008 r2" in t or "windows 2008 r2" in t or "win2k8r2" in t or "win2008r2" in t:
|
|
97
|
+
return "Windows Server 2008 R2"
|
|
98
|
+
if "windows server 2008" in t or "windows 2008" in t or "win2k8" in t or "win2008" in t:
|
|
87
99
|
return "Windows Server 2008"
|
|
88
|
-
if "windows server 2003" in t or "win2k3" in t or "win2003" in t:
|
|
100
|
+
if "windows server 2003" in t or "windows 2003" in t or "win2k3" in t or "win2003" in t:
|
|
89
101
|
return "Windows Server 2003"
|
|
90
|
-
if "windows server 2000" in t or "win2k" in t or "win2000" in t:
|
|
102
|
+
if "windows server 2000" in t or "windows 2000" in t or "win2k" in t or "win2000" in t:
|
|
91
103
|
return "Windows Server 2000"
|
|
92
104
|
|
|
93
105
|
if "windows" in t:
|
|
@@ -97,7 +109,20 @@ def _detect_product_from_text(t: str) -> str:
|
|
|
97
109
|
|
|
98
110
|
def _detect_edition(text: str) -> str | None:
|
|
99
111
|
m = WIN_EDITION_RE.search(text)
|
|
100
|
-
|
|
112
|
+
if not m:
|
|
113
|
+
return None
|
|
114
|
+
token = m.group(1).lower()
|
|
115
|
+
norm = {
|
|
116
|
+
"pro": "Professional",
|
|
117
|
+
"professional": "Professional",
|
|
118
|
+
"enterprise": "Enterprise",
|
|
119
|
+
"home": "Home",
|
|
120
|
+
"education": "Education",
|
|
121
|
+
"ltsc": "LTSC",
|
|
122
|
+
"datacenter": "Datacenter",
|
|
123
|
+
"standard": "Standard",
|
|
124
|
+
}
|
|
125
|
+
return norm.get(token, token.title())
|
|
101
126
|
|
|
102
127
|
|
|
103
128
|
def _parse_service_pack(text: str, p: OSData) -> None:
|
|
@@ -138,26 +163,42 @@ def _apply_nt_mapping(text: str, p: OSData, server_like: bool) -> None:
|
|
|
138
163
|
p.product = product
|
|
139
164
|
|
|
140
165
|
|
|
141
|
-
def _apply_build_mapping(text: str, p: OSData) -> None:
|
|
166
|
+
def _apply_build_mapping(text: str, p: OSData, server_like: bool) -> None:
|
|
142
167
|
m = WIN_BUILD_RE.search(text)
|
|
143
168
|
if not m:
|
|
144
169
|
return
|
|
145
170
|
build_num = int(m.group(1))
|
|
146
171
|
p.version_build = str(build_num)
|
|
147
172
|
|
|
148
|
-
# Kernel version
|
|
149
|
-
if
|
|
150
|
-
p.
|
|
151
|
-
|
|
152
|
-
|
|
173
|
+
# Kernel version string
|
|
174
|
+
if not p.kernel_version:
|
|
175
|
+
if (p.product == "Windows 10/11") or ("10.0" in text):
|
|
176
|
+
p.kernel_version = f"10.0.{build_num}"
|
|
177
|
+
else:
|
|
178
|
+
nt_mm = WIN_NT_RE.search(text)
|
|
179
|
+
if nt_mm:
|
|
180
|
+
maj, minr = int(nt_mm.group(1)), int(nt_mm.group(2))
|
|
181
|
+
p.kernel_version = f"{maj}.{minr}.{build_num}"
|
|
153
182
|
|
|
154
|
-
# Only apply client build mapping if current product isn't an explicit Server
|
|
155
183
|
is_server_product = isinstance(p.product, str) and "server" in p.product.lower()
|
|
156
|
-
if
|
|
184
|
+
if is_server_product or server_like:
|
|
185
|
+
# Apply server build mapping; do not override explicit server product names
|
|
186
|
+
for lo, hi, product_name, marketing in WINDOWS_SERVER_BUILD_MAP:
|
|
187
|
+
if lo <= build_num <= hi:
|
|
188
|
+
if not p.product or p.product in ("Windows", "Windows 10/11"):
|
|
189
|
+
p.product = product_name
|
|
190
|
+
# Only set channel for modern Server (2016+)
|
|
191
|
+
if build_num >= 14393:
|
|
192
|
+
p.channel = p.channel or marketing
|
|
193
|
+
break
|
|
194
|
+
else:
|
|
195
|
+
# Apply client build mapping
|
|
157
196
|
for lo, hi, product_name, marketing in WINDOWS_BUILD_MAP:
|
|
158
197
|
if lo <= build_num <= hi:
|
|
159
|
-
|
|
160
|
-
|
|
198
|
+
# Only use build map to set product for Windows 10/11 trains
|
|
199
|
+
if build_num >= 10240:
|
|
200
|
+
p.product = product_name
|
|
201
|
+
p.channel = p.channel or marketing
|
|
161
202
|
break
|
|
162
203
|
|
|
163
204
|
|
|
@@ -177,6 +218,14 @@ def _apply_full_kernel_and_channel(text: str, p: OSData) -> None:
|
|
|
177
218
|
if ch and not p.channel:
|
|
178
219
|
p.channel = ch.group(1).upper()
|
|
179
220
|
|
|
221
|
+
if not p.kernel_version:
|
|
222
|
+
m2 = WIN_GENERIC_VERSION_RE.search(text)
|
|
223
|
+
if m2:
|
|
224
|
+
major, minor, build, suffix = m2.groups()
|
|
225
|
+
p.kernel_version = f"{major}.{minor}.{build}{('.' + suffix) if suffix else ''}"
|
|
226
|
+
p.version_build = p.version_build or build
|
|
227
|
+
p.evidence.setdefault("nt_version", f"{major}.{minor}")
|
|
228
|
+
|
|
180
229
|
|
|
181
230
|
def _finalize_precision_and_version(p: OSData) -> None:
|
|
182
231
|
if p.version_build:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|