onionshare_cli 2.3__py3-none-any.whl

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.
Files changed (45) hide show
  1. onionshare_cli/__init__.py +522 -0
  2. onionshare_cli/common.py +296 -0
  3. onionshare_cli/mode_settings.py +146 -0
  4. onionshare_cli/onion.py +845 -0
  5. onionshare_cli/onionshare.py +110 -0
  6. onionshare_cli/resources/static/css/style.css +393 -0
  7. onionshare_cli/resources/static/img/ajax.gif +0 -0
  8. onionshare_cli/resources/static/img/favicon.ico +0 -0
  9. onionshare_cli/resources/static/img/logo.png +0 -0
  10. onionshare_cli/resources/static/img/logo_large.png +0 -0
  11. onionshare_cli/resources/static/img/web_file.png +0 -0
  12. onionshare_cli/resources/static/img/web_folder.png +0 -0
  13. onionshare_cli/resources/static/js/chat.js +173 -0
  14. onionshare_cli/resources/static/js/jquery-3.5.1.min.js +2 -0
  15. onionshare_cli/resources/static/js/receive.js +130 -0
  16. onionshare_cli/resources/static/js/send.js +75 -0
  17. onionshare_cli/resources/static/js/socket.io.min.js +3 -0
  18. onionshare_cli/resources/templates/401.html +21 -0
  19. onionshare_cli/resources/templates/403.html +21 -0
  20. onionshare_cli/resources/templates/404.html +21 -0
  21. onionshare_cli/resources/templates/405.html +21 -0
  22. onionshare_cli/resources/templates/chat.html +46 -0
  23. onionshare_cli/resources/templates/denied.html +14 -0
  24. onionshare_cli/resources/templates/listing.html +59 -0
  25. onionshare_cli/resources/templates/receive.html +46 -0
  26. onionshare_cli/resources/templates/send.html +69 -0
  27. onionshare_cli/resources/templates/thankyou.html +27 -0
  28. onionshare_cli/resources/torrc_template +8 -0
  29. onionshare_cli/resources/torrc_template-meek_lite_amazon +2 -0
  30. onionshare_cli/resources/torrc_template-meek_lite_azure +2 -0
  31. onionshare_cli/resources/torrc_template-obfs4 +27 -0
  32. onionshare_cli/resources/version.txt +1 -0
  33. onionshare_cli/resources/wordlist.txt +7776 -0
  34. onionshare_cli/settings.py +201 -0
  35. onionshare_cli/web/__init__.py +21 -0
  36. onionshare_cli/web/chat_mode.py +169 -0
  37. onionshare_cli/web/receive_mode.py +488 -0
  38. onionshare_cli/web/send_base_mode.py +338 -0
  39. onionshare_cli/web/share_mode.py +406 -0
  40. onionshare_cli/web/web.py +424 -0
  41. onionshare_cli/web/website_mode.py +123 -0
  42. onionshare_cli-2.3.dist-info/METADATA +34 -0
  43. onionshare_cli-2.3.dist-info/RECORD +45 -0
  44. onionshare_cli-2.3.dist-info/WHEEL +4 -0
  45. onionshare_cli-2.3.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,296 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ OnionShare | https://onionshare.org/
