Skip to content
Merged
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
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)

11 changes: 6 additions & 5 deletions bin/srv.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from gevent.monkey import patch_all
from gevent.pywsgi import WSGIServer

patch_all()

import os
Expand All @@ -13,16 +14,16 @@
from app import app, CONFIG


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

if 'CHEATSH_PORT' in os.environ:
port = int(os.environ.get('CHEATSH_PORT'))
if "CHEATSH_PORT" in os.environ:
port = int(os.environ.get("CHEATSH_PORT"))
else:
port = CONFIG['server.port']
port = CONFIG["server.port"]

srv = WSGIServer((CONFIG['server.bind'], port), app)
srv = WSGIServer((CONFIG["server.bind"], port), app)
print("Starting gevent server on {}:{}".format(srv.address[0], srv.address[1]))
srv.serve_forever()
2 changes: 1 addition & 1 deletion doc/README-ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ let g:syntastic_shell_checkers = ['shellcheck']
scrooloose / syntastic – 構文チェックプラグイン
cheat.sh-vim – Vimのサポート

Syntasticは警告とエラー(code analysysツールで見つかった: jshint 、 jshint 、 pylint 、 shellcheckt etc.), and cheat.sh-vim`を表示すると、エディタに書き込まれたプログラミング言語のクエリに関するエラーと警告と回答の説明が表示されます。
Syntasticは警告とエラー(code analysisツールで見つかった: jshint 、 jshint 、 pylint 、 shellcheckt etc.), and cheat.sh-vim`を表示すると、エディタに書き込まれたプログラミング言語のクエリに関するエラーと警告と回答の説明が表示されます。

cheat.sh Vimプラグインの最も重要な機能が表示されているデモをご覧ください(5分):

Expand Down
3 changes: 2 additions & 1 deletion lib/adapter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
__all__ = [
basename(f)[:-3]
for f in glob.glob(join(dirname(__file__), "*.py"))
if isfile(f) and not f.endswith('__init__.py')]
if isfile(f) and not f.endswith("__init__.py")
]

from .adapter import all_adapters
from . import *
Loading
Loading