diff --git a/kallithea/controllers/admin/my_account.py b/kallithea/controllers/admin/my_account.py --- a/kallithea/controllers/admin/my_account.py +++ b/kallithea/controllers/admin/my_account.py @@ -41,7 +41,6 @@ from kallithea.lib import auth_modules from kallithea.lib.auth import LoginRequired, NotAnonymous, AuthUser from kallithea.lib.base import BaseController, render from kallithea.lib.utils2 import generate_api_key, safe_int -from kallithea.lib.compat import json from kallithea.model.db import Repository, UserEmailMap, User, UserFollowing from kallithea.model.forms import UserForm, PasswordChangeForm from kallithea.model.user import UserModel @@ -84,10 +83,8 @@ class MyAccountController(BaseController .filter(Repository.owner_id == request.authuser.user_id).all() - repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list, + return RepoModel().get_repos_as_dict(repos_list=repos_list, admin=admin) - #json used to render the grid - return json.dumps(repos_data) def my_account(self): c.active = 'profile' @@ -176,7 +173,7 @@ class MyAccountController(BaseController c.active = 'repos' self.__load_data() - #json used to render the grid + #data used to render the grid c.data = self._load_my_repos_data() return render('admin/my_account/my_account.html') @@ -184,7 +181,7 @@ class MyAccountController(BaseController c.active = 'watched' self.__load_data() - #json used to render the grid + #data used to render the grid c.data = self._load_my_repos_data(watched=True) return render('admin/my_account/my_account.html') diff --git a/kallithea/controllers/admin/repo_groups.py b/kallithea/controllers/admin/repo_groups.py --- a/kallithea/controllers/admin/repo_groups.py +++ b/kallithea/controllers/admin/repo_groups.py @@ -39,7 +39,6 @@ from webob.exc import HTTPFound, HTTPFor import kallithea from kallithea.config.routing import url from kallithea.lib import helpers as h -from kallithea.lib.compat import json from kallithea.lib.auth import LoginRequired, \ HasRepoGroupPermissionLevelDecorator, HasRepoGroupPermissionLevel, \ HasPermissionAny @@ -141,13 +140,13 @@ class RepoGroupsController(BaseControlle repo_count) }) - c.data = json.dumps({ + c.data = { "totalRecords": total_records, "startIndex": 0, "sort": None, "dir": "asc", "records": repo_groups_data - }) + } return render('admin/repo_groups/repo_groups.html') @@ -304,8 +303,8 @@ class RepoGroupsController(BaseControlle repos_list = Repository.query(sorted=True).filter_by(group=c.group).all() repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list, admin=False, short_name=True) - #json used to render the grid - c.data = json.dumps(repos_data) + # data used to render the grid + c.data = repos_data return render('admin/repo_groups/repo_group_show.html') diff --git a/kallithea/controllers/admin/repos.py b/kallithea/controllers/admin/repos.py --- a/kallithea/controllers/admin/repos.py +++ b/kallithea/controllers/admin/repos.py @@ -47,7 +47,6 @@ from kallithea.model.db import User, Rep from kallithea.model.forms import RepoForm, RepoFieldForm, RepoPermsForm from kallithea.model.scm import ScmModel, AvailableRepoGroupChoices, RepoList from kallithea.model.repo import RepoModel -from kallithea.lib.compat import json from kallithea.lib.exceptions import AttachedForksError from kallithea.lib.utils2 import safe_int @@ -105,8 +104,8 @@ class ReposController(BaseRepoController repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list, admin=True, super_user_actions=True) - #json used to render the grid - c.data = json.dumps(repos_data) + #data used to render the grid + c.data = repos_data return render('admin/repos/repos.html') diff --git a/kallithea/controllers/admin/user_groups.py b/kallithea/controllers/admin/user_groups.py --- a/kallithea/controllers/admin/user_groups.py +++ b/kallithea/controllers/admin/user_groups.py @@ -56,7 +56,6 @@ from kallithea.model.forms import UserGr CustomDefaultPermissionsForm from kallithea.model.meta import Session from kallithea.lib.utils import action_logger -from kallithea.lib.compat import json log = logging.getLogger(__name__) @@ -119,13 +118,13 @@ class UserGroupsController(BaseControlle "action": user_group_actions(user_gr.users_group_id, user_gr.users_group_name) }) - c.data = json.dumps({ + c.data = { "totalRecords": total_records, "startIndex": 0, "sort": None, "dir": "asc", "records": user_groups_data - }) + } return render('admin/user_groups/user_groups.html') diff --git a/kallithea/controllers/admin/users.py b/kallithea/controllers/admin/users.py --- a/kallithea/controllers/admin/users.py +++ b/kallithea/controllers/admin/users.py @@ -51,7 +51,6 @@ from kallithea.model.forms import UserFo from kallithea.model.user import UserModel from kallithea.model.meta import Session from kallithea.lib.utils import action_logger -from kallithea.lib.compat import json from kallithea.lib.utils2 import datetime_to_time, safe_int, generate_api_key log = logging.getLogger(__name__) @@ -103,13 +102,13 @@ class UsersController(BaseController): "action": user_actions(user.user_id, user.username), }) - c.data = json.dumps({ + c.data = { "totalRecords": total_records, "startIndex": 0, "sort": None, "dir": "asc", "records": users_data - }) + } return render('admin/users/users.html') diff --git a/kallithea/controllers/changelog.py b/kallithea/controllers/changelog.py --- a/kallithea/controllers/changelog.py +++ b/kallithea/controllers/changelog.py @@ -36,7 +36,6 @@ import kallithea.lib.helpers as h from kallithea.config.routing import url from kallithea.lib.auth import LoginRequired, HasRepoPermissionLevelDecorator from kallithea.lib.base import BaseRepoController, render -from kallithea.lib.compat import json from kallithea.lib.graphmod import graph_data from kallithea.lib.page import RepoPage from kallithea.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \ @@ -171,7 +170,7 @@ class ChangelogController(BaseRepoContro revs = [] if not f_path: revs = [x.revision for x in c.pagination] - c.jsdata = json.dumps(graph_data(c.db_repo_scm_instance, revs)) + c.jsdata = graph_data(c.db_repo_scm_instance, revs) c.revision = revision # requested revision ref c.first_revision = c.pagination[0] # pagination is never empty here! diff --git a/kallithea/controllers/changeset.py b/kallithea/controllers/changeset.py --- a/kallithea/controllers/changeset.py +++ b/kallithea/controllers/changeset.py @@ -36,7 +36,6 @@ from webob.exc import HTTPFound, HTTPFor from kallithea.lib.vcs.exceptions import RepositoryError, \ ChangesetDoesNotExistError, EmptyRepositoryError -from kallithea.lib.compat import json import kallithea.lib.helpers as h from kallithea.lib.auth import LoginRequired, HasRepoPermissionLevelDecorator, \ NotAnonymous @@ -333,7 +332,7 @@ class ChangesetController(BaseRepoContro c.cs_ranges_org = None c.cs_comments = {} revs = [ctx.revision for ctx in reversed(c.cs_ranges)] - c.jsdata = json.dumps(graph_data(c.db_repo_scm_instance, revs)) + c.jsdata = graph_data(c.db_repo_scm_instance, revs) return render('changeset/changeset_range.html') @LoginRequired() diff --git a/kallithea/controllers/compare.py b/kallithea/controllers/compare.py --- a/kallithea/controllers/compare.py +++ b/kallithea/controllers/compare.py @@ -45,7 +45,6 @@ from kallithea.model.db import Repositor from kallithea.lib.diffs import LimitedDiffContainer from kallithea.controllers.changeset import _ignorews_url, _context_url from kallithea.lib.graphmod import graph_data -from kallithea.lib.compat import json, OrderedDict log = logging.getLogger(__name__) @@ -227,7 +226,7 @@ class CompareController(BaseRepoControll c.statuses = c.cs_repo.statuses(raw_ids) revs = [ctx.revision for ctx in reversed(c.cs_ranges)] - c.jsdata = json.dumps(graph_data(c.cs_repo.scm_instance, revs)) + c.jsdata = graph_data(c.cs_repo.scm_instance, revs) if partial: return render('compare/compare_cs.html') diff --git a/kallithea/controllers/home.py b/kallithea/controllers/home.py --- a/kallithea/controllers/home.py +++ b/kallithea/controllers/home.py @@ -34,7 +34,6 @@ from webob.exc import HTTPBadRequest from sqlalchemy.sql.expression import func from kallithea.lib.utils import conditional_cache -from kallithea.lib.compat import json from kallithea.lib.auth import LoginRequired, HasRepoPermissionLevelDecorator from kallithea.lib.base import BaseController, render, jsonify from kallithea.model.db import Repository, RepoGroup @@ -61,8 +60,8 @@ class HomeController(BaseController): repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list, admin=False, short_name=True) - #json used to render the grid - c.data = json.dumps(repos_data) + #data used to render the grid + c.data = repos_data return render('/index.html') diff --git a/kallithea/controllers/journal.py b/kallithea/controllers/journal.py --- a/kallithea/controllers/journal.py +++ b/kallithea/controllers/journal.py @@ -48,7 +48,6 @@ from kallithea.model.repo import RepoMod import kallithea.lib.helpers as h from kallithea.lib.auth import LoginRequired, NotAnonymous from kallithea.lib.base import BaseController, render -from kallithea.lib.compat import json from kallithea.lib.page import Page from kallithea.lib.utils2 import safe_int, AttributeDict @@ -218,8 +217,8 @@ class JournalController(BaseController): repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list, admin=True) - #json used to render the grid - c.data = json.dumps(repos_data) + #data used to render the grid + c.data = repos_data return render('journal/journal.html') diff --git a/kallithea/controllers/pullrequests.py b/kallithea/controllers/pullrequests.py --- a/kallithea/controllers/pullrequests.py +++ b/kallithea/controllers/pullrequests.py @@ -40,7 +40,6 @@ from kallithea.lib import diffs from kallithea.lib.auth import LoginRequired, HasRepoPermissionLevelDecorator, \ NotAnonymous from kallithea.lib.base import BaseRepoController, render, jsonify -from kallithea.lib.compat import json, OrderedDict from kallithea.lib.diffs import LimitedDiffContainer from kallithea.lib.page import Page from kallithea.lib.utils import action_logger @@ -619,7 +618,7 @@ class PullrequestsController(BaseRepoCon 'error') c.cs_ranges_org = None # not stored and not important and moving target - could be calculated ... revs = [ctx.revision for ctx in reversed(c.cs_ranges)] - c.jsdata = json.dumps(graph_data(org_scm_instance, revs)) + c.jsdata = graph_data(org_scm_instance, revs) c.is_range = False try: @@ -701,7 +700,7 @@ class PullrequestsController(BaseRepoCon c.avail_revs = avail_revs c.avail_cs = [org_scm_instance.get_changeset(r) for r in avail_show] - c.avail_jsdata = json.dumps(graph_data(org_scm_instance, avail_show)) + c.avail_jsdata = graph_data(org_scm_instance, avail_show) raw_ids = [x.raw_id for x in c.cs_ranges] c.cs_comments = c.cs_repo.get_comments(raw_ids) diff --git a/kallithea/controllers/summary.py b/kallithea/controllers/summary.py --- a/kallithea/controllers/summary.py +++ b/kallithea/controllers/summary.py @@ -146,12 +146,12 @@ class SummaryController(BaseRepoControll "desc": LANGUAGES_EXTENSIONS_MAP.get(x)}) for x, y in lang_stats_d.items()) - c.trending_languages = json.dumps( + c.trending_languages = ( sorted(lang_stats, reverse=True, key=lambda k: k[1])[:10] ) else: c.no_data = True - c.trending_languages = json.dumps([]) + c.trending_languages = [] c.enable_downloads = c.db_repo.enable_downloads c.readme_data, c.readme_file = \ @@ -202,7 +202,7 @@ class SummaryController(BaseRepoControll "desc": LANGUAGES_EXTENSIONS_MAP.get(x)}) for x, y in lang_stats_d.items()) - c.trending_languages = json.dumps( + c.trending_languages = ( sorted(lang_stats, reverse=True, key=lambda k: k[1])[:10] ) last_rev = stats.stat_on_revision + 1 @@ -214,9 +214,9 @@ class SummaryController(BaseRepoControll c.stats_percentage = '%.2f' % ((float((last_rev)) / c.repo_last_rev) * 100) else: - c.commit_data = json.dumps({}) - c.overview_data = json.dumps([[ts_min_y, 0], [ts_max_y, 10]]) - c.trending_languages = json.dumps({}) + c.commit_data = {} + c.overview_data = ([[ts_min_y, 0], [ts_max_y, 10]]) + c.trending_languages = {} c.no_data = True recurse_limit = 500 # don't recurse more than 500 times when parsing diff --git a/kallithea/lib/helpers.py b/kallithea/lib/helpers.py --- a/kallithea/lib/helpers.py +++ b/kallithea/lib/helpers.py @@ -18,6 +18,7 @@ Consists of functions to typically be us available to Controllers. This module is available to both as 'h'. """ import hashlib +import json import StringIO import logging import re @@ -85,6 +86,50 @@ def html_escape(s): .replace("'", "'") ) +def js(value): + """Convert Python value to the corresponding JavaScript representation. + + This is necessary to safely insert arbitrary values into HTML " + is forbidden), the function ensures that the result never contains + '&', '<' and '>', thus making it safe in both those contexts (but + not in attributes). + """ + return literal( + ('(' + json.dumps(value) + ')') + # In JSON, the following can only appear in string literals. + .replace('&', r'\x26') + .replace('<', r'\x3c') + .replace('>', r'\x3e') + ) + +def jshtml(val): + """HTML escapes a string value, then converts the resulting string + to its corresponding JavaScript representation (see `js`). + + This is used when a plain-text string (possibly containing special + HTML characters) will be used by a script in an HTML context (e.g. + element.innerHTML or jQuery's 'html' method). + + If in doubt, err on the side of using `jshtml` over `js`, since it's + better to escape too much than too little. + """ + return js(escape(val)) + + def shorter(s, size=20, firstline=False, postfix='...'): """Truncate s to size, including the postfix string if truncating. If firstline, truncate at newline. diff --git a/kallithea/model/repo.py b/kallithea/model/repo.py --- a/kallithea/model/repo.py +++ b/kallithea/model/repo.py @@ -35,7 +35,6 @@ from sqlalchemy.orm import subqueryload from kallithea.lib.utils import make_ui from kallithea.lib.vcs.backends import get_backend -from kallithea.lib.compat import json from kallithea.lib.utils2 import LazyProperty, safe_str, safe_unicode, \ remove_prefix, obfuscate_url_pw, get_current_authuser from kallithea.lib.caching_query import FromCache @@ -127,7 +126,7 @@ class RepoModel(object): .filter(User.active == True) \ .order_by(User.name, User.lastname) \ .all() - return json.dumps([ + return [ { 'id': u.user_id, 'fname': h.escape(u.name), @@ -136,7 +135,6 @@ class RepoModel(object): 'gravatar_lnk': h.gravatar_url(u.email, size=28, default='default'), 'gravatar_size': 14, } for u in users] - ) def get_user_groups_js(self): user_groups = UserGroup.query() \ @@ -145,13 +143,12 @@ class RepoModel(object): .options(subqueryload(UserGroup.members)) \ .all() user_groups = UserGroupList(user_groups, perm_level='read') - return json.dumps([ + return [ { 'id': gr.users_group_id, 'grname': gr.users_group_name, 'grmembers': len(gr.members), } for gr in user_groups] - ) @classmethod def _render_datatable(cls, tmpl, *args, **kwargs): diff --git a/kallithea/templates/admin/admin.html b/kallithea/templates/admin/admin.html --- a/kallithea/templates/admin/admin.html +++ b/kallithea/templates/admin/admin.html @@ -46,7 +46,7 @@ $(document).ready(function() { $('#filter_form').submit(function (e) { e.preventDefault(); var val = $('#j_filter').val(); - window.location = "${url.current(filter='__FILTER__')}".replace('__FILTER__',val); + window.location = ${h.js(url.current(filter='__FILTER__'))}.replace('__FILTER__',val); }); fix_j_filter_width($('#j_filter').val().length); }); diff --git a/kallithea/templates/admin/gists/edit.html b/kallithea/templates/admin/gists/edit.html --- a/kallithea/templates/admin/gists/edit.html +++ b/kallithea/templates/admin/gists/edit.html @@ -81,10 +81,10 @@ ## dynamic edit box. %def> diff --git a/kallithea/templates/admin/notifications/show_notification.html b/kallithea/templates/admin/notifications/show_notification.html --- a/kallithea/templates/admin/notifications/show_notification.html +++ b/kallithea/templates/admin/notifications/show_notification.html @@ -43,8 +43,8 @@ diff --git a/kallithea/templates/admin/repo_groups/repo_groups.html b/kallithea/templates/admin/repo_groups/repo_groups.html --- a/kallithea/templates/admin/repo_groups/repo_groups.html +++ b/kallithea/templates/admin/repo_groups/repo_groups.html @@ -31,16 +31,16 @@ diff --git a/kallithea/templates/admin/repos/repo_edit_settings.html b/kallithea/templates/admin/repos/repo_edit_settings.html --- a/kallithea/templates/admin/repos/repo_edit_settings.html +++ b/kallithea/templates/admin/repos/repo_edit_settings.html @@ -125,7 +125,7 @@ ${h.form(url('update_repo', repo_name=c. }); // autocomplete - var _USERS_AC_DATA = ${c.users_array|n}; + var _USERS_AC_DATA = ${h.js(c.users_array)}; SimpleUserAutoComplete($('#owner'), $('#owner_container'), _USERS_AC_DATA); }); diff --git a/kallithea/templates/admin/repos/repos.html b/kallithea/templates/admin/repos/repos.html --- a/kallithea/templates/admin/repos/repos.html +++ b/kallithea/templates/admin/repos/repos.html @@ -30,18 +30,18 @@ diff --git a/kallithea/templates/admin/user_groups/user_group_edit_perms.html b/kallithea/templates/admin/user_groups/user_group_edit_perms.html --- a/kallithea/templates/admin/user_groups/user_group_edit_perms.html +++ b/kallithea/templates/admin/user_groups/user_group_edit_perms.html @@ -75,7 +75,7 @@ ${h.form(url('edit_user_group_perms_upda %endfor <% - _tmpl = h.literal("""'\ + _tmpl = """\