Skip to content

Commit 4de30de

Browse files
authored
Merge branch 'master' into fix-typos
2 parents 9e1cab3 + 124b7bb commit 4de30de

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1671
-1153
lines changed

.github/workflows/tests-macos.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
name: MacOS Tests
2+
3+
on:
4+
push:
5+
branches: [ master ]
6+
pull_request:
7+
branches: [ master ]
8+
9+
jobs:
10+
build:
11+
runs-on: macos-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
- run: ./share/adapters/rfc.sh

.github/workflows/tests-ubuntu.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ on:
1010

1111
jobs:
1212
build:
13-
runs-on: ubuntu-20.04
13+
runs-on: ubuntu-24.04
1414
steps:
15-
- uses: actions/checkout@v2
15+
- uses: actions/checkout@v4
16+
- run: ./share/adapters/rfc.sh
1617
- name: install dependencies
1718
run: pip install --upgrade -r requirements.txt
1819
- name: fetch upstream cheat sheets

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2017 Igor Chubin
3+
Copyright (c) 2025 Igor Chubin
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

bin/app.py

Lines changed: 87 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@
1717
from __future__ import print_function
1818

1919
import sys
20+
2021
if sys.version_info[0] < 3:
2122
reload(sys)
22-
sys.setdefaultencoding('utf8')
23+
sys.setdefaultencoding("utf8")
2324

2425
import sys
2526
import logging
@@ -43,7 +44,8 @@
4344
logging.basicConfig(
4445
filename=CONFIG["path.log.main"],
4546
level=logging.DEBUG,
46-
format='%(asctime)s %(message)s')
47+
format="%(asctime)s %(message)s",
48+
)
4749
# Fix Flask "exception and request logging" to `stderr`.
4850
#
4951
# When Flask's werkzeug detects that logging is already set, it
@@ -52,24 +54,28 @@
5254
logging.getLogger().addHandler(stderr_handler)
5355
#
5456
# Alter log format to disting log lines from everything else
55-
stderr_handler.setFormatter(logging.Formatter('%(filename)s:%(lineno)s: %(message)s'))
57+
stderr_handler.setFormatter(logging.Formatter("%(filename)s:%(lineno)s: %(message)s"))
58+
59+
5660
#
5761
# Sometimes werkzeug starts logging before an app is imported
5862
# (https://github.com/pallets/werkzeug/issues/1969)
5963
# resulting in duplicating lines. In that case we need root
6064
# stderr handler to skip lines from werkzeug.
6165
class SkipFlaskLogger(object):
6266
def filter(self, record):
63-
if record.name != 'werkzeug':
67+
if record.name != "werkzeug":
6468
return True
65-
if logging.getLogger('werkzeug').handlers:
69+
70+
71+
if logging.getLogger("werkzeug").handlers:
6672
stderr_handler.addFilter(SkipFlaskLogger())
6773

6874

69-
app = Flask(__name__) # pylint: disable=invalid-name
70-
app.jinja_loader = jinja2.ChoiceLoader([
71-
app.jinja_loader,
72-
jinja2.FileSystemLoader(CONFIG["path.internal.templates"])])
75+
app = Flask(__name__) # pylint: disable=invalid-name
76+
app.jinja_loader = jinja2.ChoiceLoader(
77+
[app.jinja_loader, jinja2.FileSystemLoader(CONFIG["path.internal.templates"])]
78+
)
7379

7480
LIMITS = Limits()
7581

@@ -85,32 +91,37 @@ def filter(self, record):
8591
"aiohttp",
8692
]
8793

94+
8895
def _is_html_needed(user_agent):
8996
"""
9097
Basing on `user_agent`, return whether it needs HTML or ANSI
9198
"""
9299
return all([x not in user_agent for x in PLAIN_TEXT_AGENTS])
93100

101+
94102
def is_result_a_script(query):
95-
return query in [':cht.sh']
103+
return query in [":cht.sh"]
104+
96105

97-
@app.route('/files/<path:path>')
106+
@app.route("/files/<path:path>")
98107
def send_static(path):
99108
"""
100109
Return static file `path`.
101110
Can be served by the HTTP frontend.
102111
"""
103112
return send_from_directory(CONFIG["path.internal.static"], path)
104113

105-
@app.route('/favicon.ico')
114+
115+
@app.route("/favicon.ico")
106116
def send_favicon():
107117
"""
108118
Return static file `favicon.ico`.
109119
Can be served by the HTTP frontend.
110120
"""
111-
return send_from_directory(CONFIG["path.internal.static"], 'favicon.ico')
121+
return send_from_directory(CONFIG["path.internal.static"], "favicon.ico")
122+
112123

