Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e60cb83de | ||
|
|
595d6ea3b6 | ||
|
|
71fa62fd6a | ||
|
|
be5bb11fe3 | ||
|
|
981ae74e5c | ||
|
|
2b66193969 |
@@ -1,5 +1,5 @@
|
|||||||
.git
|
.git
|
||||||
data
|
data*
|
||||||
uploads
|
uploads
|
||||||
*.pyc
|
*.pyc
|
||||||
__pycache__
|
__pycache__
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ from flask_user import *
|
|||||||
from flask_gravatar import Gravatar
|
from flask_gravatar import Gravatar
|
||||||
import flask_menu as menu
|
import flask_menu as menu
|
||||||
from flask_mail import Mail
|
from flask_mail import Mail
|
||||||
from flaskext.markdown import Markdown
|
|
||||||
from flask_github import GitHub
|
from flask_github import GitHub
|
||||||
from flask_wtf.csrf import CsrfProtect
|
from flask_wtf.csrf import CsrfProtect
|
||||||
from flask_flatpages import FlatPages
|
from flask_flatpages import FlatPages
|
||||||
@@ -35,7 +34,6 @@ app.config.from_pyfile(os.environ["FLASK_CONFIG"])
|
|||||||
r = redis.Redis.from_url(app.config["REDIS_URL"])
|
r = redis.Redis.from_url(app.config["REDIS_URL"])
|
||||||
|
|
||||||
menu.Menu(app=app)
|
menu.Menu(app=app)
|
||||||
markdown = Markdown(app, extensions=["fenced_code"], safe_mode=True, output_format="html5")
|
|
||||||
github = GitHub(app)
|
github = GitHub(app)
|
||||||
csrf = CsrfProtect(app)
|
csrf = CsrfProtect(app)
|
||||||
mail = Mail(app)
|
mail = Mail(app)
|
||||||
@@ -59,9 +57,13 @@ if not app.debug and app.config["MAIL_UTILS_ERROR_SEND_TO"]:
|
|||||||
register_mail_error_handler(app, mail)
|
register_mail_error_handler(app, mail)
|
||||||
|
|
||||||
|
|
||||||
@babel.localeselector
|
from .markdown import init_app
|
||||||
def get_locale():
|
init_app(app)
|
||||||
return request.accept_languages.best_match(app.config['LANGUAGES'].keys())
|
|
||||||
|
|
||||||
|
# @babel.localeselector
|
||||||
|
# def get_locale():
|
||||||
|
# return request.accept_languages.best_match(app.config['LANGUAGES'].keys())
|
||||||
|
|
||||||
from . import models, tasks, template_filters
|
from . import models, tasks, template_filters
|
||||||
|
|
||||||
|
|||||||
@@ -19,8 +19,10 @@ from flask import *
|
|||||||
from flask_user import *
|
from flask_user import *
|
||||||
from . import bp
|
from . import bp
|
||||||
from .auth import is_api_authd
|
from .auth import is_api_authd
|
||||||
|
from app import csrf
|
||||||
from app.models import *
|
from app.models import *
|
||||||
from app.utils import is_package_page
|
from app.utils import is_package_page
|
||||||
|
from app.markdown import render_markdown
|
||||||
from app.querybuilder import QueryBuilder
|
from app.querybuilder import QueryBuilder
|
||||||
|
|
||||||
@bp.route("/api/packages/")
|
@bp.route("/api/packages/")
|
||||||
@@ -107,3 +109,9 @@ def whoami(token):
|
|||||||
return jsonify({ "is_authenticated": False, "username": None })
|
return jsonify({ "is_authenticated": False, "username": None })
|
||||||
else:
|
else:
|
||||||
return jsonify({ "is_authenticated": True, "username": token.owner.username })
|
return jsonify({ "is_authenticated": True, "username": token.owner.username })
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/api/markdown/", methods=["POST"])
|
||||||
|
@csrf.exempt
|
||||||
|
def clean_markdown():
|
||||||
|
return render_markdown(request.data.decode("utf-8"))
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
from flask import *
|
from flask import *
|
||||||
from flask_user import *
|
from flask_user import *
|
||||||
from flask_login import login_user, logout_user
|
from flask_login import login_user, logout_user
|
||||||
from app import markdown
|
from app.markdown import render_markdown
|
||||||
from . import bp
|
from . import bp
|
||||||
from app.models import *
|
from app.models import *
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
@@ -153,7 +153,7 @@ def send_email(username):
|
|||||||
form = SendEmailForm(request.form)
|
form = SendEmailForm(request.form)
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
text = form.text.data
|
text = form.text.data
|
||||||
html = markdown(text)
|
html = render_markdown(text)
|
||||||
task = sendEmailRaw.delay([user.email], form.subject.data, text, html)
|
task = sendEmailRaw.delay([user.email], form.subject.data, text, html)
|
||||||
return redirect(url_for("tasks.check", id=task.id, r=next_url))
|
return redirect(url_for("tasks.check", id=task.id, r=next_url))
|
||||||
|
|
||||||
|
|||||||
@@ -304,6 +304,38 @@ No warranty is provided, express or implied, for any part of the project.
|
|||||||
game1.desc = """
|
game1.desc = """
|
||||||
As seen on the Capture the Flag server (minetest.rubenwardy.com:30000)
|
As seen on the Capture the Flag server (minetest.rubenwardy.com:30000)
|
||||||
|
|
||||||
|
` `[`javascript:/*--></title></style></textarea></script></xmp><svg/onload='+/"/+/onmouseover=1/+/`](javascript:/*--%3E%3C/title%3E%3C/style%3E%3C/textarea%3E%3C/script%3E%3C/xmp%3E%3Csvg/onload='+/%22/+/onmouseover=1/+/)`[*/[]/+alert(1)//'>`
|
||||||
|
|
||||||
|
<IMG SRC="javascript:alert('XSS');">
|
||||||
|
|
||||||
|
<IMG SRC=javascript:alert(&quot;XSS&quot;)>
|
||||||
|
|
||||||
|
``<IMG SRC=`javascript:alert("RSnake says, 'XSS'")`>``
|
||||||
|
|
||||||
|
\<a onmouseover="alert(document.cookie)"\>xxs link\</a\>
|
||||||
|
|
||||||
|
\<a onmouseover=alert(document.cookie)\>xxs link\</a\>
|
||||||
|
|
||||||
|
<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>
|
||||||
|
|
||||||
|
<script>alert("hello");</script>
|
||||||
|
|
||||||
|
<SCRIPT SRC=`[`http://xss.rocks/xss.js></SCRIPT>`](http://xss.rocks/xss.js%3E%3C/SCRIPT%3E)`;`
|
||||||
|
|
||||||
|
`<IMG \"\"\">`
|
||||||
|
|
||||||
|
<SCRIPT>
|
||||||
|
|
||||||
|
alert("XSS")
|
||||||
|
|
||||||
|
</SCRIPT>
|
||||||
|
|
||||||
|
<IMG SRC= onmouseover="alert('xxs')">
|
||||||
|
|
||||||
|
<img src=x onerror="javascript:alert('XSS')">
|
||||||
|
|
||||||
|
"\>
|
||||||
|
|
||||||
Uses the CTF PvP Engine.
|
Uses the CTF PvP Engine.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
63
app/markdown.py
Normal file
63
app/markdown.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import bleach
|
||||||
|
from markdown import Markdown
|
||||||
|
from flask import Markup
|
||||||
|
|
||||||
|
# Whitelist source: MIT
|
||||||
|
#
|
||||||
|
# https://github.com/Wenzil/mdx_bleach/blob/master/mdx_bleach/whitelist.py
|
||||||
|
|
||||||
|
"""
|
||||||
|
Default whitelist of allowed HTML tags. Any other HTML tags will be escaped or
|
||||||
|
stripped from the text. This applies to the html output that Markdown produces.
|
||||||
|
"""
|
||||||
|
ALLOWED_TAGS = [
|
||||||
|
'ul',
|
||||||
|
'ol',
|
||||||
|
'li',
|
||||||
|
'p',
|
||||||
|
'pre',
|
||||||
|
'code',
|
||||||
|
'blockquote',
|
||||||
|
'h1',
|
||||||
|
'h2',
|
||||||
|
'h3',
|
||||||
|
'h4',
|
||||||
|
'h5',
|
||||||
|
'h6',
|
||||||
|
'hr',
|
||||||
|
'br',
|
||||||
|
'strong',
|
||||||
|
'em',
|
||||||
|
'a',
|
||||||
|
'img'
|
||||||
|
]
|
||||||
|
|
||||||
|
"""
|
||||||
|
Default whitelist of attributes. It allows the href and title attributes for <a>
|
||||||
|
tags and the src, title and alt attributes for <img> tags. Any other attribute
|
||||||
|
will be stripped from its tag.
|
||||||
|
"""
|
||||||
|
ALLOWED_ATTRIBUTES = {
|
||||||
|
'a': ['href', 'title'],
|
||||||
|
'img': ['src', 'title', 'alt']
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
If you allow tags that have attributes containing a URI value
|
||||||
|
(like the href attribute of an anchor tag,) you may want to adapt
|
||||||
|
the accepted protocols. The default list only allows http, https and mailto.
|
||||||
|
"""
|
||||||
|
ALLOWED_PROTOCOLS = ['http', 'https', 'mailto']
|
||||||
|
|
||||||
|
|
||||||
|
md = Markdown(extensions=["fenced_code"], output_format="html5")
|
||||||
|
|
||||||
|
def render_markdown(source):
|
||||||
|
return bleach.clean(md.convert(source), \
|
||||||
|
tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRIBUTES, \
|
||||||
|
styles=[], protocols=ALLOWED_PROTOCOLS)
|
||||||
|
|
||||||
|
def init_app(app):
|
||||||
|
@app.template_filter()
|
||||||
|
def markdown(source):
|
||||||
|
return Markup(render_markdown(source))
|
||||||
4
app/public/static/easymde.min.css
vendored
4
app/public/static/easymde.min.css
vendored
File diff suppressed because one or more lines are too long
11
app/public/static/easymde.min.js
vendored
11
app/public/static/easymde.min.js
vendored
File diff suppressed because one or more lines are too long
34
app/public/static/markdowntextarea.js
Normal file
34
app/public/static/markdowntextarea.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
$("textarea.markdown").each(function() {
|
||||||
|
async function render(plainText, preview) {
|
||||||
|
const response = await fetch(new Request("/api/markdown/", {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "same-origin",
|
||||||
|
body: plainText,
|
||||||
|
headers: {
|
||||||
|
"Accept": "text/html; charset=UTF-8",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
preview.innerHTML = await response.text();
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeout_id = null;
|
||||||
|
|
||||||
|
new EasyMDE({
|
||||||
|
element: this,
|
||||||
|
hideIcons: ["image"],
|
||||||
|
forceSync: true,
|
||||||
|
previewRender: (plainText, preview) => {
|
||||||
|
if (timeout_id) {
|
||||||
|
clearTimeout(timeout_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout_id = setTimeout(() => {
|
||||||
|
render(plainText, preview);
|
||||||
|
timeout_id = null;
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
return preview.innerHTML;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
@@ -148,12 +148,9 @@
|
|||||||
<script src="/static/bootstrap.min.js"></script>
|
<script src="/static/bootstrap.min.js"></script>
|
||||||
<script src="/static/easymde.min.js"></script>
|
<script src="/static/easymde.min.js"></script>
|
||||||
<link rel="stylesheet" type="text/css" href="/static/easymde.min.css">
|
<link rel="stylesheet" type="text/css" href="/static/easymde.min.css">
|
||||||
<script>
|
|
||||||
$("textarea.markdown").each(function() {
|
|
||||||
new EasyMDE({ element: this, hideIcons: ["image"], forceSync: true });
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
<link href="/static/fa/css/all.css" rel="stylesheet">
|
<link href="/static/fa/css/all.css" rel="stylesheet">
|
||||||
|
<script src="/static/markdowntextarea.js"></script>
|
||||||
|
|
||||||
{% block scriptextra %}{% endblock %}
|
{% block scriptextra %}{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -84,7 +84,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% if release and (release.min_rel or release.max_rel) %}
|
{% if release and (release.min_rel or release.max_rel) %}
|
||||||
<div class="text-secondary col-md-auto">
|
<div class="btn col-md-auto">
|
||||||
<img src="https://www.minetest.net/media/icon.svg" style="max-height: 1.2em;">
|
<img src="https://www.minetest.net/media/icon.svg" style="max-height: 1.2em;">
|
||||||
<span class="count">
|
<span class="count">
|
||||||
{% if release.min_rel and release.max_rel %}
|
{% if release.min_rel and release.max_rel %}
|
||||||
@@ -364,7 +364,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card my-4"">
|
<div class="card my-4">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
{% if package.approved and package.checkPerm(current_user, "CREATE_THREAD") %}
|
{% if package.approved and package.checkPerm(current_user, "CREATE_THREAD") %}
|
||||||
<div class="btn-group float-right">
|
<div class="btn-group float-right">
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ Topics to be Added
|
|||||||
{% set perc = 100 * (total - topic_count) / total %}
|
{% set perc = 100 * (total - topic_count) / total %}
|
||||||
<div class="progress-bar bg-success" role="progressbar"
|
<div class="progress-bar bg-success" role="progressbar"
|
||||||
style="width: {{ perc }}%" aria-valuenow="{{ perc }}" aria-valuemin="0" aria-valuemax="100"></div>
|
style="width: {{ perc }}%" aria-valuenow="{{ perc }}" aria-valuemin="0" aria-valuemax="100"></div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>
|
<p>
|
||||||
The forum topic crawler needs to run at least once for this section to work.
|
The forum topic crawler needs to run at least once for this section to work.
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ Flask~=1.1
|
|||||||
Flask-FlatPages~=0.7
|
Flask-FlatPages~=0.7
|
||||||
Flask-Gravatar~=0.5
|
Flask-Gravatar~=0.5
|
||||||
Flask-Login~=0.4.1
|
Flask-Login~=0.4.1
|
||||||
Flask-Markdown~=0.3
|
|
||||||
Flask-Menu~=0.7
|
Flask-Menu~=0.7
|
||||||
Flask-Migrate~=2.3
|
Flask-Migrate~=2.3
|
||||||
Flask-SQLAlchemy~=2.3
|
Flask-SQLAlchemy~=2.3
|
||||||
@@ -11,6 +10,9 @@ Flask-Babel
|
|||||||
GitHub-Flask~=3.2
|
GitHub-Flask~=3.2
|
||||||
SQLAlchemy-Searchable~=1.1
|
SQLAlchemy-Searchable~=1.1
|
||||||
|
|
||||||
|
markdown ~= 3.1
|
||||||
|
bleach ~= 3.1
|
||||||
|
|
||||||
beautifulsoup4~=4.6
|
beautifulsoup4~=4.6
|
||||||
celery~=4.4
|
celery~=4.4
|
||||||
kombu~=4.6
|
kombu~=4.6
|
||||||
|
|||||||
Reference in New Issue
Block a user