diff --git a/lib/channel/opts.js b/lib/channel/opts.js
index e79e5e1a..f3a0dc49 100644
--- a/lib/channel/opts.js
+++ b/lib/channel/opts.js
@@ -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) {
diff --git a/lib/channel/playlist.js b/lib/channel/playlist.js
index efb3b38d..bf4a17d7 100644
--- a/lib/channel/playlist.js
+++ b/lib/channel/playlist.js
@@ -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) {
diff --git a/templates/channel.jade b/templates/channel.jade
index 560fe69d..baa6a826 100644
--- a/templates/channel.jade
+++ b/templates/channel.jade
@@ -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
diff --git a/templates/useroptions.jade b/templates/useroptions.jade
index 4fc72004..c91b4e3a 100644
--- a/templates/useroptions.jade
+++ b/templates/useroptions.jade
@@ -52,6 +52,17 @@ mixin us-general
p#us-conninfo.text-info Connection Information:
.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
diff --git a/www/css/cytube.css b/www/css/cytube.css
index c7111481..048bef51 100644
--- a/www/css/cytube.css
+++ b/www/css/cytube.css
@@ -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;
+}
diff --git a/www/js/callbacks.js b/www/js/callbacks.js
index 3d296bbe..d18af9f6 100644
--- a/www/js/callbacks.js
+++ b/www/js/callbacks.js
@@ -250,6 +250,7 @@ Callbacks = {
document.title = opts.pagetitle;
PAGETITLE = opts.pagetitle;
$("#chanexternalcss").remove();
+
if(opts.externalcss.trim() != "" && !USEROPTS.ignore_channelcss) {
$("")
.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) {
- $("").attr("type", "text/javascript")
- .attr("id", "chanjs")
- .text(data.js)
- .appendTo($("body"));
+ var src = data.js
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/\n/g, "
")
+ .replace(/\t/g, " ")
+ .replace(/ /g, " ");
+ src = encodeURIComponent(src);
+
+ var viewsource = "data:text/html,
" + IO_URL + "
(";
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 = $("").attr("id", "chanjs-allow-prompt");
+ var close = $("").addClass("close pull-right")
+ .html("×")
+ .appendTo(div);
+ var form = $("")
+ .attr("id", "chanjs-allow-prompt")
+ .attr("style", "text-align: center")
+ .appendTo(div);
+ form.append("This channel is requesting permission to run a 3rd-party " +
+ "script for additional features. Only " +
+ "run scripts from channels you trust.