113-
@app.route('/malformed-response.html')
124+
@app.route("/malformed-response.html")
114125
def send_malformed():
115126
"""
116127
Return static file `malformed-response.html`.
@@ -119,13 +130,15 @@ def send_malformed():
119130
dirname, filename = os.path.split(CONFIG["path.internal.malformed"])
120131
return send_from_directory(dirname, filename)
121132

133+
122134
def log_query(ip_addr, found, topic, user_agent):
123135
"""
124136
Log processed query and some internal data
125137
"""
126138
log_entry = "%s %s %s %s\n" % (ip_addr, found, topic, user_agent)
127-
with open(CONFIG["path.log.queries"], 'ab') as my_file:
128-
my_file.write(log_entry.encode('utf-8'))
139+
with open(CONFIG["path.log.queries"], "ab") as my_file:
140+
my_file.write(log_entry.encode("utf-8"))
141+
129142

130143
def get_request_ip(req):
131144
"""
@@ -134,19 +147,20 @@ def get_request_ip(req):
134147

135148
if req.headers.getlist("X-Forwarded-For"):
136149
ip_addr = req.headers.getlist("X-Forwarded-For")[0]
137-
if ip_addr.startswith('::ffff:'):
150+
if ip_addr.startswith("::ffff:"):
138151
ip_addr = ip_addr[7:]
139152
else:
140153
ip_addr = req.remote_addr
141154
if req.headers.getlist("X-Forwarded-For"):
142155
ip_addr = req.headers.getlist("X-Forwarded-For")[0]
143-
if ip_addr.startswith('::ffff:'):
156+
if ip_addr.startswith("::ffff:"):
144157
ip_addr = ip_addr[7:]
145158
else:
146159
ip_addr = req.remote_addr
147160

148161
return ip_addr
149162

