-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
install_package_control.py
171 lines (130 loc) · 5.46 KB
/
install_package_control.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import base64
import binascii
import os
import threading
from urllib.error import URLError
from urllib.request import build_opener, install_opener, ProxyHandler, urlopen
import certifi
import sublime
import sublime_api
import sublime_plugin
class InstallPackageControlCommand(sublime_plugin.ApplicationCommand):
error_prefix = 'Error installing Package Control: '
filename = 'Package Control.sublime-package'
public_key = (
'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEkiE2JtDn/IQDfVLso4HRg0BNMHNj'
'5rpuEIVaX6txyFS0HoBmCgd+9AXKcgKAsBKbEBD6a9nVzLLmJrDVFafepQ==')
def run(self):
threading.Thread(target=self._install).start()
def is_visible(self):
ipp_path = os.path.join(sublime.installed_packages_path(), self.filename)
p_path = os.path.join(sublime.packages_path(), self.filename.replace('.sublime-package', ''))
return not os.path.exists(ipp_path) and not os.path.exists(p_path)
def _install(self):
"""
RUNS IN A THREAD
Downloads and then installs Package Control, alerting the user to
the result
"""
try:
package_data = self._download()
if package_data is None:
sublime.set_timeout(self._show_error, 10)
return
dest = os.path.join(sublime.installed_packages_path(), self.filename)
with open(dest, 'wb') as f:
f.write(package_data)
sublime.set_timeout(self._show_success, 10)
except (Exception) as e:
print(self.error_prefix + str(e))
sublime.set_timeout(self._show_error, 10)
def _show_success(self):
"""
RUNS IN THE MAIN THREAD
"""
sublime.message_dialog(
"Package Control was successfully installed\n\n"
"Use the Command Palette and type \"Install Package\" to get started")
def _show_error(self):
"""
RUNS IN THE MAIN THREAD
"""
sublime.error_message(
"An error occurred installing Package Control\n\n"
"Please check the Console for details\n\n"
"Visit https://packagecontrol.io/installation for manual instructions")
def _download(self):
"""
RUNS IN A THREAD
Attempts to download over TLS first, falling back to HTTP in case a
user's proxy configuration doesn't work with TLS by default.
Although a secure connection is made, Python 3.3 does not check the
connection hostname against the certificate, so a TLS connection
really only provides privacy. To ensure that the package has not been
modified, we check a public-key signature of the file.
:return:
None or a byte string of the verified package file
"""
host_path = 'packagecontrol.io/' + self.filename.replace(' ', '%20')
# Don't be fooled by the TLS URL, Python 3.3 does not verify hostnames
secure_url = 'https://' + host_path
insecure_url = 'http://' + host_path
secure_sig_url = secure_url + '.sig'
insecure_sig_url = insecure_url + '.sig'
install_opener(build_opener(ProxyHandler()))
try:
package_data = urlopen(secure_url, cafile=certifi.where()).read()
sig_data = urlopen(secure_sig_url, cafile=certifi.where()).read()
except (URLError) as e:
print('%sHTTPS error encountered, falling back to HTTP - %s' % (self.error_prefix, str(e)))
try:
package_data = urlopen(insecure_url).read()
sig_data = urlopen(insecure_sig_url).read()
except (URLError) as e2:
print('%sHTTP error encountered, giving up - %s' % (self.error_prefix, str(e2)))
return None
return self._verify(package_data, sig_data)
def _verify(self, package_data, sig_data):
"""
RUNS IN A THREAD
Verifies the package is authentic
:param package_data:
A byte string of the .sublime-package data
:param sig_data:
A byte string of the .sig data
:return:
None if invalid, byte string of package file otherwise
"""
try:
armored_sig = sig_data.decode('ascii').strip()
except (UnicodeDecodeError):
print(self.error_prefix + 'invalid signature ASCII encoding')
return None
begin = '-----BEGIN PACKAGE CONTROL SIGNATURE-----'
end = '-----END PACKAGE CONTROL SIGNATURE-----'
pem_error = self.error_prefix + 'invalid signature PEM armor'
b64_sig = ''
in_body = None
for line in armored_sig.splitlines():
if not in_body:
if line != begin:
print(pem_error)
return None
in_body = True
else:
if line.startswith('-----'):
if line != end:
print(pem_error)
return None
break
b64_sig += line
try:
sig = base64.b64decode(b64_sig)
except (binascii.Error):
print(self.error_prefix + 'invalid signature base64 decoding')
return None
public_key = base64.b64decode(self.public_key)
if not sublime_api.verify_pc_signature(package_data, sig, public_key):
print(self.error_prefix + 'signature could not be verified')
return None
return package_data