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 CHANGED
@@ -1,3 +1,3 @@
1
1
  from .flask_tor import run_with_tor
2
2
 
3
- __version__ = '1.0.3'
3
+ __version__ = '1.1.0'
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 basic_auth != None :
366
- res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=True, basic_auth=basic_auth)
367
- else :
368
- # if the stem interface is older than 1.5.0, basic_auth isn't a valid keyword arg
369
- res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=True)
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
- self.onion_host = self.onion.start_onion_service(self.port)
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
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
- ### Credit :- [onionshare](https://github.com/onionshare/onionshare)
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.34.2)
2
+ Generator: bdist_wheel (0.42.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
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
@@ -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,,