Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/workflows/tests-macos.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: MacOS Tests

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- run: ./share/adapters/rfc.sh
5 changes: 3 additions & 2 deletions .github/workflows/tests-ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ on:

jobs:
build:
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- run: ./share/adapters/rfc.sh
- name: install dependencies
run: pip install --upgrade -r requirements.txt
- name: fetch upstream cheat sheets
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ RUN apk add --no-cache --virtual build-deps py3-pip g++ python3-dev libffi-dev \
&& pip3 install --no-cache-dir --upgrade pygments \
&& pip3 install --no-cache-dir -r requirements.txt \
&& apk del build-deps
# fetching dependencies
## fetching cheat sheets
RUN mkdir -p /root/.cheat.sh/log/ \
&& python3 lib/fetch.py fetch-all

Expand Down
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -823,15 +823,15 @@ and information sources, maintained by thousands of users, developers and author
all over the world
(in the *Users* column number of contributors/number of stars is shown):

|Cheat sheets |Repository |C/U* |Stars |Creation Date|
|-----------------------|------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------|-------------|
|UNIX/Linux, programming|[cheat.sheets](https://github.com/chubin/cheat.sheets) |![](https://img.shields.io/github/contributors-anon/chubin/cheat.sheets?label=%F0%9F%91%A5&labelColor=white) |![](https://img.shields.io/github/stars/chubin/cheat.sheets?label=%E2%AD%90&labelColor=white) |May 1, 2017 |
|UNIX/Linux commands |[tldr-pages/tldr](https://github.com/tldr-pages/tldr) |![](https://img.shields.io/github/contributors-anon/tldr-pages/tldr?label=%F0%9F%91%A5&labelColor=white) |![](https://img.shields.io/github/stars/tldr-pages/tldr?label=%E2%AD%90&labelColor=white) |Dec 8, 2013 |
|UNIX/Linux commands |[chrisallenlane/cheat](https://github.com/chrisallenlane/cheat) |![](https://img.shields.io/github/contributors-anon/chrisallenlane/cheat?label=%F0%9F%91%A5&labelColor=white) |![](https://img.shields.io/github/stars/chrisallenlane/cheat?label=%E2%AD%90&labelColor=white) |Jul 28, 2013 |
|Programming languages |[adambard/learnxinyminutes-docs](https://github.com/adambard/learnxinyminutes-docs) |![](https://img.shields.io/github/contributors-anon/adambard/learnxinyminutes-docs?label=%F0%9F%91%A5&labelColor=white)|![](https://img.shields.io/github/stars/adambard/learnxinyminutes-docs?label=%E2%AD%90&labelColor=white)|Jun 23, 2013 |
|Go |[a8m/go-lang-cheat-sheet](https://github.com/a8m/go-lang-cheat-sheet) |![](https://img.shields.io/github/contributors-anon/a8m/go-lang-cheat-sheet?label=%F0%9F%91%A5&labelColor=white) |![](https://img.shields.io/github/stars/a8m/go-lang-cheat-sheet?label=%E2%AD%90&labelColor=white) |Feb 9, 2014 |
|Perl |[pkrumnis/perl1line.txt](https://github.com/pkrumins/perl1line.txt) |![](https://img.shields.io/github/contributors-anon/pkrumins/perl1line.txt?label=%F0%9F%91%A5&labelColor=white) |![](https://img.shields.io/github/stars/pkrumins/perl1line.txt?label=%E2%AD%90&labelColor=white) |Nov 4, 2011 |
|Programming languages |[StackOverflow](https://stackoverflow.com) |[14M](https://stackexchange.com/leagues/1/alltime/stackoverflow) |N/A |Sep 15, 2008 |
| Cheat sheets | Repository | C/U* | Stars | Creation Date |
|-------------------------|-------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------|---------------|
| UNIX/Linux, programming | [cheat.sheets](https://github.com/chubin/cheat.sheets) | ![](https://img.shields.io/github/contributors-anon/chubin/cheat.sheets?label=%F0%9F%91%A5&labelColor=white) | ![](https://img.shields.io/github/stars/chubin/cheat.sheets?label=%E2%AD%90&labelColor=white) | May 1, 2017 |
| UNIX/Linux commands | [tldr-pages/tldr](https://github.com/tldr-pages/tldr) | ![](https://img.shields.io/github/contributors-anon/tldr-pages/tldr?label=%F0%9F%91%A5&labelColor=white) | ![](https://img.shields.io/github/stars/tldr-pages/tldr?label=%E2%AD%90&labelColor=white) | Dec 8, 2013 |
| UNIX/Linux commands | [cheat/cheat](https://github.com/cheat/cheat) | ![](https://img.shields.io/github/contributors-anon/cheat/cheat?label=%F0%9F%91%A5&labelColor=white) | ![](https://img.shields.io/github/stars/cheat/cheat?label=%E2%AD%90&labelColor=white) | Jul 28, 2013 |
| Programming languages | [adambard/learnxinyminutes-docs](https://github.com/adambard/learnxinyminutes-docs) | ![](https://img.shields.io/github/contributors-anon/adambard/learnxinyminutes-docs?label=%F0%9F%91%A5&labelColor=white) | ![](https://img.shields.io/github/stars/adambard/learnxinyminutes-docs?label=%E2%AD%90&labelColor=white) | Jun 23, 2013 |
| Go | [a8m/go-lang-cheat-sheet](https://github.com/a8m/go-lang-cheat-sheet) | ![](https://img.shields.io/github/contributors-anon/a8m/go-lang-cheat-sheet?label=%F0%9F%91%A5&labelColor=white) | ![](https://img.shields.io/github/stars/a8m/go-lang-cheat-sheet?label=%E2%AD%90&labelColor=white) | Feb 9, 2014 |
| Perl | [pkrumnis/perl1line.txt](https://github.com/pkrumins/perl1line.txt) | ![](https://img.shields.io/github/contributors-anon/pkrumins/perl1line.txt?label=%F0%9F%91%A5&labelColor=white) | ![](https://img.shields.io/github/stars/pkrumins/perl1line.txt?label=%E2%AD%90&labelColor=white) | Nov 4, 2011 |
| Programming languages | [StackOverflow](https://stackoverflow.com) | [14M](https://stackexchange.com/leagues/1/alltime/stackoverflow) | N/A | Sep 15, 2008 |

<sup>(*) C/U — contributors for GitHub repositories, Users for Stackoverflow</sup>

Expand Down
145 changes: 88 additions & 57 deletions bin/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
from __future__ import print_function

import sys

if sys.version_info[0] < 3:
reload(sys)
sys.setdefaultencoding('utf8')
sys.setdefaultencoding("utf8")

import sys
import logging
Expand All @@ -43,7 +44,8 @@
logging.basicConfig(
filename=CONFIG["path.log.main"],
level=logging.DEBUG,
format='%(asctime)s %(message)s')
format="%(asctime)s %(message)s",
)
# Fix Flask "exception and request logging" to `stderr`.
#
# When Flask's werkzeug detects that logging is already set, it
Expand All @@ -52,24 +54,28 @@
logging.getLogger().addHandler(stderr_handler)
#
# Alter log format to disting log lines from everything else
stderr_handler.setFormatter(logging.Formatter('%(filename)s:%(lineno)s: %(message)s'))
stderr_handler.setFormatter(logging.Formatter("%(filename)s:%(lineno)s: %(message)s"))


#
# Sometimes werkzeug starts logging before an app is imported
# (https://github.com/pallets/werkzeug/issues/1969)
# resulting in duplicating lines. In that case we need root
# stderr handler to skip lines from werkzeug.
class SkipFlaskLogger(object):
def filter(self, record):
if record.name != 'werkzeug':
if record.name != "werkzeug":
return True
if logging.getLogger('werkzeug').handlers:


if logging.getLogger("werkzeug").handlers:
stderr_handler.addFilter(SkipFlaskLogger())


app = Flask(__name__) # pylint: disable=invalid-name
app.jinja_loader = jinja2.ChoiceLoader([
app.jinja_loader,
jinja2.FileSystemLoader(CONFIG["path.internal.templates"])])
app = Flask(__name__) # pylint: disable=invalid-name
app.jinja_loader = jinja2.ChoiceLoader(
[app.jinja_loader, jinja2.FileSystemLoader(CONFIG["path.internal.templates"])]
)

LIMITS = Limits()

Expand All @@ -83,34 +89,40 @@ def filter(self, record):
"powershell",
"fetch",
"aiohttp",
"xh",
]


def _is_html_needed(user_agent):
"""
Basing on `user_agent`, return whether it needs HTML or ANSI
"""
return all([x not in user_agent for x in PLAIN_TEXT_AGENTS])


def is_result_a_script(query):
return query in [':cht.sh']
return query in [":cht.sh"]


@app.route('/files/<path:path>')
@app.route("/files/<path:path>")
def send_static(path):
"""
Return static file `path`.
Can be served by the HTTP frontend.
"""
return send_from_directory(CONFIG["path.internal.static"], path)

@app.route('/favicon.ico')

@app.route("/favicon.ico")
def send_favicon():
"""
Return static file `favicon.ico`.
Can be served by the HTTP frontend.
"""
return send_from_directory(CONFIG["path.internal.static"], 'favicon.ico')
return send_from_directory(CONFIG["path.internal.static"], "favicon.ico")


@app.route('/malformed-response.html')
@app.route("/malformed-response.html")
def send_malformed():
"""
Return static file `malformed-response.html`.
Expand All @@ -119,13 +131,15 @@ def send_malformed():
dirname, filename = os.path.split(CONFIG["path.internal.malformed"])
return send_from_directory(dirname, filename)


def log_query(ip_addr, found, topic, user_agent):
"""
Log processed query and some internal data
"""
log_entry = "%s %s %s %s\n" % (ip_addr, found, topic, user_agent)
with open(CONFIG["path.log.queries"], 'ab') as my_file:
my_file.write(log_entry.encode('utf-8'))
with open(CONFIG["path.log.queries"], "ab") as my_file:
my_file.write(log_entry.encode("utf-8"))


def get_request_ip(req):
"""
Expand All @@ -134,19 +148,20 @@ def get_request_ip(req):

if req.headers.getlist("X-Forwarded-For"):
ip_addr = req.headers.getlist("X-Forwarded-For")[0]
if ip_addr.startswith('::ffff:'):
if ip_addr.startswith("::ffff:"):
ip_addr = ip_addr[7:]
else:
ip_addr = req.remote_addr
if req.headers.getlist("X-Forwarded-For"):
ip_addr = req.headers.getlist("X-Forwarded-For")[0]
if ip_addr.startswith('::ffff:'):
if ip_addr.startswith("::ffff:"):
ip_addr = ip_addr[7:]
else:
ip_addr = req.remote_addr

return ip_addr


def get_answer_language(request):
"""
Return preferred answer language based on
Expand Down Expand Up @@ -174,26 +189,26 @@ def _parse_accept_language(accept_language):
def _find_supported_language(accepted_languages):
for lang_tuple in accepted_languages:
lang = lang_tuple[0]
if '-' in lang:
lang = lang.split('-', 1)[0]
if "-" in lang:
lang = lang.split("-", 1)[0]
return lang
return None

lang = None
hostname = request.headers['Host']
if hostname.endswith('.cheat.sh'):
hostname = request.headers["Host"]
if hostname.endswith(".cheat.sh"):
lang = hostname[:-9]

if 'lang' in request.args:
lang = request.args.get('lang')
if "lang" in request.args:
lang = request.args.get("lang")

header_accept_language = request.headers.get('Accept-Language', '')
header_accept_language = request.headers.get("Accept-Language", "")
if lang is None and header_accept_language:
lang = _find_supported_language(
_parse_accept_language(header_accept_language))
lang = _find_supported_language(_parse_accept_language(header_accept_language))

return lang


def _proxy(*args, **kwargs):
# print "method=", request.method,
# print "url=", request.url.replace('/:shell-x/', ':3000/')
Expand All @@ -202,32 +217,41 @@ def _proxy(*args, **kwargs):
# print "cookies=", request.cookies
# print "allow_redirects=", False

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

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

url += url_after
print(url)
print(request.get_data())
resp = requests.request(
method=request.method,
url=url,
headers={key: value for (key, value) in request.headers if key != 'Host'},
headers={key: value for (key, value) in request.headers if key != "Host"},
data=request.get_data(),
cookies=request.cookies,
allow_redirects=False)

excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection']
headers = [(name, value) for (name, value) in resp.raw.headers.items()
if name.lower() not in excluded_headers]
allow_redirects=False,
)

excluded_headers = [
"content-encoding",
"content-length",
"transfer-encoding",
"connection",
]
headers = [
(name, value)
for (name, value) in resp.raw.headers.items()
if name.lower() not in excluded_headers
]

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


@app.route("/", methods=['GET', 'POST'])
@app.route("/", methods=["GET", "POST"])
@app.route("/<path:topic>", methods=["GET", "POST"])
def answer(topic=None):
"""
Expand All @@ -242,16 +266,19 @@ def answer(topic=None):
request.query_string
"""

user_agent = request.headers.get('User-Agent', '').lower()
user_agent = request.headers.get("User-Agent", "").lower()
html_needed = _is_html_needed(user_agent)
options = parse_args(request.args)

if topic in ['apple-touch-icon-precomposed.png', 'apple-touch-icon.png', 'apple-touch-icon-120x120-precomposed.png'] \
or (topic is not None and any(topic.endswith('/'+x) for x in ['favicon.ico'])):
return ''
if topic in [
"apple-touch-icon-precomposed.png",
"apple-touch-icon.png",
"apple-touch-icon-120x120-precomposed.png",
] or (topic is not None and any(topic.endswith("/" + x) for x in ["favicon.ico"])):
return ""

request_id = request.cookies.get('id')
if topic is not None and topic.lstrip('/') == ':last':
request_id = request.cookies.get("id")
if topic is not None and topic.lstrip("/") == ":last":
if request_id:
topic = last_query(request_id)
else:
Expand All @@ -260,43 +287,47 @@ def answer(topic=None):
if request_id:
save_query(request_id, topic)

if request.method == 'POST':
if request.method == "POST":
process_post_request(request, html_needed)
if html_needed:
return redirect("/")
return "OK\n"

if 'topic' in request.args:
return redirect("/%s" % request.args.get('topic'))
if "topic" in request.args:
return redirect("/%s" % request.args.get("topic"))

if topic is None:
topic = ":firstpage"

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

lang = get_answer_language(request)
if lang:
options['lang'] = lang
options["lang"] = lang

ip_address = get_request_ip(request)
if '+' in topic:
if "+" in topic:
not_allowed = LIMITS.check_ip(ip_address)
if not_allowed:
return "429 %s\n" % not_allowed, 429

html_is_needed = _is_html_needed(user_agent) and not is_result_a_script(topic)
if html_is_needed:
output_format='html'
output_format = "html"
else:
output_format='ansi'
result, found = cheat_wrapper(topic, request_options=options, output_format=output_format)
if 'Please come back in several hours' in result and html_is_needed:
malformed_response = open(os.path.join(CONFIG["path.internal.malformed"])).read()
output_format = "ansi"
result, found = cheat_wrapper(
topic, request_options=options, output_format=output_format
)
if "Please come back in several hours" in result and html_is_needed:
malformed_response = open(
os.path.join(CONFIG["path.internal.malformed"])
).read()
return malformed_response

log_query(ip_address, found, topic, user_agent)
if html_is_needed:
return result
return Response(result, mimetype='text/plain')
return Response(result, mimetype="text/plain")
4 changes: 2 additions & 2 deletions bin/clean_cache.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import sys
import redis
REDIS = redis.Redis(host='localhost', port=6379, db=0)

REDIS = redis.Redis(host="localhost", port=6379, db=0)

for key in sys.argv[1:]:
REDIS.delete(key)

Loading