163+
150164
def get_answer_language(request):
151165
"""
152166
Return preferred answer language based on
@@ -174,26 +188,26 @@ def _parse_accept_language(accept_language):
174188
def _find_supported_language(accepted_languages):
175189
for lang_tuple in accepted_languages:
176190
lang = lang_tuple[0]
177-
if '-' in lang:
178-
lang = lang.split('-', 1)[0]
191+
if "-" in lang:
192+
lang = lang.split("-", 1)[0]
179193
return lang
180194
return None
181195

182196
lang = None
183-
hostname = request.headers['Host']
184-
if hostname.endswith('.cheat.sh'):
197+
hostname = request.headers["Host"]
198+
if hostname.endswith(".cheat.sh"):
185199
lang = hostname[:-9]
186200

187-
if 'lang' in request.args:
188-
lang = request.args.get('lang')
201+
if "lang" in request.args:
202+
lang = request.args.get("lang")
189203

190-
header_accept_language = request.headers.get('Accept-Language', '')
204+
header_accept_language = request.headers.get("Accept-Language", "")
191205
if lang is None and header_accept_language:
192-
lang = _find_supported_language(
193-
_parse_accept_language(header_accept_language))
206+
lang = _find_supported_language(_parse_accept_language(header_accept_language))
194207

195208
return lang
196209

210+
197211
def _proxy(*args, **kwargs):
198212
# print "method=", request.method,
199213
# print "url=", request.url.replace('/:shell-x/', ':3000/')
@@ -202,32 +216,41 @@ def _proxy(*args, **kwargs):
202216
# print "cookies=", request.cookies
203217
# print "allow_redirects=", False
204218

205-
url_before, url_after = request.url.split('/:shell-x/', 1)
206-
url = url_before + ':3000/'
219+
url_before, url_after = request.url.split("/:shell-x/", 1)
220+
url = url_before + ":3000/"
207221

208-
if 'q' in request.args:
209-
url_after = '?' + "&".join("arg=%s" % x for x in request.args['q'].split())
222+
if "q" in request.args:
223+
url_after = "?" + "&".join("arg=%s" % x for x in request.args["q"].split())
210224

211225
url += url_after
212226
print(url)
213227
print(request.get_data())
214228
resp = requests.request(
215229
method=request.method,
216230
url=url,
217-
headers={key: value for (key, value) in request.headers if key != 'Host'},
231+
headers={key: value for (key, value) in request.headers if key != "Host"},
218232
data=request.get_data(),
219233
cookies=request.cookies,
220-
allow_redirects=False)
221-
222-
excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection']
223-
headers = [(name, value) for (name, value) in resp.raw.headers.items()
224-
if name.lower() not in excluded_headers]
234+
allow_redirects=False,
235+
)
236+
237+
excluded_headers = [
238+
"content-encoding",
239+
"content-length",
240+
"transfer-encoding",
241+
"connection",
242+
]
243+
headers = [
244+
(name, value)
245+
for (name, value) in resp.raw.headers.items()
246+
if name.lower() not in excluded_headers
247+
]
225248

226249
response = Response(resp.content, resp.status_code, headers)
227250
return response
228251

229252

230-
@app.route("/", methods=['GET', 'POST'])
253+
@app.route("/", methods=["GET", "POST"])
231254
@app.route("/<path:topic>", methods=["GET", "POST"])
232255
def answer(topic=None):
233256
"""
@@ -242,16 +265,19 @@ def answer(topic=None):
242265
request.query_string
243266
"""
244267

245-
user_agent = request.headers.get('User-Agent', '').lower()
268+
user_agent = request.headers.get("User-Agent", "").lower()
246269
html_needed = _is_html_needed(user_agent)
247270
options = parse_args(request.args)
248271

249-
if topic in ['apple-touch-icon-precomposed.png', 'apple-touch-icon.png', 'apple-touch-icon-120x120-precomposed.png'] \
250-
or (topic is not None and any(topic.endswith('/'+x) for x in ['favicon.ico'])):
251-
return ''
272+
if topic in [
273+
"apple-touch-icon-precomposed.png",
274+
"apple-touch-icon.png",
275+
"apple-touch-icon-120x120-precomposed.png",
276+
] or (topic is not None and any(topic.endswith("/" + x) for x in ["favicon.ico"])):
277+
return ""
252278

253-
request_id = request.cookies.get('id')
254-
if topic is not None and topic.lstrip('/') == ':last':
279+
request_id = request.cookies.get("id")
280+
if topic is not None and topic.lstrip("/") == ":last":
255281
if request_id:
256282
topic = last_query(request_id)
257283
else:
@@ -260,43 +286,47 @@ def answer(topic=None):
260286
if request_id:
261287
save_query(request_id, topic)
262288

263-
if request.method == 'POST':
289+
if request.method == "POST":
264290
process_post_request(request, html_needed)
265291
if html_needed:
266292
return redirect("/")
267293
return "OK\n"
268294

269-
if 'topic' in request.args:
270-
return redirect("/%s" % request.args.get('topic'))
295+
if "topic" in request.args:
296+
return redirect("/%s" % request.args.get("topic"))
271297

272298
if topic is None:
273299
topic = ":firstpage"
274300

275-
if topic.startswith(':shell-x/'):
301+
if topic.startswith(":shell-x/"):
276302
return _proxy()
277-
#return requests.get('http://127.0.0.1:3000'+topic[8:]).text
303+
# return requests.get('http://127.0.0.1:3000'+topic[8:]).text
278304

279305
lang = get_answer_language(request)
280306
if lang:
281-
options['lang'] = lang
307+
options["lang"] = lang
282308

283309
ip_address = get_request_ip(request)
284-
if '+' in topic:
310+
if "+" in topic:
285311
not_allowed = LIMITS.check_ip(ip_address)
286312
if not_allowed:
287313
return "429 %s\n" % not_allowed, 429
288314

289315
html_is_needed = _is_html_needed(user_agent) and not is_result_a_script(topic)
290316
if html_is_needed:
291-
output_format='html'
317+
output_format = "html"
292318
else:
293-
output_format='ansi'
294-
result, found = cheat_wrapper(topic, request_options=options, output_format=output_format)
295-
if 'Please come back in several hours' in result and html_is_needed:
296-
malformed_response = open(os.path.join(CONFIG["path.internal.malformed"])).read()
319+
output_format = "ansi"
320+
result, found = cheat_wrapper(
321+
topic, request_options=options, output_format=output_format
322+
)
323+
if "Please come back in several hours" in result and html_is_needed:
324+
malformed_response = open(
325+
os.path.join(CONFIG["path.internal.malformed"])
326+
).read()
297327
return malformed_response
298328

299329
log_query(ip_address, found, topic, user_agent)
300330
if html_is_needed:
301331
return result
302-
return Response(result, mimetype='text/plain')
332+
return Response(result, mimetype="text/plain")

bin/clean_cache.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import sys
22
import redis
3-
REDIS = redis.Redis(host='localhost', port=6379, db=0)
3+
4+
REDIS = redis.Redis(host="localhost", port=6379, db=0)
45

56
for key in sys.argv[1:]:
67
REDIS.delete(key)
7-

bin/srv.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from gevent.monkey import patch_all
77
from gevent.pywsgi import WSGIServer
8+
89
patch_all()
910

1011
import os
@@ -13,16 +14,16 @@
1314
from app import app, CONFIG
1415

1516

16-
if '--debug' in sys.argv:
17+
if "--debug" in sys.argv:
1718
# Not all debug mode features are available under `gevent`
1819
# https://github.com/pallets/flask/issues/3825
1920
app.debug = True
2021

21-
if 'CHEATSH_PORT' in os.environ:
22-
port = int(os.environ.get('CHEATSH_PORT'))
22+
if "CHEATSH_PORT" in os.environ:
23+
port = int(os.environ.get("CHEATSH_PORT"))
2324
else:
24-
port = CONFIG['server.port']
25+
port = CONFIG["server.port"]
2526

26-
srv = WSGIServer((CONFIG['server.bind'], port), app)
27+
srv = WSGIServer((CONFIG["server.bind"], port), app)
2728
print("Starting gevent server on {}:{}".format(srv.address[0], srv.address[1]))
2829
srv.serve_forever()

0 commit comments

Comments
 (0)