Require user permission to run channel js
This commit is contained in:
parent
3661ab1fd9
commit
e87ddb473b
|
@ -1,6 +1,7 @@
|
|||
var ChannelModule = require("./module");
|
||||
var Config = require("../config");
|
||||
var Utilities = require("../utilities");
|
||||
var url = require("url");
|
||||
|
||||
function OptionsModule(channel) {
|
||||
ChannelModule.apply(this, arguments);
|
||||
|
@ -143,11 +144,48 @@ OptionsModule.prototype.handleSetOptions = function (user, data) {
|
|||
}
|
||||
|
||||
if ("externalcss" in data && user.account.effectiveRank >= 3) {
|
||||
this.opts.externalcss = (""+data.externalcss).substring(0, 255);
|
||||
var link = (""+data.externalcss).substring(0, 255);
|
||||
try {
|
||||
var data = url.parse(link);
|
||||
if (!data.protocol || !data.protocol.match(/^(https?|ftp):$/)) {
|
||||
throw "Unacceptable protocol " + data.protocol;
|
||||
} else if (!data.host) {
|
||||
throw "URL is missing host";
|
||||
} else {
|
||||
link = data.href;
|
||||
}
|
||||
} catch (e) {
|
||||
user.socket.emit("errorMsg", {
|
||||
msg: "Invalid URL for external CSS: " + e,
|
||||
alert: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.opts.externalcss = link;
|
||||
}
|
||||
|
||||
if ("externaljs" in data && user.account.effectiveRank >= 3) {
|
||||
this.opts.externaljs = (""+data.externaljs).substring(0, 255);
|
||||
var link = (""+data.externaljs).substring(0, 255);
|
||||
|
||||
try {
|
||||
var data = url.parse(link);
|
||||
if (!data.protocol || !data.protocol.match(/^(https?|ftp)$/)) {
|
||||
throw "Unacceptable protocol " + data.protocol;
|
||||
} else if (!data.host) {
|
||||
throw "URL is missing host";
|
||||
} else {
|
||||
link = data.href;
|
||||
}
|
||||
} catch (e) {
|
||||
user.socket.emit("errorMsg", {
|
||||
msg: "Invalid URL for external JS: " + e,
|
||||
alert: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.opts.externaljs = link;
|
||||
}
|
||||
|
||||
if ("chat_antiflood" in data) {
|
||||
|
|
|
@ -270,16 +270,16 @@ PlaylistModule.prototype.sendChangeMedia = function (users) {
|
|||
if (users === this.channel.users) {
|
||||
this.channel.broadcastAll("setCurrent", uid);
|
||||
this.channel.broadcastAll("changeMedia", update);
|
||||
|
||||
var m = this.current.media;
|
||||
this.channel.logger.log("[playlist] Now playing: " + m.title +
|
||||
" (" + m.type + ":" + m.id + ")");
|
||||
} else {
|
||||
users.forEach(function (u) {
|
||||
u.socket.emit("setCurrent", uid);
|
||||
u.socket.emit("changeMedia", update);
|
||||
});
|
||||
}
|
||||
|
||||
var m = this.current.media;
|
||||
this.channel.logger.log("[playlist] Now playing: " + m.title +
|
||||
" (" + m.type + ":" + m.id + ")");
|
||||
};
|
||||
|
||||
PlaylistModule.prototype.sendMediaUpdate = function (users) {
|
||||
|
|
|
@ -158,6 +158,7 @@ html(lang="en")
|
|||
li: a(href="#us-general", data-toggle="tab") General
|
||||
li: a(href="#us-playback", data-toggle="tab") Playback
|
||||
li: a(href="#us-chat", data-toggle="tab") Chat
|
||||
li: a(href="#us-scriptcontrol", data-toggle="tab") Script Access
|
||||
li: a(href="#us-mod", data-toggle="tab", style="") Moderator
|
||||
.modal-body
|
||||
.tab-content
|
||||
|
@ -165,6 +166,7 @@ html(lang="en")
|
|||
mixin us-general()
|
||||
mixin us-playback()
|
||||
mixin us-chat()
|
||||
mixin us-scripts()
|
||||
mixin us-mod()
|
||||
.modal-footer
|
||||
button.btn.btn-primary(type="button", data-dismiss="modal", onclick="javascript:saveUserOptions()") Save
|
||||
|
|
|
@ -52,6 +52,17 @@ mixin us-general
|
|||
p#us-conninfo.text-info <strong>Connection Information: </strong>
|
||||
.clear
|
||||
|
||||
mixin us-scripts
|
||||
#us-scriptcontrol.tab-pane
|
||||
h4 Script Access
|
||||
table.table
|
||||
thead
|
||||
tr
|
||||
th Channel
|
||||
th Type
|
||||
th Preference
|
||||
th Clear
|
||||
|
||||
mixin us-playback
|
||||
#us-playback.tab-pane
|
||||
h4 Playback Preferences
|
||||
|
|
|
@ -562,3 +562,15 @@ body.chatOnly .pm-panel, body.chatOnly .pm-panel-placeholder {
|
|||
.chat-shadow {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
#chanjs-allow-prompt {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#chanjs-allow-prompt-buttons {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#chanjs-allow-prompt-buttons button:first-child {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
|
|
@ -250,6 +250,7 @@ Callbacks = {
|
|||
document.title = opts.pagetitle;
|
||||
PAGETITLE = opts.pagetitle;
|
||||
$("#chanexternalcss").remove();
|
||||
|
||||
if(opts.externalcss.trim() != "" && !USEROPTS.ignore_channelcss) {
|
||||
$("<link/>")
|
||||
.attr("rel", "stylesheet")
|
||||
|
@ -257,10 +258,14 @@ Callbacks = {
|
|||
.attr("id", "chanexternalcss")
|
||||
.appendTo($("head"));
|
||||
}
|
||||
if(opts.externaljs.trim() != "" && !USEROPTS.ignore_channeljs) {
|
||||
if(opts.externaljs != CHANNEL.opts.externaljs) {
|
||||
$.getScript(opts.externaljs);
|
||||
}
|
||||
|
||||
if(opts.externaljs.trim() != "" && !USEROPTS.ignore_channeljs &&
|
||||
opts.externaljs !== CHANNEL.opts.externaljs) {
|
||||
checkScriptAccess(opts.externaljs, "external", function (pref) {
|
||||
if (pref === "ALLOW") {
|
||||
$.getScript(opts.externaljs);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
CHANNEL.opts = opts;
|
||||
|
@ -294,10 +299,26 @@ Callbacks = {
|
|||
$("#jstext").val(data.js);
|
||||
|
||||
if(data.js && !USEROPTS.ignore_channeljs) {
|
||||
$("<script/>").attr("type", "text/javascript")
|
||||
.attr("id", "chanjs")
|
||||
.text(data.js)
|
||||
.appendTo($("body"));
|
||||
var src = data.js
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/\n/g, "<br>")
|
||||
.replace(/\t/g, " ")
|
||||
.replace(/ /g, " ");
|
||||
src = encodeURIComponent(src);
|
||||
|
||||
var viewsource = "data:text/html, <body style='font: 9pt monospace;" +
|
||||
"max-width:60rem;margin:0 auto;padding:4rem;'>" +
|
||||
src + "</body>";
|
||||
checkScriptAccess(viewsource, "embedded", function (pref) {
|
||||
if (pref === "ALLOW") {
|
||||
$("<script/>").attr("type", "text/javascript")
|
||||
.attr("id", "chanjs")
|
||||
.text(data.js)
|
||||
.appendTo($("body"));
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -76,10 +76,15 @@ var FILTER_TO = 0;
|
|||
var NO_STORAGE = typeof localStorage == "undefined" || localStorage === null;
|
||||
|
||||
function getOpt(k) {
|
||||
return NO_STORAGE ? readCookie(k) : localStorage.getItem(k);
|
||||
var v = NO_STORAGE ? readCookie(k) : localStorage.getItem(k);
|
||||
try {
|
||||
v = JSON.parse(v);
|
||||
} catch (e) { }
|
||||
return v;
|
||||
}
|
||||
|
||||
function setOpt(k, v) {
|
||||
v = JSON.stringify(v);
|
||||
NO_STORAGE ? createCookie(k, v, 1000) : localStorage.setItem(k, v);
|
||||
}
|
||||
|
||||
|
@ -91,9 +96,9 @@ function getOrDefault(k, def) {
|
|||
return true;
|
||||
if(v === "false")
|
||||
return false;
|
||||
if(v.match(/^[0-9]+$/))
|
||||
if(v.match && v.match(/^[0-9]+$/))
|
||||
return parseInt(v);
|
||||
if(v.match(/^[0-9\.]+$/))
|
||||
if(v.match && v.match(/^[0-9\.]+$/))
|
||||
return parseFloat(v);
|
||||
return v;
|
||||
}
|
||||
|
@ -163,6 +168,8 @@ var VOLUME = parseFloat(getOrDefault("volume", 1));
|
|||
var NO_WEBSOCKETS = USEROPTS.altsocket;
|
||||
var NO_VIMEO = Boolean(location.host.match("cytu.be"));
|
||||
|
||||
var JSPREF = getOpt("channel_js_pref") || {};
|
||||
|
||||
var Rank = {
|
||||
Guest: 0,
|
||||
Member: 1,
|
||||
|
|
139
www/js/util.js
139
www/js/util.js
|
@ -589,17 +589,6 @@ function showUserOptions() {
|
|||
$("#us-layout").val(USEROPTS.layout);
|
||||
$("#us-no-channelcss").prop("checked", USEROPTS.ignore_channelcss);
|
||||
$("#us-no-channeljs").prop("checked", USEROPTS.ignore_channeljs);
|
||||
/*
|
||||
if (!ALLOW_SSL) {
|
||||
$("#us-ssl").prop("checked", false);
|
||||
$("#us-ssl").attr("disabled", true);
|
||||
$("#us-ssl").attr("title", "This server has not enabled SSL");
|
||||
} else {
|
||||
$("#us-ssl").prop("checked", USEROPTS.secure_connection);
|
||||
$("#us-ssl").attr("disabled", false);
|
||||
$("#us-ssl").attr("title", "");
|
||||
}
|
||||
*/
|
||||
var conninfo = "<strong>Connection Information: </strong>" +
|
||||
"Connected to <code>" + IO_URL + "</code> (";
|
||||
if (IO_V6) {
|
||||
|
@ -638,6 +627,8 @@ function showUserOptions() {
|
|||
$("#us-joinmessage").prop("checked", USEROPTS.joinmessage);
|
||||
$("#us-shadowchat").prop("checked", USEROPTS.show_shadowchat);
|
||||
|
||||
formatScriptAccessPrefs();
|
||||
|
||||
$("a[href='#us-general']").click();
|
||||
$("#useroptions").modal();
|
||||
}
|
||||
|
@ -1845,14 +1836,13 @@ function chatDialog(div) {
|
|||
"z-index": "auto",
|
||||
position: "absolute"
|
||||
})
|
||||
.appendTo($("body"));
|
||||
.appendTo($("#chatwrap"));
|
||||
|
||||
div.appendTo(parent);
|
||||
var cw = $("#chatwrap").width();
|
||||
var ch = $("#chatwrap").height();
|
||||
var cp = $("#chatwrap").offset();
|
||||
var x = cp.left + cw/2 - parent.width()/2;
|
||||
var y = cp.top + ch/2 - parent.height()/2;
|
||||
var x = cw/2 - parent.width()/2;
|
||||
var y = ch/2 - parent.height()/2;
|
||||
parent.css("left", x + "px");
|
||||
parent.css("top", y + "px");
|
||||
return parent;
|
||||
|
@ -2563,3 +2553,122 @@ function fallbackRaw(data) {
|
|||
|
||||
handleMediaUpdate(data);
|
||||
}
|
||||
|
||||
function checkScriptAccess(source, type, cb) {
|
||||
var pref = JSPREF[CHANNEL.name.toLowerCase() + "_" + type];
|
||||
if (pref === "ALLOW") {
|
||||
return cb("ALLOW");
|
||||
} else if (pref !== "DENY") {
|
||||
var div = $("#chanjs-allow-prompt");
|
||||
if (div.length > 0) {
|
||||
setTimeout(function () {
|
||||
checkScriptAccess(source, type, cb);
|
||||
}, 500);
|
||||
return;
|
||||
}
|
||||
|
||||
div = $("<div/>").attr("id", "chanjs-allow-prompt");
|
||||
var close = $("<button/>").addClass("close pull-right")
|
||||
.html("×")
|
||||
.appendTo(div);
|
||||
var form = $("<form/>")
|
||||
.attr("id", "chanjs-allow-prompt")
|
||||
.attr("style", "text-align: center")
|
||||
.appendTo(div);
|
||||
form.append("<span>This channel is requesting permission to run a 3rd-party " +
|
||||
"script for additional features. <strong>Only " +
|
||||
"run scripts from channels you trust.</strong></span><br>");
|
||||
$("<a/>").attr("href", source)
|
||||
.attr("target", "_blank")
|
||||
.text(type === "embedded" ? "view embedded script" : source)
|
||||
.appendTo(form);
|
||||
form.append("<div id='chanjs-allow-prompt-buttons'>" +
|
||||
"<button id='chanjs-allow' class='btn btn-xs btn-danger'>Allow</button>" +
|
||||
"<button id='chanjs-deny' class='btn btn-xs btn-danger'>Deny</button>" +
|
||||
"</div>");
|
||||
form.append("<div class='checkbox'><label><input type='checkbox' " +
|
||||
"id='chanjs-save-pref'/>Remember my choice for this channel" +
|
||||
"</label></div>");
|
||||
var dialog = chatDialog(div);
|
||||
|
||||
close.click(function () {
|
||||
dialog.remove();
|
||||
/* Implicit denial of script access */
|
||||
cb("DENY");
|
||||
});
|
||||
|
||||
$("#chanjs-allow").click(function () {
|
||||
var save = $("#chanjs-save-pref").is(":checked");
|
||||
dialog.remove();
|
||||
if (save) {
|
||||
JSPREF[CHANNEL.name.toLowerCase() + "_" + type] = "ALLOW";
|
||||
setOpt("channel_js_pref", JSPREF);
|
||||
}
|
||||
cb("ALLOW");
|
||||
});
|
||||
|
||||
$("#chanjs-deny").click(function () {
|
||||
var save = $("#chanjs-save-pref").is(":checked");
|
||||
dialog.remove();
|
||||
if (save) {
|
||||
JSPREF[CHANNEL.name.toLowerCase() + "_" + type] = "DENY";
|
||||
setOpt("channel_js_pref", JSPREF);
|
||||
}
|
||||
cb("DENY");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function formatScriptAccessPrefs() {
|
||||
var tbl = $("#us-scriptcontrol table");
|
||||
tbl.find("tbody").remove();
|
||||
|
||||
var channels = Object.keys(JSPREF).sort();
|
||||
channels.forEach(function (channel) {
|
||||
var parts = channel.split("_");
|
||||
if (!parts[1].match(/^(external|embedded)$/)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var pref = JSPREF[channel];
|
||||
var tr = $("<tr/>").appendTo(tbl);
|
||||
$("<td/>").text(parts[0]).appendTo(tr);
|
||||
$("<td/>").text(parts[1]).appendTo(tr);
|
||||
|
||||
var pref_td = $("<td/>").appendTo(tr);
|
||||
var allow_label = $("<label/>").addClass("radio-inline")
|
||||
.text("Allow").appendTo(pref_td);
|
||||
var allow = $("<input/>").attr("type", "radio")
|
||||
.prop("checked", pref === "ALLOW").
|
||||
prependTo(allow_label);
|
||||
allow.change(function () {
|
||||
if (allow.is(":checked")) {
|
||||
JSPREF[channel] = "ALLOW";
|
||||
setOpt("channel_js_pref", JSPREF);
|
||||
deny.prop("checked", false);
|
||||
}
|
||||
});
|
||||
|
||||
var deny_label = $("<label/>").addClass("radio-inline")
|
||||
.text("Deny").appendTo(pref_td);
|
||||
var deny = $("<input/>").attr("type", "radio")
|
||||
.prop("checked", pref === "DENY").
|
||||
prependTo(deny_label);
|
||||
deny.change(function () {
|
||||
if (deny.is(":checked")) {
|
||||
JSPREF[channel] = "DENY";
|
||||
setOpt("channel_js_pref", JSPREF);
|
||||
allow.prop("checked", false);
|
||||
}
|
||||
});
|
||||
|
||||
var clearpref = $("<button/>").addClass("btn btn-sm btn-danger")
|
||||
.text("Clear Preference")
|
||||
.appendTo($("<td/>").appendTo(tr))
|
||||
.click(function () {
|
||||
delete JSPREF[channel];
|
||||
setOpt("channel_js_pref", JSPREF);
|
||||
tr.remove();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue