Skip to content

Commit

Permalink
deploy xss fixes again
Browse files Browse the repository at this point in the history
  • Loading branch information
cycomachead committed Feb 28, 2025
1 parent 14afe1b commit 692810a
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 18 deletions.
5 changes: 4 additions & 1 deletion controllers/user.lua
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ require 'responses'
require 'passwords'
local validations = require('validation')
local assert_current_user_logged_in = validations.assert_current_user_logged_in
-- Local Snap!Cloud functions
local utils = require('lib.util')
local escape_html = utils.escape_html

UserController = {
run_query = function (self, query)
Expand Down Expand Up @@ -448,7 +451,7 @@ UserController = {
create_token(self, 'verify_user', user)

return jsonResponse({
message = 'User ' .. self.params.username ..
message = 'User ' .. escape_html(self.params.username) ..
' created.\nPlease check your email and validate your\n' ..
'account within the next 3 days.\nYou can now log in.',
title = 'Account Created',
Expand Down
20 changes: 19 additions & 1 deletion lib/util.lua
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,25 @@ local function domain_name(url)
return url:gsub('https*://', ''):gsub(':%d+$', '')
end

local function escape_html(text)
if text == nil then return end

text = tostring(text)
local map = {
["&"] = "&",
["<"] = "&lt;",
[">"] = "&gt;",
['"'] = "&quot;",
["'"] = "&#039;"
}

return (text:gsub("[&<>\'\"]", function(m)
return map[m]
end))
end

return {
capitalize = capitalize,
domain_name = domain_name
domain_name = domain_name,
escape_html = escape_html,
}
33 changes: 17 additions & 16 deletions static/js/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,21 @@ function getUrlParameter (param) {
return decodeURIComponent(results[2].replace(/\+/g, ' '));
};

function escapeHtml (text) {
if (text === null || text === undefined) { return; }

if (text.toString) { text = text.toString(); }
// Based on an answer by Kip @ StackOverflow
let map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
return text.replace(/[&<>"']/g, (m) => map[m]);
}

// Error handling

function genericError (errorString, title) {
Expand Down Expand Up @@ -61,20 +76,6 @@ function doneLoading (selector) {
}
};

// Other goodies

function escapeHtml (text) {
// Based on an answer by Kip @ StackOverflow
var map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
return text ? text.replace(/[&<>"']/g, function (m) { return map[m]; }) : ''
};

function enableEnterSubmit () {
// Submits "forms" when enter is pressed on any of their inputs
document.querySelectorAll('.pure-form input').forEach(
Expand Down Expand Up @@ -170,7 +171,7 @@ Cloud.prototype.apiRequest = function (method, path, onSuccess, body) {
if (response && response.title) {
alert(
localizer.localize(response.message),
{ title: localizer.localize(response.title) },
{ title: escapeHtml(localizer.localize(response.title)) },
function () { onSuccess.call(this, response) }
);
} else {
Expand All @@ -180,7 +181,7 @@ Cloud.prototype.apiRequest = function (method, path, onSuccess, body) {
errorMessage => {
alert(
localizer.localize(errorMessage),
{ title: localizer.localize('Error') },
{ title: escapeHtml(localizer.localize('Error')) },
Cloud.redirect
)
},
Expand Down
18 changes: 18 additions & 0 deletions views/js/dialog.etlua
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
<script>
function escapeHtml (text) {
if (text === null || text === undefined) { return; }

if (text.toString) { text = text.toString(); }
// Based on an answer by Kip @ StackOverflow
let map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
return text.replace(/[&<>"']/g, (m) => map[m]);
}

function dialog (title, body, onSuccess, onCancel, onOpen) {
// I reuse CustomAlert's dialogs
var dialogBox = onCancel
? document.querySelector('#customconfirm')
: document.querySelector('#customalert'),
bodyDiv = dialogBox.querySelector('.body');

title = escapeHtml(title);

if (typeof body == 'string') {
bodyDiv.innerHTML = body.replaceAll('\n', '<br>');
} else {
Expand Down Expand Up @@ -116,6 +133,7 @@ document.onkeyup = function (event) {
};

// custom-alert.min.js
// TODO: de-minify and add HTML escaping.
function customAlert(a){function c(a,b){for(var c in b)a[c]=b[c];return a}function d(){var a={button:"OK",title:"Alert"};this.render=function(b,d){this.options=d?c(a,d):a;var e=document.querySelector("#customalert");e.querySelector(".header").innerHTML=this.options.title,e.querySelector(".body").innerHTML=b,e.querySelector(".button-done").innerHTML=this.options.button,document.querySelector("html").style.overflow="hidden",document.querySelector("#customalert-overlay").style.display="block",e.style.display="block"},this.done=function(){document.querySelector("#customalert").style.display=null,document.querySelector("#customalert-overlay").style.display=null,document.querySelector("html").style.overflow="auto","function"==typeof this.callback&&this.callback.call()}}function e(){var a={done:{text:"<%= locale.get('ok') %>",bold:!1,default:!0},cancel:{text:"<%= locale.get('cancel') %>",bold:!1,default:!1},title:"<%= locale.get('confirm') %>"},b=function(a,b){return a[b].bold?"<strong>"+a[b].text+"</strong>":a[b].text};this.callback=function(a){},this.render=function(d,e){this.options=a,e&&(e.done&&"string"==typeof e.done&&(e.done={text:e.done}),e.cancel&&"string"==typeof e.cancel&&(e.cancel={text:e.cancel}),1==e.cancel.default?e.done.default=!1:e.done.default=!0,e.cancel&&(this.options.cancel=c(a.cancel,e.cancel)),e.done&&(this.options.done=c(a.done,e.done)),e.title&&(this.options.title=e.title));var f=document.querySelector("#customconfirm");f.querySelector(".header").innerHTML=this.options.title,f.querySelector(".body").innerHTML=d,f.querySelector(".button-cancel").innerHTML=b(this.options,"cancel"),f.querySelector(".button-done").innerHTML=b(this.options,"done"),document.querySelector("html").style.overflow="hidden",document.querySelector("#customconfirm-overlay").style.display="block",f.style.display="block"},this.done=function(){if(this.end(),this.callbackSuccess)return this.callbackSuccess();this.callback(!0)},this.cancel=function(){if(this.end(),this.callbackCancel)return this.callbackCancel();this.callback(!1)},this.end=function(){document.querySelector("#customconfirm").style.display="none",document.querySelector("#customconfirm-overlay").style.display="none",document.querySelector("html").style.overflow="auto"}}var f,g,b=function(a){return this.el=document.createElement(a),this.attr=function(a){var b=this;for(var c in a)b.el.setAttribute(c,a[c]);return b},this.parent=function(a,b){return b=b?document.querySelector(b):document,a=b.querySelector(a),a.appendChild(this.el),this},this.html=function(a){return this.el.innerHTML=a,this},this};if(null==document.getElementById("customalert")&&(b("div").attr({id:"customalert-overlay",class:"customalert-overlay"}).parent("body"),b("div").attr({id:"customalert",class:"customalert customalert-alert"}).parent("body"),b("div").attr({class:"header"}).parent("#customalert"),b("div").attr({class:"body"}).parent("#customalert"),b("div").attr({class:"footer"}).parent("#customalert"),b("button").attr({class:"pure-button btn btn-primary custom-alert button-done",onclick:"window.customalert.done()"}).parent(".footer","#customalert"),window.addEventListener("keydown",function(a){var b=document.getElementById("customconfirm"),c=document.getElementById("customalert");if(!(null==c&&null==b||null!=b&&"block"!=b.style.display||null!=c&&"block"!=c.style.display)){a.preventDefault(),a.stopPropagation();var d=a.keyCode?a.keyCode:a.which;13==d?"block"==b.style.display?window.customconfirm.options.cancel.default?window.customconfirm.cancel():window.customconfirm.done():"block"==c.style.display&&window.customalert.done():27==d&&"block"==b.style.display&&window.customconfirm.cancel()}},!1),f=window.Alert=function(a,b,c){window.customalert=new d,"function"==typeof b?(window.customalert.callback=b,b=null):window.customalert.callback=c||null,window.customalert.render(a,b)}),null==document.getElementById("customconfirm")&&(b("div").attr({id:"customconfirm-overlay",class:"customalert-overlay"}).parent("body"),b("div").attr({id:"customconfirm",class:"customalert customalert-confirm"}).parent("body"),b("div").attr({class:"header"}).parent("#customconfirm"),b("div").attr({class:"body"}).parent("#customconfirm"),b("div").attr({class:"footer"}).parent("#customconfirm"),b("button").attr({class:"pure-button btn btn-danger button-cancel",onclick:"window.customconfirm.cancel()"}).parent(".footer","#customconfirm"),b("button").attr({class:"pure-button btn btn-success custom-alert button-done",onclick:"window.customconfirm.done()"}).parent(".footer","#customconfirm"),g=window.Confirm=function(a,b,c){window.customconfirm=new e,"object"==typeof b?(window.customconfirm.callbackSuccess=b.success,window.customconfirm.callbackCancel=b.cancel):window.customconfirm.callback=b,window.customconfirm.render(a,c)}),!1===a)return{alert:f,confirm:g};window.alert=f;
// old_confirm had very strange behavior...
window.confirm = function(text, onSuccess, params) { g(text, ok => { if (ok) { onSuccess.call(params); } }); }; }
Expand Down

0 comments on commit 692810a

Please sign in to comment.