4
+
5
+ Copyright (C) 2014-2020 Micah Lee, et al. <micah@micahflee.com>
6
+
7
+ This program is free software: you can redistribute it and/or modify
8
+ it under the terms of the GNU General Public License as published by
9
+ the Free Software Foundation, either version 3 of the License, or
10
+ (at your option) any later version.
11
+
12
+ This program is distributed in the hope that it will be useful,
13
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ GNU General Public License for more details.
16
+
17
+ You should have received a copy of the GNU General Public License
18
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
19
+ """
20
+ import base64
21
+ import hashlib
22
+ import inspect
23
+ import os
24
+ import platform
25
+ import random
26
+ import socket
27
+ import sys
28
+ import tempfile
29
+ import threading
30
+ import time
31
+ import shutil
32
+ from pkg_resources import resource_filename
33
+
34
+ from .settings import Settings
35
+
36
+
37
+ class CannotFindTor(Exception):
38
+ """
39
+ OnionShare can't find a tor binary
40
+ """
41
+
42
+
43
+ class Common:
44
+ """
45
+ The Common object is shared amongst all parts of OnionShare.
46
+ """
47
+
48
+ def __init__(self, verbose=False):
49
+ self.verbose = verbose
50
+
51
+ # The platform OnionShare is running on
52
+ self.platform = platform.system()
53
+ if self.platform.endswith("BSD") or self.platform == "DragonFly":
54
+ self.platform = "BSD"
55
+
56
+ # The current version of OnionShare
57
+ with open(self.get_resource_path("version.txt")) as f:
58
+ self.version = f.read().strip()
59
+
60
+ def load_settings(self, config=None):
61
+ """
62
+ Loading settings, optionally from a custom config json file.
63
+ """
64
+ self.settings = Settings(self, config)
65
+ self.settings.load()
66
+
67
+ def log(self, module, func, msg=None):
68
+ """
69
+ If verbose mode is on, log error messages to stdout
70
+ """
71
+ if self.verbose:
72
+ timestamp = time.strftime("%b %d %Y %X")
73
+
74
+ final_msg = f"[{timestamp}] {module}.{func}"
75
+ if msg:
76
+ final_msg = f"{final_msg}: {msg}"
77
+ print(final_msg)
78
+
79
+ def get_resource_path(self, filename):
80
+ """
81
+ Returns the absolute path of a resource
82
+ """
83
+ self.log("Common", "get_resource_path", f"filename={filename}")
84
+ path = resource_filename("onionshare_cli", os.path.join("resources", filename))
85
+ self.log("Common", "get_resource_path", f"filename={filename}, path={path}")
86
+ return path
87
+
88
+ def get_tor_paths(self):
89
+ if self.platform == "Linux":
90
+ tor_path = shutil.which("tor")
91
+ if not tor_path:
92
+ raise CannotFindTor()
93
+ obfs4proxy_file_path = shutil.which("obfs4proxy")
94
+ prefix = os.path.dirname(os.path.dirname(tor_path))
95
+ tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip")
96
+ tor_geo_ipv6_file_path = os.path.join(prefix, "share/tor/geoip6")
97
+ elif self.platform == "Windows":
98
+ base_path = self.get_resource_path("tor")
99
+ tor_path = os.path.join(base_path, "Tor", "tor.exe")
100
+ obfs4proxy_file_path = os.path.join(base_path, "Tor", "obfs4proxy.exe")
101
+ tor_geo_ip_file_path = os.path.join(base_path, "Data", "Tor", "geoip")
102
+ tor_geo_ipv6_file_path = os.path.join(base_path, "Data", "Tor", "geoip6")
103
+ elif self.platform == "Darwin":
104
+ tor_path = shutil.which("tor")
105
+ if not tor_path:
106
+ raise CannotFindTor()
107
+ obfs4proxy_file_path = shutil.which("obfs4proxy")
108
+ prefix = os.path.dirname(os.path.dirname(tor_path))
109
+ tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip")
110
+ tor_geo_ipv6_file_path = os.path.join(prefix, "share/tor/geoip6")
111
+ elif self.platform == "BSD":
112
+ tor_path = "/usr/local/bin/tor"
113
+ tor_geo_ip_file_path = "/usr/local/share/tor/geoip"
114
+ tor_geo_ipv6_file_path = "/usr/local/share/tor/geoip6"
115
+ obfs4proxy_file_path = "/usr/local/bin/obfs4proxy"
116
+
117
+ return (
118
+ tor_path,
119
+ tor_geo_ip_file_path,
120
+ tor_geo_ipv6_file_path,
121
+ obfs4proxy_file_path,
122
+ )
123
+
124
+ def build_data_dir(self):
125
+ """
126
+ Returns the path of the OnionShare data directory.
127
+ """
128
+ if self.platform == "Windows":
129
+ try:
130
+ appdata = os.environ["APPDATA"]
131
+ onionshare_data_dir = f"{appdata}\\OnionShare"
132
+ except:
133
+ # If for some reason we don't have the 'APPDATA' environment variable
134
+ # (like running tests in Linux while pretending to be in Windows)
135
+ onionshare_data_dir = os.path.expanduser("~/.config/onionshare")
136
+ elif self.platform == "Darwin":
137
+ onionshare_data_dir = os.path.expanduser(
138
+ "~/Library/Application Support/OnionShare"
139
+ )
140
+ else:
141
+ onionshare_data_dir = os.path.expanduser("~/.config/onionshare")
142
+
143
+ # Modify the data dir if running tests
144
+ if getattr(sys, "onionshare_test_mode", False):
145
+ onionshare_data_dir += "-testdata"
146
+
147
+ os.makedirs(onionshare_data_dir, 0o700, True)
148
+ return onionshare_data_dir
149
+
150
+ def build_tmp_dir(self):
151
+ """
152
+ Returns path to a folder that can hold temporary files
153
+ """
154
+ tmp_dir = os.path.join(self.build_data_dir(), "tmp")
155
+ os.makedirs(tmp_dir, 0o700, True)
156
+ return tmp_dir
157
+
158
+ def build_persistent_dir(self):
159
+ """
160
+ Returns the path to the folder that holds persistent files
161
+ """
162
+ persistent_dir = os.path.join(self.build_data_dir(), "persistent")
163
+ os.makedirs(persistent_dir, 0o700, True)
164
+ return persistent_dir
165
+
166
+ def build_tor_dir(self):
167
+ """
168
+ Returns path to the tor data directory
169
+ """
170
+ tor_dir = os.path.join(self.build_data_dir(), "tor_data")
171
+ os.makedirs(tor_dir, 0o700, True)
172
+ return tor_dir
173
+
174
+ def build_password(self, word_count=2):
175
+ """
176
+ Returns a random string made of words from the wordlist, such as "deter-trig".
177
+ """
178
+ with open(self.get_resource_path("wordlist.txt")) as f:
179
+ wordlist = f.read().split()
180
+
181
+ r = random.SystemRandom()
182
+ return "-".join(r.choice(wordlist) for _ in range(word_count))
183
+
184
+ def build_username(self, word_count=2):
185
+ """
186
+ Returns a random string made of words from the wordlist, such as "deter-trig".
187
+ """
188
+ with open(self.get_resource_path("wordlist.txt")) as f:
189
+ wordlist = f.read().split()
190
+
191
+ r = random.SystemRandom()
192
+ return "-".join(r.choice(wordlist) for _ in range(word_count))
193
+
194
+ @staticmethod
195
+ def random_string(num_bytes, output_len=None):
196
+ """
197
+ Returns a random string with a specified number of bytes.
198
+ """
199
+ b = os.urandom(num_bytes)
200
+ h = hashlib.sha256(b).digest()[:16]
201
+ s = base64.b32encode(h).lower().replace(b"=", b"").decode("utf-8")
202
+ if not output_len:
203
+ return s
204
+ return s[:output_len]
205
+
206
+ @staticmethod
207
+ def human_readable_filesize(b):
208
+ """
209
+ Returns filesize in a human readable format.
210
+ """
211
+ thresh = 1024.0
212
+ if b < thresh:
213
+ return "{:.1f} B".format(b)
214
+ units = ("KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB")
215
+ u = 0
216
+ b /= thresh
217
+ while b >= thresh:
218
+ b /= thresh
219
+ u += 1
220
+ return "{:.1f} {}".format(b, units[u])
221
+
222
+ @staticmethod
223
+ def format_seconds(seconds):
224
+ """Return a human-readable string of the format 1d2h3m4s"""
225
+ days, seconds = divmod(seconds, 86400)
226
+ hours, seconds = divmod(seconds, 3600)
227
+ minutes, seconds = divmod(seconds, 60)
228
+
229
+ human_readable = []
230
+ if days:
231
+ human_readable.append("{:.0f}d".format(days))
232
+ if hours:
233
+ human_readable.append("{:.0f}h".format(hours))
234
+ if minutes:
235
+ human_readable.append("{:.0f}m".format(minutes))
236
+ if seconds or not human_readable:
237
+ human_readable.append("{:.0f}s".format(seconds))
238
+ return "".join(human_readable)
239
+
240
+ @staticmethod
241
+ def estimated_time_remaining(bytes_downloaded, total_bytes, started):
242
+ now = time.time()
243
+ time_elapsed = now - started # in seconds
244
+ download_rate = bytes_downloaded / time_elapsed
245
+ remaining_bytes = total_bytes - bytes_downloaded
246
+ eta = remaining_bytes / download_rate
247
+ return Common.format_seconds(eta)
248
+
249
+ @staticmethod
250
+ def get_available_port(min_port, max_port):
251
+ """
252
+ Find a random available port within the given range.
253
+ """
254
+ with socket.socket() as tmpsock:
255
+ while True:
256
+ try:
257
+ tmpsock.bind(("127.0.0.1", random.randint(min_port, max_port)))
258
+ break
259
+ except OSError as e:
260
+ pass
261
+ _, port = tmpsock.getsockname()
262
+ return port
263
+
264
+ @staticmethod
265
+ def dir_size(start_path):
266
+ """
267
+ Calculates the total size, in bytes, of all of the files in a directory.
268
+ """
269
+ total_size = 0
270
+ for dirpath, dirnames, filenames in os.walk(start_path):
271
+ for f in filenames:
272
+ fp = os.path.join(dirpath, f)
273
+ if not os.path.islink(fp):
274
+ total_size += os.path.getsize(fp)
275
+ return total_size
276
+
277
+
278
+ class AutoStopTimer(threading.Thread):
279
+ """
280
+ Background thread sleeps t hours and returns.
281
+ """
282
+
283
+ def __init__(self, common, time):
284
+ threading.Thread.__init__(self)
285
+
286
+ self.common = common
287
+
288
+ self.setDaemon(True)
289
+ self.time = time
290
+
291
+ def run(self):
292
+ self.common.log(
293
+ "AutoStopTimer", f"Server will shut down after {self.time} seconds"
294
+ )
295
+ time.sleep(self.time)
296
+ return 1
@@ -0,0 +1,146 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ OnionShare | https://onionshare.org/
4
+
5
+ Copyright (C) 2014-2020 Micah Lee, et al. <micah@micahflee.com>
6
+
7
+ This program is free software: you can redistribute it and/or modify
8
+ it under the terms of the GNU General Public License as published by
9
+ the Free Software Foundation, either version 3 of the License, or
10
+ (at your option) any later version.
11
+
12
+ This program is distributed in the hope that it will be useful,
13
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ GNU General Public License for more details.
16
+
17
+ You should have received a copy of the GNU General Public License
18
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
19
+ """
20
+ import os
21
+ import json
22
+ import platform
23
+
24
+ if platform.system() == "Darwin":
25
+ import pwd
26
+
27
+
28
+ class ModeSettings:
29
+ """
30
+ This stores the settings for a single instance of an OnionShare mode. In CLI there
31
+ is only one ModeSettings, and in the GUI there is a separate ModeSettings for each tab
32
+ """
33
+
34
+ def __init__(self, common, filename=None, id=None):
35
+ self.common = common
36
+
37
+ self.default_settings = {
38
+ "onion": {
39
+ "private_key": None,
40
+ "hidservauth_string": None,
41
+ "password": None,
42
+ },
43
+ "persistent": {"mode": None, "enabled": False},
44
+ "general": {
45
+ "public": False,
46
+ "autostart_timer": False,
47
+ "autostop_timer": False,
48
+ "legacy": False,
49
+ "client_auth": False,
50
+ "service_id": None,
51
+ },
52
+ "share": {"autostop_sharing": True, "filenames": []},
53
+ "receive": {"data_dir": self.build_default_receive_data_dir()},
54
+ "website": {"disable_csp": False, "filenames": []},
55
+ "chat": {"room": "default"},
56
+ }
57
+ self._settings = {}
58
+
59
+ self.just_created = False
60
+ if id:
61
+ self.id = id
62
+ else:
63
+ self.id = self.common.build_password(3)
64
+
65
+ self.load(filename)
66
+
67
+ def fill_in_defaults(self):
68
+ """
69
+ If there are any missing settings from self._settings, replace them with
70
+ their default values.
71
+ """
72
+ for key in self.default_settings:
73
+ if key in self._settings:
74
+ for inner_key in self.default_settings[key]:
75
+ if inner_key not in self._settings[key]:
76
+ self._settings[key][inner_key] = self.default_settings[key][
77
+ inner_key
78
+ ]
79
+ else:
80
+ self._settings[key] = self.default_settings[key]
81
+
82
+ def get(self, group, key):
83
+ return self._settings[group][key]
84
+
85
+ def set(self, group, key, val):
86
+ self._settings[group][key] = val
87
+ self.common.log(
88
+ "ModeSettings", "set", f"updating {self.id}: {group}.{key} = {val}"
89
+ )
90
+ self.save()
91
+
92
+ def build_default_receive_data_dir(self):
93
+ """
94
+ Returns the path of the default Downloads directory for receive mode.
95
+ """
96
+
97
+ if self.common.platform == "Darwin":
98
+ # We can't use os.path.expanduser() in macOS because in the sandbox it
99
+ # returns the path to the sandboxed homedir
100
+ real_homedir = pwd.getpwuid(os.getuid()).pw_dir
101
+ return os.path.join(real_homedir, "OnionShare")
102
+ elif self.common.platform == "Windows":
103
+ # On Windows, os.path.expanduser() needs to use backslash, or else it
104
+ # retains the forward slash, which breaks opening the folder in explorer.
105
+ return os.path.expanduser("~\\OnionShare")
106
+ else:
107
+ # All other OSes
108
+ return os.path.expanduser("~/OnionShare")
109
+
110
+ def load(self, filename=None):
111
+ # Load persistent settings from disk. If the file doesn't exist, create it
112
+ if filename:
113
+ self.filename = filename
114
+ else:
115
+ self.filename = os.path.join(
116
+ self.common.build_persistent_dir(), f"{self.id}.json"
117
+ )
118
+
119
+ if os.path.exists(self.filename):
120
+ try:
121
+ with open(self.filename, "r") as f:
122
+ self._settings = json.load(f)
123
+ self.fill_in_defaults()
124
+ self.common.log("ModeSettings", "load", f"loaded {self.filename}")
125
+ return
126
+ except:
127
+ pass
128
+
129
+ # If loading settings didn't work, create the settings file
130
+ self.common.log("ModeSettings", "load", f"creating {self.filename}")
131
+ self.fill_in_defaults()
132
+ self.just_created = True
133
+
134
+ def save(self):
135
+ # Save persistent setting to disk
136
+ if not self.get("persistent", "enabled"):
137
+ return
138
+
139
+ if self.filename:
140
+ with open(self.filename, "w") as file:
141
+ file.write(json.dumps(self._settings, indent=2))
142
+
143
+ def delete(self):
144
+ # Delete the file from disk
145
+ if os.path.exists(self.filename):
146
+ os.remove(self.filename)