Show More
@@ -1,4 +1,4 b'' | |||||
1 | # util.py - Common packaging utility code. |
|
1 | # downloads.py - Code for downloading dependencies. | |
2 | # |
|
2 | # | |
3 | # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com> |
|
3 | # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com> | |
4 | # |
|
4 | # | |
@@ -10,9 +10,7 b'' | |||||
10 | import gzip |
|
10 | import gzip | |
11 | import hashlib |
|
11 | import hashlib | |
12 | import pathlib |
|
12 | import pathlib | |
13 | import tarfile |
|
|||
14 | import urllib.request |
|
13 | import urllib.request | |
15 | import zipfile |
|
|||
16 |
|
14 | |||
17 |
|
15 | |||
18 | def hash_path(p: pathlib.Path): |
|
16 | def hash_path(p: pathlib.Path): | |
@@ -116,13 +114,3 b' def download_entry(entry: dict, dest_pat' | |||||
116 | download_to_path(url, local_path, entry['size'], entry['sha256']) |
|
114 | download_to_path(url, local_path, entry['size'], entry['sha256']) | |
117 |
|
115 | |||
118 | return local_path |
|
116 | return local_path | |
119 |
|
||||
120 |
|
||||
121 | def extract_tar_to_directory(source: pathlib.Path, dest: pathlib.Path): |
|
|||
122 | with tarfile.open(source, 'r') as tf: |
|
|||
123 | tf.extractall(dest) |
|
|||
124 |
|
||||
125 |
|
||||
126 | def extract_zip_to_directory(source: pathlib.Path, dest: pathlib.Path): |
|
|||
127 | with zipfile.ZipFile(source, 'r') as zf: |
|
|||
128 | zf.extractall(dest) |
|
@@ -7,117 +7,11 b'' | |||||
7 |
|
7 | |||
8 | # no-check-code because Python 3 native. |
|
8 | # no-check-code because Python 3 native. | |
9 |
|
9 | |||
10 | import gzip |
|
|||
11 | import hashlib |
|
|||
12 | import pathlib |
|
10 | import pathlib | |
13 | import tarfile |
|
11 | import tarfile | |
14 | import urllib.request |
|
|||
15 | import zipfile |
|
12 | import zipfile | |
16 |
|
13 | |||
17 |
|
14 | |||
18 | def hash_path(p: pathlib.Path): |
|
|||
19 | h = hashlib.sha256() |
|
|||
20 |
|
||||
21 | with p.open('rb') as fh: |
|
|||
22 | while True: |
|
|||
23 | chunk = fh.read(65536) |
|
|||
24 | if not chunk: |
|
|||
25 | break |
|
|||
26 |
|
||||
27 | h.update(chunk) |
|
|||
28 |
|
||||
29 | return h.hexdigest() |
|
|||
30 |
|
||||
31 |
|
||||
32 | class IntegrityError(Exception): |
|
|||
33 | """Represents an integrity error when downloading a URL.""" |
|
|||
34 |
|
||||
35 |
|
||||
36 | def secure_download_stream(url, size, sha256): |
|
|||
37 | """Securely download a URL to a stream of chunks. |
|
|||
38 |
|
||||
39 | If the integrity of the download fails, an IntegrityError is |
|
|||
40 | raised. |
|
|||
41 | """ |
|
|||
42 | h = hashlib.sha256() |
|
|||
43 | length = 0 |
|
|||
44 |
|
||||
45 | with urllib.request.urlopen(url) as fh: |
|
|||
46 | if not url.endswith('.gz') and fh.info().get('Content-Encoding') == 'gzip': |
|
|||
47 | fh = gzip.GzipFile(fileobj=fh) |
|
|||
48 |
|
||||
49 | while True: |
|
|||
50 | chunk = fh.read(65536) |
|
|||
51 | if not chunk: |
|
|||
52 | break |
|
|||
53 |
|
||||
54 | h.update(chunk) |
|
|||
55 | length += len(chunk) |
|
|||
56 |
|
||||
57 | yield chunk |
|
|||
58 |
|
||||
59 | digest = h.hexdigest() |
|
|||
60 |
|
||||
61 | if length != size: |
|
|||
62 | raise IntegrityError('size mismatch on %s: wanted %d; got %d' % ( |
|
|||
63 | url, size, length)) |
|
|||
64 |
|
||||
65 | if digest != sha256: |
|
|||
66 | raise IntegrityError('sha256 mismatch on %s: wanted %s; got %s' % ( |
|
|||
67 | url, sha256, digest)) |
|
|||
68 |
|
||||
69 |
|
||||
70 | def download_to_path(url: str, path: pathlib.Path, size: int, sha256: str): |
|
|||
71 | """Download a URL to a filesystem path, possibly with verification.""" |
|
|||
72 |
|
||||
73 | # We download to a temporary file and rename at the end so there's |
|
|||
74 | # no chance of the final file being partially written or containing |
|
|||
75 | # bad data. |
|
|||
76 | print('downloading %s to %s' % (url, path)) |
|
|||
77 |
|
||||
78 | if path.exists(): |
|
|||
79 | good = True |
|
|||
80 |
|
||||
81 | if path.stat().st_size != size: |
|
|||
82 | print('existing file size is wrong; removing') |
|
|||
83 | good = False |
|
|||
84 |
|
||||
85 | if good: |
|
|||
86 | if hash_path(path) != sha256: |
|
|||
87 | print('existing file hash is wrong; removing') |
|
|||
88 | good = False |
|
|||
89 |
|
||||
90 | if good: |
|
|||
91 | print('%s exists and passes integrity checks' % path) |
|
|||
92 | return |
|
|||
93 |
|
||||
94 | path.unlink() |
|
|||
95 |
|
||||
96 | tmp = path.with_name('%s.tmp' % path.name) |
|
|||
97 |
|
||||
98 | try: |
|
|||
99 | with tmp.open('wb') as fh: |
|
|||
100 | for chunk in secure_download_stream(url, size, sha256): |
|
|||
101 | fh.write(chunk) |
|
|||
102 | except IntegrityError: |
|
|||
103 | tmp.unlink() |
|
|||
104 | raise |
|
|||
105 |
|
||||
106 | tmp.rename(path) |
|
|||
107 | print('successfully downloaded %s' % url) |
|
|||
108 |
|
||||
109 |
|
||||
110 | def download_entry(entry: dict, dest_path: pathlib.Path, local_name=None) -> pathlib.Path: |
|
|||
111 | url = entry['url'] |
|
|||
112 |
|
||||
113 | local_name = local_name or url[url.rindex('/') + 1:] |
|
|||
114 |
|
||||
115 | local_path = dest_path / local_name |
|
|||
116 | download_to_path(url, local_path, entry['size'], entry['sha256']) |
|
|||
117 |
|
||||
118 | return local_path |
|
|||
119 |
|
||||
120 |
|
||||
121 | def extract_tar_to_directory(source: pathlib.Path, dest: pathlib.Path): |
|
15 | def extract_tar_to_directory(source: pathlib.Path, dest: pathlib.Path): | |
122 | with tarfile.open(source, 'r') as tf: |
|
16 | with tarfile.open(source, 'r') as tf: | |
123 | tf.extractall(dest) |
|
17 | tf.extractall(dest) |
@@ -87,8 +87,10 b' def build(source_dir: pathlib.Path, buil' | |||||
87 | for finding the Python 2.7 toolchain. So, we require the environment |
|
87 | for finding the Python 2.7 toolchain. So, we require the environment | |
88 | to already be configured with an active toolchain. |
|
88 | to already be configured with an active toolchain. | |
89 | """ |
|
89 | """ | |
|
90 | from hgpackaging.downloads import ( | |||
|
91 | download_entry, | |||
|
92 | ) | |||
90 | from hgpackaging.util import ( |
|
93 | from hgpackaging.util import ( | |
91 | download_entry, |
|
|||
92 | extract_tar_to_directory, |
|
94 | extract_tar_to_directory, | |
93 | extract_zip_to_directory, |
|
95 | extract_zip_to_directory, | |
94 | ) |
|
96 | ) |
@@ -12,6 +12,7 b' New errors are not allowed. Warnings are' | |||||
12 | > -X hgext/fsmonitor/pywatchman \ |
|
12 | > -X hgext/fsmonitor/pywatchman \ | |
13 | > -X mercurial/thirdparty \ |
|
13 | > -X mercurial/thirdparty \ | |
14 | > | sed 's-\\-/-g' | "$check_code" --warnings --per-file=0 - || false |
|
14 | > | sed 's-\\-/-g' | "$check_code" --warnings --per-file=0 - || false | |
|
15 | Skipping contrib/packaging/hgpackaging/downloads.py it has no-che?k-code (glob) | |||
15 | Skipping contrib/packaging/hgpackaging/util.py it has no-che?k-code (glob) |
|
16 | Skipping contrib/packaging/hgpackaging/util.py it has no-che?k-code (glob) | |
16 | Skipping contrib/packaging/inno/build.py it has no-che?k-code (glob) |
|
17 | Skipping contrib/packaging/inno/build.py it has no-che?k-code (glob) | |
17 | Skipping i18n/polib.py it has no-che?k-code (glob) |
|
18 | Skipping i18n/polib.py it has no-che?k-code (glob) |
General Comments 0
You need to be logged in to leave comments.
Login now