flask-tor 1.0.3__py3-none-any.whl → 1.1.0__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.
- flask_tor/__init__.py +1 -1
- flask_tor/flask_tor.py +13 -3
- flask_tor/onion.py +30 -7
- flask_tor/onionstart.py +35 -4
- {flask_tor-1.0.3.dist-info → flask_tor-1.1.0.dist-info}/METADATA +8 -4
- flask_tor-1.1.0.dist-info/RECORD +13 -0
- {flask_tor-1.0.3.dist-info → flask_tor-1.1.0.dist-info}/WHEEL +1 -1
- flask_tor/onionshare.py +0 -61
- flask_tor/web.py +0 -400
- flask_tor-1.0.3.dist-info/RECORD +0 -15
- {flask_tor-1.0.3.dist-info → flask_tor-1.1.0.dist-info}/LICENSE +0 -0
- {flask_tor-1.0.3.dist-info → flask_tor-1.1.0.dist-info}/top_level.txt +0 -0
flask_tor/__init__.py
CHANGED
flask_tor/flask_tor.py
CHANGED
@@ -1,8 +1,18 @@
|
|
1
1
|
from .onion import *
|
2
2
|
from .onionstart import OnionStart
|
3
|
-
import sys, threading
|
3
|
+
import sys, threading, os
|
4
4
|
|
5
|
-
def run_with_tor():
|
5
|
+
def run_with_tor(persistent_key_file=None, reuse_key=True):
|
6
|
+
"""
|
7
|
+
Start Flask app with Tor hidden service.
|
8
|
+
|
9
|
+
Args:
|
10
|
+
persistent_key_file (str, optional): Path to a file for saving/loading the onion service private key. If provided, the same onion address will be reused across runs (unless reuse_key is False). If None, a new address is generated each run.
|
11
|
+
reuse_key (bool, optional): If True (default), reuse the key in persistent_key_file for the onion address. If False, always generate a new address and overwrite the file.
|
12
|
+
|
13
|
+
Returns:
|
14
|
+
int: The port number to use for the Flask app.
|
15
|
+
"""
|
6
16
|
# Start the Onion object
|
7
17
|
onion = Onion()
|
8
18
|
try:
|
@@ -15,7 +25,7 @@ def run_with_tor():
|
|
15
25
|
|
16
26
|
# Start the onionshare app
|
17
27
|
try:
|
18
|
-
app_tor = OnionStart(onion)
|
28
|
+
app_tor = OnionStart(onion, persistent_key_file=persistent_key_file, reuse_key=reuse_key)
|
19
29
|
# app_tor.set_stealth(stealth)
|
20
30
|
app_tor.start_onion_service()
|
21
31
|
except KeyboardInterrupt:
|
flask_tor/onion.py
CHANGED
@@ -105,9 +105,11 @@ class Onion(object):
|
|
105
105
|
is necessary for status updates to reach the GUI.
|
106
106
|
"""
|
107
107
|
def __init__(self):
|
108
|
-
|
109
108
|
self.stealth = False
|
110
109
|
self.service_id = None
|
110
|
+
# Private key (eg: 'ED25519-V3:AAAA....') captured from ADD_ONION so it can
|
111
|
+
# optionally be reused to get the same onion address in future runs.
|
112
|
+
self.private_key = None
|
111
113
|
|
112
114
|
self.system = platform.system()
|
113
115
|
|
@@ -342,7 +344,7 @@ class Onion(object):
|
|
342
344
|
# ephemeral stealth onion services are not supported
|
343
345
|
self.supports_stealth = False
|
344
346
|
|
345
|
-
def start_onion_service(self, port):
|
347
|
+
def start_onion_service(self, port, existing_key=None):
|
346
348
|
"""
|
347
349
|
Start a onion service on port 80, pointing to the given port, and
|
348
350
|
return the onion hostname.
|
@@ -362,11 +364,22 @@ class Onion(object):
|
|
362
364
|
basic_auth = None
|
363
365
|
|
364
366
|
try:
|
365
|
-
if
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
367
|
+
if existing_key:
|
368
|
+
# Re-create an onion service using a previously saved key
|
369
|
+
try:
|
370
|
+
key_type, key_content = existing_key.split(':', 1)
|
371
|
+
except ValueError:
|
372
|
+
raise TorErrorInvalidSetting('invalid_saved_private_key')
|
373
|
+
if basic_auth is not None:
|
374
|
+
res = self.c.create_ephemeral_hidden_service({80: port}, key_type=key_type, key_content=key_content, await_publication=True, basic_auth=basic_auth)
|
375
|
+
else:
|
376
|
+
res = self.c.create_ephemeral_hidden_service({80: port}, key_type=key_type, key_content=key_content, await_publication=True)
|
377
|
+
else:
|
378
|
+
if basic_auth is not None:
|
379
|
+
res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=True, basic_auth=basic_auth)
|
380
|
+
else:
|
381
|
+
# if the stem interface is older than 1.5.0, basic_auth isn't a valid keyword arg
|
382
|
+
res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=True)
|
370
383
|
|
371
384
|
except ProtocolError:
|
372
385
|
raise TorErrorProtocolError('error_tor_protocol_error')
|
@@ -374,6 +387,16 @@ class Onion(object):
|
|
374
387
|
self.service_id = res.content()[0][2].split('=')[1]
|
375
388
|
onion_host = self.service_id + '.onion'
|
376
389
|
|
390
|
+
# Extract and store the private key if Tor returned it (Tor omits this
|
391
|
+
# line when we supplied our own existing key).
|
392
|
+
try:
|
393
|
+
for line in res.content():
|
394
|
+
if line[2].startswith('PrivateKey='):
|
395
|
+
self.private_key = line[2].split('=',1)[1]
|
396
|
+
break
|
397
|
+
except Exception:
|
398
|
+
pass
|
399
|
+
|
377
400
|
if self.stealth:
|
378
401
|
auth_cookie = res.content()[2][2].split('=')[1].split(':')[1]
|
379
402
|
self.auth_string = 'HidServAuth {} {}'.format(onion_host, auth_cookie)
|
flask_tor/onionstart.py
CHANGED
@@ -7,15 +7,25 @@ class OnionStart(object):
|
|
7
7
|
OnionShare is the main application class. Pass in options and run
|
8
8
|
start_onion_service and it will do the magic.
|
9
9
|
"""
|
10
|
-
def __init__(self, onion, local_only=False, stay_open=False):
|
11
|
-
|
10
|
+
def __init__(self, onion, local_only=False, stay_open=False, persistent_key_file=None, reuse_key=True):
|
11
|
+
"""
|
12
|
+
Args:
|
13
|
+
onion (Onion): The Onion controller object.
|
14
|
+
local_only (bool): If True, only bind to localhost (no Tor).
|
15
|
+
stay_open (bool): Unused, for compatibility.
|
16
|
+
persistent_key_file (str, optional): Path to file for saving/loading the onion service private key. If provided, the same onion address will be reused across runs (unless reuse_key is False).
|
17
|
+
reuse_key (bool, optional): If True (default), reuse the key in persistent_key_file for the onion address. If False, always generate a new address and overwrite the file.
|
18
|
+
"""
|
12
19
|
# The Onion object
|
13
20
|
self.onion = onion
|
14
|
-
|
15
21
|
self.hidserv_dir = None
|
16
22
|
self.onion_host = None
|
17
23
|
self.stealth = None
|
18
24
|
self.local_only = local_only
|
25
|
+
# Path to store/load persistent onion private key (text file containing 'ED25519-V3:...' format)
|
26
|
+
self.persistent_key_file = os.path.expanduser(persistent_key_file) if persistent_key_file else None
|
27
|
+
print(self.persistent_key_file)
|
28
|
+
self.reuse_key = reuse_key
|
19
29
|
|
20
30
|
def start_onion_service(self):
|
21
31
|
"""
|
@@ -29,7 +39,28 @@ class OnionStart(object):
|
|
29
39
|
self.onion_host = '127.0.0.1:{0:d}'.format(self.port)
|
30
40
|
return
|
31
41
|
|
32
|
-
|
42
|
+
existing_key = None
|
43
|
+
if self.persistent_key_file and self.reuse_key and os.path.exists(self.persistent_key_file):
|
44
|
+
try:
|
45
|
+
with open(self.persistent_key_file, 'r') as f:
|
46
|
+
existing_key = f.read().strip() or None
|
47
|
+
except Exception:
|
48
|
+
existing_key = None
|
49
|
+
|
50
|
+
self.onion_host = self.onion.start_onion_service(self.port, existing_key=existing_key)
|
51
|
+
|
52
|
+
# Save newly generated key if we didn't reuse one
|
53
|
+
if self.persistent_key_file and (self.onion.private_key is not None):
|
54
|
+
# Only save if either no existing file or we generated a new key
|
55
|
+
if (not existing_key) or (existing_key and existing_key != self.onion.private_key):
|
56
|
+
try:
|
57
|
+
parent_dir = os.path.dirname(self.persistent_key_file)
|
58
|
+
if parent_dir:
|
59
|
+
os.makedirs(parent_dir, exist_ok=True)
|
60
|
+
with open(self.persistent_key_file, 'w') as f:
|
61
|
+
f.write(self.onion.private_key)
|
62
|
+
except Exception as e:
|
63
|
+
print(f"[flask-tor] Failed to save persistent key file '{self.persistent_key_file}': {e}")
|
33
64
|
|
34
65
|
if self.stealth:
|
35
66
|
self.auth_string = self.onion.auth_string
|
@@ -1,13 +1,12 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: flask-tor
|
3
|
-
Version: 1.0
|
3
|
+
Version: 1.1.0
|
4
4
|
Summary: A simple way to run Flask apps on tor from your machine.
|
5
5
|
Home-page: https://github.com/jakbin/flask-tor
|
6
6
|
Author: Jak Bin
|
7
7
|
License: MIT License
|
8
8
|
Project-URL: Bug Tracker, https://github.com/jakbin/flask-tor/issues
|
9
9
|
Keywords: flask,tor,onion
|
10
|
-
Platform: UNKNOWN
|
11
10
|
Classifier: Development Status :: 4 - Beta
|
12
11
|
Classifier: Programming Language :: Python :: 3.6
|
13
12
|
Classifier: License :: OSI Approved :: MIT License
|
@@ -34,6 +33,7 @@ Use it only for educational purpose.
|
|
34
33
|
## Features
|
35
34
|
- No need root permission
|
36
35
|
- Multiple instances
|
36
|
+
- Optional persistent onion address (reuse the same .onion between runs)
|
37
37
|
|
38
38
|
## Compatability
|
39
39
|
Python 3.6+ is required.
|
@@ -54,7 +54,7 @@ from flask import Flask
|
|
54
54
|
from flask_tor import run_with_tor
|
55
55
|
|
56
56
|
app = Flask(__name__)
|
57
|
-
port = run_with_tor()
|
57
|
+
port = run_with_tor(persistent_key_file='~/.flask_tor/hidden_service_key')
|
58
58
|
|
59
59
|
@app.route("/")
|
60
60
|
def hello():
|
@@ -72,7 +72,11 @@ connecting_to_tor: 100% - Done
|
|
72
72
|
* Serving Flask app "main"
|
73
73
|
* Debug mode: off
|
74
74
|
* Running on http://127.0.0.1:<port>/
|
75
|
+
|
76
|
+
If you pass a persistent_key_file path, the first run will create a key file and subsequent runs will reuse it so the onion address stays the same. Set reuse_key=False to force a new address while still updating the stored key.
|
75
77
|
```
|
76
78
|
|
77
|
-
|
79
|
+
## Tutorial
|
80
|
+
[Watch Here](https://youtu.be/gmssaGzRT8M)
|
78
81
|
|
82
|
+
### Credit :- [onionshare](https://github.com/onionshare/onionshare)
|
@@ -0,0 +1,13 @@
|
|
1
|
+
flask_tor/__init__.py,sha256=hV56naldJN-3Q9Iffd0qxNh0RsdEc-jcL7-7E7Ov-Pg,59
|
2
|
+
flask_tor/common.py,sha256=wEQ-nQsBhA7mKkt-FCnpmaOpwsK3dTqkLhjgZu6L6Sw,4492
|
3
|
+
flask_tor/flask_tor.py,sha256=63Dx_22gOdAp60UYmUWfaWkQJSBWJQ9BFvJzZPQjUBA,1505
|
4
|
+
flask_tor/onion.py,sha256=uNsGZPIMgLhgY6i5emmV1An1RpH2-kc70AgkAebUVQE,18281
|
5
|
+
flask_tor/onionstart.py,sha256=U93oJ9oWEH8Ifk5VRxQqDrZ8bDQb8hqhwW1TdXEHJwY,2992
|
6
|
+
flask_tor/settings.py,sha256=dVSZrIOnCPW2CLjtmdqGbIbk51MFO_5Qm9p762SDpm4,3146
|
7
|
+
flask_tor/torrc_template,sha256=9hah8SkySKsEfpTCxkQbfmdUSmfuAuWFSdli_AJRC_Y,242
|
8
|
+
flask_tor/torrc_template-windows,sha256=iJAGV0jGKxSuWmUU0IFsVPZLJXtKCdpiQKBSod2sIgo,238
|
9
|
+
flask_tor-1.1.0.dist-info/LICENSE,sha256=fcHwilQ6HuERLVuHUY0uEcSJBl0tLJn_QUy0IhoqhkI,1064
|
10
|
+
flask_tor-1.1.0.dist-info/METADATA,sha256=nC_Urkn5u3psubwiaQVYoRRCyliNlZ3kFhrlYL9nJHc,2650
|
11
|
+
flask_tor-1.1.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
12
|
+
flask_tor-1.1.0.dist-info/top_level.txt,sha256=qfcTTXgsiR9ZfbDewrmFNFnID7jXBgnwS8e5h5Uv2pk,10
|
13
|
+
flask_tor-1.1.0.dist-info/RECORD,,
|
flask_tor/onionshare.py
DELETED
@@ -1,61 +0,0 @@
|
|
1
|
-
import os, shutil
|
2
|
-
|
3
|
-
from . import common
|
4
|
-
|
5
|
-
class OnionShare(object):
|
6
|
-
"""
|
7
|
-
OnionShare is the main application class. Pass in options and run
|
8
|
-
start_onion_service and it will do the magic.
|
9
|
-
"""
|
10
|
-
def __init__(self, onion, local_only=False, stay_open=False):
|
11
|
-
|
12
|
-
# The Onion object
|
13
|
-
self.onion = onion
|
14
|
-
|
15
|
-
self.hidserv_dir = None
|
16
|
-
self.onion_host = None
|
17
|
-
self.stealth = None
|
18
|
-
|
19
|
-
# files and dirs to delete on shutdown
|
20
|
-
self.cleanup_filenames = []
|
21
|
-
|
22
|
-
# do not use tor -- for development
|
23
|
-
self.local_only = local_only
|
24
|
-
|
25
|
-
# automatically close when download is finished
|
26
|
-
self.stay_open = stay_open
|
27
|
-
|
28
|
-
def set_stealth(self, stealth):
|
29
|
-
|
30
|
-
self.stealth = stealth
|
31
|
-
self.onion.stealth = stealth
|
32
|
-
|
33
|
-
def start_onion_service(self):
|
34
|
-
"""
|
35
|
-
Start the onionshare onion service.
|
36
|
-
"""
|
37
|
-
|
38
|
-
# Choose a random port
|
39
|
-
self.port = common.get_available_port(17600, 17650)
|
40
|
-
|
41
|
-
if self.local_only:
|
42
|
-
self.onion_host = '127.0.0.1:{0:d}'.format(self.port)
|
43
|
-
return
|
44
|
-
|
45
|
-
self.onion_host = self.onion.start_onion_service(self.port)
|
46
|
-
|
47
|
-
if self.stealth:
|
48
|
-
self.auth_string = self.onion.auth_string
|
49
|
-
|
50
|
-
def cleanup(self):
|
51
|
-
"""
|
52
|
-
Shut everything down and clean up temporary files, etc.
|
53
|
-
"""
|
54
|
-
|
55
|
-
# cleanup files
|
56
|
-
for filename in self.cleanup_filenames:
|
57
|
-
if os.path.isfile(filename):
|
58
|
-
os.remove(filename)
|
59
|
-
elif os.path.isdir(filename):
|
60
|
-
shutil.rmtree(filename)
|
61
|
-
self.cleanup_filenames = []
|
flask_tor/web.py
DELETED
@@ -1,400 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
"""
|
3
|
-
OnionShare | https://onionshare.org/
|
4
|
-
|
5
|
-
Copyright (C) 2017 Micah Lee <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
|
-
from distutils.version import StrictVersion as Version
|
21
|
-
import queue, mimetypes, platform, os, sys, socket, logging, hmac
|
22
|
-
from urllib.request import urlopen
|
23
|
-
|
24
|
-
from flask import Flask, Response, request, render_template_string, abort, make_response
|
25
|
-
from flask import __version__ as flask_version
|
26
|
-
|
27
|
-
from . import strings, common
|
28
|
-
|
29
|
-
|
30
|
-
def _safe_select_jinja_autoescape(self, filename):
|
31
|
-
if filename is None:
|
32
|
-
return True
|
33
|
-
return filename.endswith(('.html', '.htm', '.xml', '.xhtml'))
|
34
|
-
|
35
|
-
# Starting in Flask 0.11, render_template_string autoescapes template variables
|
36
|
-
# by default. To prevent content injection through template variables in
|
37
|
-
# earlier versions of Flask, we force autoescaping in the Jinja2 template
|
38
|
-
# engine if we detect a Flask version with insecure default behavior.
|
39
|
-
if Version(flask_version) < Version('0.11'):
|
40
|
-
# Monkey-patch in the fix from https://github.com/pallets/flask/commit/99c99c4c16b1327288fd76c44bc8635a1de452bc
|
41
|
-
Flask.select_jinja_autoescape = _safe_select_jinja_autoescape
|
42
|
-
|
43
|
-
app = Flask(__name__)
|
44
|
-
|
45
|
-
# information about the file
|
46
|
-
file_info = []
|
47
|
-
zip_filename = None
|
48
|
-
zip_filesize = None
|
49
|
-
|
50
|
-
security_headers = [
|
51
|
-
('Content-Security-Policy', 'default-src \'self\'; style-src \'unsafe-inline\'; img-src \'self\' data:;'),
|
52
|
-
('X-Frame-Options', 'DENY'),
|
53
|
-
('X-Xss-Protection', '1; mode=block'),
|
54
|
-
('X-Content-Type-Options', 'nosniff'),
|
55
|
-
('Referrer-Policy', 'no-referrer'),
|
56
|
-
('Server', 'OnionShare')
|
57
|
-
]
|
58
|
-
|
59
|
-
def set_file_info(filenames, processed_size_callback=None):
|
60
|
-
"""
|
61
|
-
Using the list of filenames being shared, fill in details that the web
|
62
|
-
page will need to display. This includes zipping up the file in order to
|
63
|
-
get the zip file's name and size.
|
64
|
-
"""
|
65
|
-
global file_info, zip_filename, zip_filesize
|
66
|
-
|
67
|
-
# build file info list
|
68
|
-
file_info = {'files': [], 'dirs': []}
|
69
|
-
for filename in filenames:
|
70
|
-
info = {
|
71
|
-
'filename': filename,
|
72
|
-
'basename': os.path.basename(filename.rstrip('/'))
|
73
|
-
}
|
74
|
-
if os.path.isfile(filename):
|
75
|
-
info['size'] = os.path.getsize(filename)
|
76
|
-
info['size_human'] = common.human_readable_filesize(info['size'])
|
77
|
-
file_info['files'].append(info)
|
78
|
-
if os.path.isdir(filename):
|
79
|
-
info['size'] = common.dir_size(filename)
|
80
|
-
info['size_human'] = common.human_readable_filesize(info['size'])
|
81
|
-
file_info['dirs'].append(info)
|
82
|
-
file_info['files'] = sorted(file_info['files'], key=lambda k: k['basename'])
|
83
|
-
file_info['dirs'] = sorted(file_info['dirs'], key=lambda k: k['basename'])
|
84
|
-
|
85
|
-
# zip up the files and folders
|
86
|
-
z = common.ZipWriter(processed_size_callback=processed_size_callback)
|
87
|
-
for info in file_info['files']:
|
88
|
-
z.add_file(info['filename'])
|
89
|
-
for info in file_info['dirs']:
|
90
|
-
z.add_dir(info['filename'])
|
91
|
-
z.close()
|
92
|
-
zip_filename = z.zip_filename
|
93
|
-
zip_filesize = os.path.getsize(zip_filename)
|
94
|
-
|
95
|
-
|
96
|
-
REQUEST_LOAD = 0
|
97
|
-
REQUEST_DOWNLOAD = 1
|
98
|
-
REQUEST_PROGRESS = 2
|
99
|
-
REQUEST_OTHER = 3
|
100
|
-
REQUEST_CANCELED = 4
|
101
|
-
REQUEST_RATE_LIMIT = 5
|
102
|
-
q = queue.Queue()
|
103
|
-
|
104
|
-
|
105
|
-
def add_request(request_type, path, data=None):
|
106
|
-
"""
|
107
|
-
Add a request to the queue, to communicate with the GUI.
|
108
|
-
"""
|
109
|
-
global q
|
110
|
-
q.put({
|
111
|
-
'type': request_type,
|
112
|
-
'path': path,
|
113
|
-
'data': data
|
114
|
-
})
|
115
|
-
|
116
|
-
|
117
|
-
slug = None
|
118
|
-
def generate_slug():
|
119
|
-
global slug
|
120
|
-
slug = common.build_slug()
|
121
|
-
|
122
|
-
download_count = 0
|
123
|
-
error404_count = 0
|
124
|
-
|
125
|
-
stay_open = False
|
126
|
-
def set_stay_open(new_stay_open):
|
127
|
-
"""
|
128
|
-
Set stay_open variable.
|
129
|
-
"""
|
130
|
-
global stay_open
|
131
|
-
stay_open = new_stay_open
|
132
|
-
def get_stay_open():
|
133
|
-
"""
|
134
|
-
Get stay_open variable.
|
135
|
-
"""
|
136
|
-
return stay_open
|
137
|
-
|
138
|
-
|
139
|
-
# Are we running in GUI mode?
|
140
|
-
gui_mode = False
|
141
|
-
def set_gui_mode():
|
142
|
-
"""
|
143
|
-
Tell the web service that we're running in GUI mode
|
144
|
-
"""
|
145
|
-
global gui_mode
|
146
|
-
gui_mode = True
|
147
|
-
|
148
|
-
def debug_mode():
|
149
|
-
"""
|
150
|
-
Turn on debugging mode, which will log flask errors to a debug file.
|
151
|
-
"""
|
152
|
-
if platform.system() == 'Windows':
|
153
|
-
temp_dir = os.environ['Temp'].replace('\\', '/')
|
154
|
-
else:
|
155
|
-
temp_dir = '/tmp/'
|
156
|
-
|
157
|
-
log_handler = logging.FileHandler('{0:s}/onionshare_server.log'.format(temp_dir))
|
158
|
-
log_handler.setLevel(logging.WARNING)
|
159
|
-
app.logger.addHandler(log_handler)
|
160
|
-
|
161
|
-
def check_slug_candidate(slug_candidate, slug_compare = None):
|
162
|
-
global slug
|
163
|
-
if not slug_compare:
|
164
|
-
slug_compare = slug
|
165
|
-
if not hmac.compare_digest(slug_compare, slug_candidate):
|
166
|
-
abort(404)
|
167
|
-
|
168
|
-
|
169
|
-
# If "Stop After First Download" is checked (stay_open == False), only allow
|
170
|
-
# one download at a time.
|
171
|
-
download_in_progress = False
|
172
|
-
|
173
|
-
@app.route("/<slug_candidate>")
|
174
|
-
def index(slug_candidate):
|
175
|
-
"""
|
176
|
-
Render the template for the onionshare landing page.
|
177
|
-
"""
|
178
|
-
check_slug_candidate(slug_candidate)
|
179
|
-
|
180
|
-
add_request(REQUEST_LOAD, request.path)
|
181
|
-
|
182
|
-
# Deny new downloads if "Stop After First Download" is checked and there is
|
183
|
-
# currently a download
|
184
|
-
global stay_open, download_in_progress
|
185
|
-
deny_download = not stay_open and download_in_progress
|
186
|
-
if deny_download:
|
187
|
-
r = make_response(render_template_string(open(common.get_resource_path('html/denied.html')).read()))
|
188
|
-
for header,value in security_headers:
|
189
|
-
r.headers.set(header, value)
|
190
|
-
return r
|
191
|
-
|
192
|
-
# If download is allowed to continue, serve download page
|
193
|
-
|
194
|
-
r = make_response(render_template_string(
|
195
|
-
open(common.get_resource_path('html/index.html')).read(),
|
196
|
-
slug=slug,
|
197
|
-
file_info=file_info,
|
198
|
-
filename=os.path.basename(zip_filename),
|
199
|
-
filesize=zip_filesize,
|
200
|
-
filesize_human=common.human_readable_filesize(zip_filesize)))
|
201
|
-
for header,value in security_headers:
|
202
|
-
r.headers.set(header, value)
|
203
|
-
return r
|
204
|
-
|
205
|
-
# If the client closes the OnionShare window while a download is in progress,
|
206
|
-
# it should immediately stop serving the file. The client_cancel global is
|
207
|
-
# used to tell the download function that the client is canceling the download.
|
208
|
-
client_cancel = False
|
209
|
-
|
210
|
-
@app.route("/<slug_candidate>/download")
|
211
|
-
def download(slug_candidate):
|
212
|
-
"""
|
213
|
-
Download the zip file.
|
214
|
-
"""
|
215
|
-
check_slug_candidate(slug_candidate)
|
216
|
-
|
217
|
-
# Deny new downloads if "Stop After First Download" is checked and there is
|
218
|
-
# currently a download
|
219
|
-
global stay_open, download_in_progress
|
220
|
-
deny_download = not stay_open and download_in_progress
|
221
|
-
if deny_download:
|
222
|
-
r = make_response(render_template_string(open(common.get_resource_path('html/denied.html')).read()))
|
223
|
-
for header,value in security_headers:
|
224
|
-
r.headers.set(header, value)
|
225
|
-
return r
|
226
|
-
|
227
|
-
global download_count
|
228
|
-
|
229
|
-
# each download has a unique id
|
230
|
-
download_id = download_count
|
231
|
-
download_count += 1
|
232
|
-
|
233
|
-
# prepare some variables to use inside generate() function below
|
234
|
-
# which is outside of the request context
|
235
|
-
shutdown_func = request.environ.get('werkzeug.server.shutdown')
|
236
|
-
path = request.path
|
237
|
-
|
238
|
-
# tell GUI the download started
|
239
|
-
add_request(REQUEST_DOWNLOAD, path, {'id': download_id})
|
240
|
-
|
241
|
-
dirname = os.path.dirname(zip_filename)
|
242
|
-
basename = os.path.basename(zip_filename)
|
243
|
-
|
244
|
-
def generate():
|
245
|
-
# The user hasn't canceled the download
|
246
|
-
global client_cancel, gui_mode
|
247
|
-
client_cancel = False
|
248
|
-
|
249
|
-
# Starting a new download
|
250
|
-
global stay_open, download_in_progress
|
251
|
-
if not stay_open:
|
252
|
-
download_in_progress = True
|
253
|
-
|
254
|
-
chunk_size = 102400 # 100kb
|
255
|
-
|
256
|
-
fp = open(zip_filename, 'rb')
|
257
|
-
done = False
|
258
|
-
canceled = False
|
259
|
-
while not done:
|
260
|
-
# The user has canceled the download, so stop serving the file
|
261
|
-
if client_cancel:
|
262
|
-
add_request(REQUEST_CANCELED, path, {'id': download_id})
|
263
|
-
break
|
264
|
-
|
265
|
-
chunk = fp.read(chunk_size)
|
266
|
-
if chunk == b'':
|
267
|
-
done = True
|
268
|
-
else:
|
269
|
-
try:
|
270
|
-
yield chunk
|
271
|
-
|
272
|
-
# tell GUI the progress
|
273
|
-
downloaded_bytes = fp.tell()
|
274
|
-
percent = (1.0 * downloaded_bytes / zip_filesize) * 100
|
275
|
-
|
276
|
-
# only output to stdout if running onionshare in CLI mode, or if using Linux (#203, #304)
|
277
|
-
if not gui_mode or common.get_platform() == 'Linux':
|
278
|
-
sys.stdout.write(
|
279
|
-
"\r{0:s}, {1:.2f}% ".format(common.human_readable_filesize(downloaded_bytes), percent))
|
280
|
-
sys.stdout.flush()
|
281
|
-
|
282
|
-
add_request(REQUEST_PROGRESS, path, {'id': download_id, 'bytes': downloaded_bytes})
|
283
|
-
except:
|
284
|
-
# looks like the download was canceled
|
285
|
-
done = True
|
286
|
-
canceled = True
|
287
|
-
|
288
|
-
# tell the GUI the download has canceled
|
289
|
-
add_request(REQUEST_CANCELED, path, {'id': download_id})
|
290
|
-
|
291
|
-
fp.close()
|
292
|
-
|
293
|
-
if common.get_platform() != 'Darwin':
|
294
|
-
sys.stdout.write("\n")
|
295
|
-
|
296
|
-
# Download is finished
|
297
|
-
if not stay_open:
|
298
|
-
download_in_progress = False
|
299
|
-
|
300
|
-
# Close the server, if necessary
|
301
|
-
if not stay_open and not canceled:
|
302
|
-
print(strings._("closing_automatically"))
|
303
|
-
if shutdown_func is None:
|
304
|
-
raise RuntimeError('Not running with the Werkzeug Server')
|
305
|
-
shutdown_func()
|
306
|
-
|
307
|
-
r = Response(generate())
|
308
|
-
r.headers.set('Content-Length', zip_filesize)
|
309
|
-
r.headers.set('Content-Disposition', 'attachment', filename=basename)
|
310
|
-
for header,value in security_headers:
|
311
|
-
r.headers.set(header, value)
|
312
|
-
# guess content type
|
313
|
-
(content_type, _) = mimetypes.guess_type(basename, strict=False)
|
314
|
-
if content_type is not None:
|
315
|
-
r.headers.set('Content-Type', content_type)
|
316
|
-
return r
|
317
|
-
|
318
|
-
|
319
|
-
@app.errorhandler(404)
|
320
|
-
def page_not_found(e):
|
321
|
-
"""
|
322
|
-
404 error page.
|
323
|
-
"""
|
324
|
-
add_request(REQUEST_OTHER, request.path)
|
325
|
-
|
326
|
-
global error404_count
|
327
|
-
if request.path != '/favicon.ico':
|
328
|
-
error404_count += 1
|
329
|
-
if error404_count == 20:
|
330
|
-
add_request(REQUEST_RATE_LIMIT, request.path)
|
331
|
-
force_shutdown()
|
332
|
-
print(strings._('error_rate_limit'))
|
333
|
-
|
334
|
-
r = make_response(render_template_string(open(common.get_resource_path('html/404.html')).read()))
|
335
|
-
for header,value in security_headers:
|
336
|
-
r.headers.set(header, value)
|
337
|
-
return r
|
338
|
-
|
339
|
-
# shutting down the server only works within the context of flask, so the easiest way to do it is over http
|
340
|
-
shutdown_slug = common.random_string(16)
|
341
|
-
|
342
|
-
|
343
|
-
@app.route("/<slug_candidate>/shutdown")
|
344
|
-
def shutdown(slug_candidate):
|
345
|
-
"""
|
346
|
-
Stop the flask web server, from the context of an http request.
|
347
|
-
"""
|
348
|
-
check_slug_candidate(slug_candidate, shutdown_slug)
|
349
|
-
force_shutdown()
|
350
|
-
return ""
|
351
|
-
|
352
|
-
|
353
|
-
def force_shutdown():
|
354
|
-
"""
|
355
|
-
Stop the flask web server, from the context of the flask app.
|
356
|
-
"""
|
357
|
-
# shutdown the flask service
|
358
|
-
func = request.environ.get('werkzeug.server.shutdown')
|
359
|
-
if func is None:
|
360
|
-
raise RuntimeError('Not running with the Werkzeug Server')
|
361
|
-
func()
|
362
|
-
|
363
|
-
|
364
|
-
def start(port, stay_open=False):
|
365
|
-
"""
|
366
|
-
Start the flask web server.
|
367
|
-
"""
|
368
|
-
generate_slug()
|
369
|
-
|
370
|
-
set_stay_open(stay_open)
|
371
|
-
|
372
|
-
# In Whonix, listen on 0.0.0.0 instead of 127.0.0.1 (#220)
|
373
|
-
if os.path.exists('/usr/share/anon-ws-base-files/workstation'):
|
374
|
-
host = '0.0.0.0'
|
375
|
-
else:
|
376
|
-
host = '127.0.0.1'
|
377
|
-
|
378
|
-
app.run(host=host, port=port, threaded=True)
|
379
|
-
|
380
|
-
|
381
|
-
def stop(port):
|
382
|
-
"""
|
383
|
-
Stop the flask web server by loading /shutdown.
|
384
|
-
"""
|
385
|
-
|
386
|
-
# If the user cancels the download, let the download function know to stop
|
387
|
-
# serving the file
|
388
|
-
global client_cancel
|
389
|
-
client_cancel = True
|
390
|
-
|
391
|
-
# to stop flask, load http://127.0.0.1:<port>/<shutdown_slug>/shutdown
|
392
|
-
try:
|
393
|
-
s = socket.socket()
|
394
|
-
s.connect(('127.0.0.1', port))
|
395
|
-
s.sendall('GET /{0:s}/shutdown HTTP/1.1\r\n\r\n'.format(shutdown_slug))
|
396
|
-
except:
|
397
|
-
try:
|
398
|
-
urlopen('http://127.0.0.1:{0:d}/{1:s}/shutdown'.format(port, shutdown_slug)).read()
|
399
|
-
except:
|
400
|
-
pass
|
flask_tor-1.0.3.dist-info/RECORD
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
flask_tor/__init__.py,sha256=wTlDr-FMRcqV97C3mu2O7-Z3WAVEuJHX3XrErrBEmTQ,59
|
2
|
-
flask_tor/common.py,sha256=wEQ-nQsBhA7mKkt-FCnpmaOpwsK3dTqkLhjgZu6L6Sw,4492
|
3
|
-
flask_tor/flask_tor.py,sha256=uSYzeeQw-cYYj-vD83Aag69aAi6-PsytoW_tHEN1zp4,831
|
4
|
-
flask_tor/onion.py,sha256=fqSdmKKW42d4zKiA9dOiulEzLhQ57usCXs2qIaXGeA8,16971
|
5
|
-
flask_tor/onionshare.py,sha256=yMpcKKIP3KLg43tSWxOrdK0e4o5aOnMDjLl0KMQcD1Y,1619
|
6
|
-
flask_tor/onionstart.py,sha256=Z0_9orkUeOTF4eKoluKRgPrCO9h0DuOe7Y1CgmUaXy4,913
|
7
|
-
flask_tor/settings.py,sha256=dVSZrIOnCPW2CLjtmdqGbIbk51MFO_5Qm9p762SDpm4,3146
|
8
|
-
flask_tor/torrc_template,sha256=9hah8SkySKsEfpTCxkQbfmdUSmfuAuWFSdli_AJRC_Y,242
|
9
|
-
flask_tor/torrc_template-windows,sha256=iJAGV0jGKxSuWmUU0IFsVPZLJXtKCdpiQKBSod2sIgo,238
|
10
|
-
flask_tor/web.py,sha256=Hbqqs2hlj2n3Zo6LoCvL-_E6U0OqBNRddzxHG_gf5rs,12775
|
11
|
-
flask_tor-1.0.3.dist-info/LICENSE,sha256=fcHwilQ6HuERLVuHUY0uEcSJBl0tLJn_QUy0IhoqhkI,1064
|
12
|
-
flask_tor-1.0.3.dist-info/METADATA,sha256=4bWdHbiQ4IwyGHEOoaHWfVszD9MXRZkD_tRvT8g9nkg,2258
|
13
|
-
flask_tor-1.0.3.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92
|
14
|
-
flask_tor-1.0.3.dist-info/top_level.txt,sha256=qfcTTXgsiR9ZfbDewrmFNFnID7jXBgnwS8e5h5Uv2pk,10
|
15
|
-
flask_tor-1.0.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|