Require user permission to run channel js

This commit is contained in:
Calvin Montgomery 2014-07-01 20:11:54 -07:00
parent 3661ab1fd9
commit e87ddb473b
8 changed files with 232 additions and 32 deletions

View File

@ -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) {

View File

@ -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) {

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/\n/g, "<br>")
.replace(/\t/g, " ")
.replace(/ /g, "&nbsp;");
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"));
}
});
}
},

View File

@ -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,

View File

@ -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("&times;")
.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();
});
});
}