nibble-cli 0.3.0__tar.gz → 0.3.1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nibble-cli
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Fast local network scanner with hardware identification and a terminal UI
5
5
  Home-page: https://github.com/backendsystems/nibble
6
6
  Author: saberd
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nibble-cli
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Fast local network scanner with hardware identification and a terminal UI
5
5
  Home-page: https://github.com/backendsystems/nibble
6
6
  Author: saberd
@@ -14,6 +14,8 @@ import tempfile
14
14
  import urllib.error
15
15
  import urllib.request
16
16
  import zipfile
17
+ import hashlib
18
+ import re
17
19
  from importlib import metadata
18
20
  from pathlib import Path
19
21
 
@@ -74,6 +76,64 @@ def _download_asset(url, out_path):
74
76
  return False
75
77
  raise
76
78
 
79
+ def _download_text(url):
80
+ try:
81
+ with urllib.request.urlopen(url) as resp:
82
+ return resp.read().decode("utf-8", errors="replace")
83
+ except urllib.error.HTTPError as e:
84
+ if e.code == 404:
85
+ return None
86
+ raise
87
+
88
+ def _parse_checksums(text):
89
+ checksums = {}
90
+ for raw_line in text.splitlines():
91
+ line = raw_line.strip()
92
+ if not line or line.startswith("#"):
93
+ continue
94
+ match = re.match(r"^([A-Fa-f0-9]{64})\s+\*?(.+)$", line)
95
+ if not match:
96
+ continue
97
+ checksums[match.group(2).strip()] = match.group(1).lower()
98
+ return checksums
99
+
100
+ def _sha256_file(path):
101
+ h = hashlib.sha256()
102
+ with open(path, "rb") as f:
103
+ for chunk in iter(lambda: f.read(1024 * 64), b""):
104
+ h.update(chunk)
105
+ return h.hexdigest()
106
+
107
+ def _verify_checksum(version, archive_name, archive_path):
108
+ base_url = f"https://github.com/{REPO}/releases/download/v{version}"
109
+ checksum_candidates = [
110
+ "checksums.txt",
111
+ f"{PROJECT}_{version}_checksums.txt",
112
+ ]
113
+
114
+ checksum_text = None
115
+ checksum_name = None
116
+ for candidate in checksum_candidates:
117
+ text = _download_text(f"{base_url}/{candidate}")
118
+ if text is not None:
119
+ checksum_text = text
120
+ checksum_name = candidate
121
+ break
122
+
123
+ if checksum_text is None:
124
+ raise RuntimeError(
125
+ f"no checksum file found for v{version} (tried: {', '.join(checksum_candidates)})"
126
+ )
127
+
128
+ checksums = _parse_checksums(checksum_text)
129
+ expected = checksums.get(archive_name)
130
+ if expected is None:
131
+ raise RuntimeError(f"checksum for {archive_name} not found in {checksum_name}")
132
+
133
+ actual = _sha256_file(archive_path)
134
+ if actual != expected:
135
+ raise RuntimeError(f"checksum mismatch for {archive_name}")
136
+
77
137
 
78
138
  def _extract_binary(archive_path, dest_binary):
79
139
  bin_names = {_binary_name(), PROJECT, f"{PROJECT}.exe"}
@@ -124,6 +184,7 @@ def ensure_installed():
124
184
  local = tmpdir / asset
125
185
  if _download_asset(url, local):
126
186
  archive_path = local
187
+ _verify_checksum(version, asset, local)
127
188
  break
128
189
  if archive_path is None:
129
190
  raise RuntimeError(
File without changes
File without changes