mirror of https://github.com/calzoneman/sync.git
Merge branch '3.0' of https://github.com/calzoneman/sync into 3.0
This commit is contained in:
commit
461301abfc
|
@ -163,10 +163,7 @@ Channel.prototype.loadState = function () {
|
||||||
var errorLoad = function (msg) {
|
var errorLoad = function (msg) {
|
||||||
if (self.modules.customization) {
|
if (self.modules.customization) {
|
||||||
self.modules.customization.load({
|
self.modules.customization.load({
|
||||||
motd: {
|
motd: msg
|
||||||
motd: msg,
|
|
||||||
html: msg
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -482,7 +482,8 @@ ChatModule.prototype.handleCmdMute = function (user, msg, meta) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target.account.effectiveRank >= user.account.effectiveRank) {
|
if (target.account.effectiveRank >= user.account.effectiveRank
|
||||||
|
|| target.account.globalRank > user.account.globalRank) {
|
||||||
user.socket.emit("errorMsg", {
|
user.socket.emit("errorMsg", {
|
||||||
msg: "/mute failed - " + target.getName() + " has equal or higher rank " +
|
msg: "/mute failed - " + target.getName() + " has equal or higher rank " +
|
||||||
"than you."
|
"than you."
|
||||||
|
@ -531,7 +532,8 @@ ChatModule.prototype.handleCmdSMute = function (user, msg, meta) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target.account.effectiveRank >= user.account.effectiveRank) {
|
if (target.account.effectiveRank >= user.account.effectiveRank
|
||||||
|
|| target.account.globalRank > user.account.globalRank) {
|
||||||
user.socket.emit("errorMsg", {
|
user.socket.emit("errorMsg", {
|
||||||
msg: "/smute failed - " + target.getName() + " has equal or higher rank " +
|
msg: "/smute failed - " + target.getName() + " has equal or higher rank " +
|
||||||
"than you."
|
"than you."
|
||||||
|
|
|
@ -17,10 +17,7 @@ function CustomizationModule(channel) {
|
||||||
ChannelModule.apply(this, arguments);
|
ChannelModule.apply(this, arguments);
|
||||||
this.css = "";
|
this.css = "";
|
||||||
this.js = "";
|
this.js = "";
|
||||||
this.motd = {
|
this.motd = "";
|
||||||
motd: "",
|
|
||||||
html: ""
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomizationModule.prototype = Object.create(ChannelModule.prototype);
|
CustomizationModule.prototype = Object.create(ChannelModule.prototype);
|
||||||
|
@ -35,10 +32,15 @@ CustomizationModule.prototype.load = function (data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("motd" in data) {
|
if ("motd" in data) {
|
||||||
this.motd = {
|
if (typeof data.motd === "object" && data.motd.motd) {
|
||||||
motd: data.motd.motd || "",
|
// Old style MOTD, convert to new
|
||||||
html: data.motd.html || ""
|
this.motd = XSS.sanitizeHTML(data.motd.motd).replace(
|
||||||
};
|
/\n/g, "<br>\n");
|
||||||
|
} else if (typeof data.motd === "string") {
|
||||||
|
// The MOTD is filtered before it is saved, however it is also
|
||||||
|
// re-filtered on load in case the filtering rules change
|
||||||
|
this.motd = XSS.sanitizeHTML(data.motd);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -49,12 +51,7 @@ CustomizationModule.prototype.save = function (data) {
|
||||||
};
|
};
|
||||||
|
|
||||||
CustomizationModule.prototype.setMotd = function (motd) {
|
CustomizationModule.prototype.setMotd = function (motd) {
|
||||||
motd = XSS.sanitizeHTML(motd);
|
this.motd = XSS.sanitizeHTML(motd);
|
||||||
var html = motd.replace(/\n/g, "<br>");
|
|
||||||
this.motd = {
|
|
||||||
motd: motd,
|
|
||||||
html: html
|
|
||||||
};
|
|
||||||
this.sendMotd(this.channel.users);
|
this.sendMotd(this.channel.users);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -175,7 +175,8 @@ KickBanModule.prototype.handleCmdKick = function (user, msg, meta) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target.account.effectiveRank >= user.account.effectiveRank) {
|
if (target.account.effectiveRank >= user.account.effectiveRank
|
||||||
|
|| target.account.globalRank > user.account.globalRank) {
|
||||||
return user.socket.emit("errorMsg", {
|
return user.socket.emit("errorMsg", {
|
||||||
msg: "You do not have permission to kick " + target.getName()
|
msg: "You do not have permission to kick " + target.getName()
|
||||||
});
|
});
|
||||||
|
|
|
@ -627,7 +627,7 @@ PlaylistModule.prototype.handleJumpTo = function (user, data) {
|
||||||
var old = this.current;
|
var old = this.current;
|
||||||
this.current = to;
|
this.current = to;
|
||||||
this.startPlayback();
|
this.startPlayback();
|
||||||
this.channel.logger.log("[playlist] " + user.getName() + " skipped" + title);
|
this.channel.logger.log("[playlist] " + user.getName() + " skipped " + title);
|
||||||
|
|
||||||
if (old && old.temp) {
|
if (old && old.temp) {
|
||||||
this._delete(old.uid);
|
this._delete(old.uid);
|
||||||
|
@ -937,6 +937,9 @@ PlaylistModule.prototype._addItem = function (media, data, user, cb) {
|
||||||
self.meta.count++;
|
self.meta.count++;
|
||||||
self.meta.rawTime += media.seconds;
|
self.meta.rawTime += media.seconds;
|
||||||
self.meta.time = util.formatTime(self.meta.rawTime);
|
self.meta.time = util.formatTime(self.meta.rawTime);
|
||||||
|
var m = item.media;
|
||||||
|
self.channel.logger.log("[playlist] " + (data.queueby || "(anonymous)") +
|
||||||
|
" added " + m.title + " (" + m.type + ":" + m.id + ")");
|
||||||
|
|
||||||
var perms = self.channel.modules.permissions;
|
var perms = self.channel.modules.permissions;
|
||||||
self.channel.users.forEach(function (u) {
|
self.channel.users.forEach(function (u) {
|
||||||
|
@ -1046,7 +1049,12 @@ PlaylistModule.prototype.startPlayback = function (time) {
|
||||||
self.channel.notifyModules("onMediaChange", [self.current.media]);
|
self.channel.notifyModules("onMediaChange", [self.current.media]);
|
||||||
|
|
||||||
/* Only start the timer if the media item is not live, i.e. has a duration */
|
/* Only start the timer if the media item is not live, i.e. has a duration */
|
||||||
if (media.seconds > 0) {
|
/*
|
||||||
|
* 2015-01-22: Don't start the timer if there is an active leader or if
|
||||||
|
* the timer is already running. Both are possible since checkModules()
|
||||||
|
* is asynchronous
|
||||||
|
*/
|
||||||
|
if (media.seconds > 0 && !self.leader && !self._leadInterval) {
|
||||||
self._lastUpdate = Date.now();
|
self._lastUpdate = Date.now();
|
||||||
self._leadInterval = setInterval(function() {
|
self._leadInterval = setInterval(function() {
|
||||||
self._leadLoop();
|
self._leadLoop();
|
||||||
|
|
|
@ -409,7 +409,8 @@ module.exports.saveUserPlaylist = function (pl, username, plname, callback) {
|
||||||
type: pl[i].media.type,
|
type: pl[i].media.type,
|
||||||
meta: {
|
meta: {
|
||||||
codec: pl[i].media.meta.codec,
|
codec: pl[i].media.meta.codec,
|
||||||
bitrate: pl[i].media.meta.bitrate
|
bitrate: pl[i].media.meta.bitrate,
|
||||||
|
scuri: pl[i].media.meta.scuri
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
time += pl[i].media.seconds || 0;
|
time += pl[i].media.seconds || 0;
|
||||||
|
|
|
@ -426,7 +426,8 @@ module.exports = {
|
||||||
|
|
||||||
var meta = JSON.stringify({
|
var meta = JSON.stringify({
|
||||||
bitrate: media.meta.bitrate,
|
bitrate: media.meta.bitrate,
|
||||||
codec: media.meta.codec
|
codec: media.meta.codec,
|
||||||
|
scuri: media.meta.scuri
|
||||||
});
|
});
|
||||||
|
|
||||||
db.query("INSERT INTO `channel_libraries` " +
|
db.query("INSERT INTO `channel_libraries` " +
|
||||||
|
|
|
@ -34,6 +34,12 @@ const CONTENT_TYPES = {
|
||||||
|
|
||||||
var urlRetrieve = function (transport, options, callback) {
|
var urlRetrieve = function (transport, options, callback) {
|
||||||
var req = transport.request(options, function (res) {
|
var req = transport.request(options, function (res) {
|
||||||
|
res.on("error", function (err) {
|
||||||
|
Logger.errlog.log("HTTP response " + options.host + options.path + " failed: "+
|
||||||
|
err);
|
||||||
|
callback(503, "");
|
||||||
|
});
|
||||||
|
|
||||||
var buffer = "";
|
var buffer = "";
|
||||||
res.setEncoding("utf-8");
|
res.setEncoding("utf-8");
|
||||||
res.on("data", function (chunk) {
|
res.on("data", function (chunk) {
|
||||||
|
@ -546,7 +552,11 @@ var Getters = {
|
||||||
data = JSON.parse(data);
|
data = JSON.parse(data);
|
||||||
var seconds = data.duration / 1000;
|
var seconds = data.duration / 1000;
|
||||||
var title = data.title;
|
var title = data.title;
|
||||||
var media = new Media(id, title, seconds, "sc");
|
var meta = {};
|
||||||
|
if (data.sharing === "private" && data.embeddable_by === "all") {
|
||||||
|
meta.scuri = data.uri;
|
||||||
|
}
|
||||||
|
var media = new Media(id, title, seconds, "sc", meta);
|
||||||
callback(false, media);
|
callback(false, media);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
callback(e, null);
|
callback(e, null);
|
||||||
|
@ -979,18 +989,16 @@ function vimeoWorkaround(id, cb) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
http.get(options, function (res) {
|
urlRetrieve(http, options, function (status, buffer) {
|
||||||
res.setEncoding("utf-8");
|
if (status !== 200) {
|
||||||
var buffer = "";
|
setImmediate(function () {
|
||||||
|
cb({});
|
||||||
res.on("data", function (data) {
|
|
||||||
buffer += data;
|
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
res.on("end", function () {
|
|
||||||
parse(buffer);
|
parse(buffer);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
inner();
|
inner();
|
||||||
|
|
|
@ -217,7 +217,15 @@ module.exports = {
|
||||||
if (id in srv.servers) {
|
if (id in srv.servers) {
|
||||||
io.attach(srv.servers[id]);
|
io.attach(srv.servers[id]);
|
||||||
} else {
|
} else {
|
||||||
io.attach(require("http").createServer().listen(bind.port, bind.ip));
|
var server = require("http").createServer().listen(bind.port, bind.ip);
|
||||||
|
server.on("clientError", function (err, socket) {
|
||||||
|
console.error("clientError on " + id + " - " + err);
|
||||||
|
try {
|
||||||
|
socket.destroy();
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
io.attach(server);
|
||||||
}
|
}
|
||||||
|
|
||||||
bound[id] = null;
|
bound[id] = null;
|
||||||
|
|
|
@ -36,7 +36,8 @@ Media.prototype = {
|
||||||
gpdirect: this.meta.gpdirect,
|
gpdirect: this.meta.gpdirect,
|
||||||
restricted: this.meta.restricted,
|
restricted: this.meta.restricted,
|
||||||
codec: this.meta.codec,
|
codec: this.meta.codec,
|
||||||
bitrate: this.meta.bitrate
|
bitrate: this.meta.bitrate,
|
||||||
|
scuri: this.meta.scuri
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const VERSION = "3.6.1";
|
const VERSION = "3.6.3";
|
||||||
var singleton = null;
|
var singleton = null;
|
||||||
var Config = require("./config");
|
var Config = require("./config");
|
||||||
|
|
||||||
|
@ -85,8 +85,22 @@ var Server = function () {
|
||||||
if (bind.https && Config.get("https.enabled")) {
|
if (bind.https && Config.get("https.enabled")) {
|
||||||
self.servers[id] = https.createServer(opts, self.express)
|
self.servers[id] = https.createServer(opts, self.express)
|
||||||
.listen(bind.port, bind.ip);
|
.listen(bind.port, bind.ip);
|
||||||
|
self.servers[id].on("clientError", function (err, socket) {
|
||||||
|
console.error("clientError on " + id + " - " + err);
|
||||||
|
try {
|
||||||
|
socket.destroy();
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
});
|
||||||
} else if (bind.http) {
|
} else if (bind.http) {
|
||||||
self.servers[id] = self.express.listen(bind.port, bind.ip);
|
self.servers[id] = self.express.listen(bind.port, bind.ip);
|
||||||
|
self.servers[id].on("clientError", function (err, socket) {
|
||||||
|
console.error("clientError on " + id + " - " + err);
|
||||||
|
try {
|
||||||
|
socket.destroy();
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
309
lib/xss.js
309
lib/xss.js
|
@ -1,260 +1,64 @@
|
||||||
/*
|
var sanitizeHTML = require("sanitize-html");
|
||||||
WARNING
|
|
||||||
|
|
||||||
This file contains an XSS prevention module I wrote myself. It has not
|
// These tags are allowed in addition to the defaults
|
||||||
been verified by any external agency, and due to the nature of XSS I cannot
|
// See https://github.com/punkave/sanitize-html
|
||||||
guarantee that it will filter correctly. Feel free to send me bug reports
|
const ALLOWED_TAGS = [
|
||||||
and I will do my best to fix them, but use at your own risk.
|
"button",
|
||||||
|
"center",
|
||||||
|
"details",
|
||||||
|
"font",
|
||||||
|
"h1",
|
||||||
|
"h2",
|
||||||
|
"img",
|
||||||
|
"marquee", // It pains me to do this, but a lot of people use it...
|
||||||
|
"s",
|
||||||
|
"section",
|
||||||
|
"span",
|
||||||
|
"summary"
|
||||||
|
];
|
||||||
|
|
||||||
*/
|
const ALLOWED_ATTRIBUTES = [
|
||||||
|
"id",
|
||||||
/* Prototype for a basic XML tag parser */
|
"aria-*",
|
||||||
function TagParser(text) {
|
"border",
|
||||||
this.text = text;
|
"class",
|
||||||
this.i = 0;
|
"color",
|
||||||
this.tag = this.parse();
|
"data-*",
|
||||||
}
|
"height",
|
||||||
|
"role",
|
||||||
/* Moves the position marker past any whitespace characters */
|
|
||||||
TagParser.prototype.skipWhitespace = function () {
|
|
||||||
while (this.i < this.text.length && this.text[this.i].match(/\s/)) {
|
|
||||||
this.i++;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Reads a literal value matching the given regexp. Defaults
|
|
||||||
to /[^\s>]/; i.e. any string not containing whitespace or
|
|
||||||
the end of tag character '>'
|
|
||||||
*/
|
|
||||||
TagParser.prototype.readLiteral = function (regexp) {
|
|
||||||
if (regexp === void 0) {
|
|
||||||
regexp = /[^\s>]/;
|
|
||||||
}
|
|
||||||
var str = "";
|
|
||||||
while (this.i < this.text.length && this.text[this.i].match(regexp)) {
|
|
||||||
str += this.text[this.i];
|
|
||||||
this.i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
str = str.replace(/&#([0-9]{2,7});?/g, function (m, p1) {
|
|
||||||
return String.fromCharCode(parseInt(p1));
|
|
||||||
});
|
|
||||||
|
|
||||||
str = str.replace(/&#x([0-9a-fA-F]{2,7});?/g, function (m, p1) {
|
|
||||||
return String.fromCharCode(parseInt(p1, 16));
|
|
||||||
});
|
|
||||||
|
|
||||||
str = str.replace(/[\x00-\x1f]/g, "");
|
|
||||||
return str;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* If the character at the current position is a quote, read
|
|
||||||
a string. Otherwise, read a literal
|
|
||||||
*/
|
|
||||||
TagParser.prototype.readLiteralOrString = function (regexp) {
|
|
||||||
if (this.text[this.i].match(/["'`]/)) {
|
|
||||||
return this.readString();
|
|
||||||
}
|
|
||||||
return this.readLiteral(regexp);
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Read a string delimited by the character at the current
|
|
||||||
position. For XML tags this means strings enclosed in
|
|
||||||
" or '. Treats \" as a literal '"' symbol and not a
|
|
||||||
delimiter.
|
|
||||||
*/
|
|
||||||
TagParser.prototype.readString = function () {
|
|
||||||
var delim = this.text[this.i++];
|
|
||||||
|
|
||||||
var str = "";
|
|
||||||
while (this.i < this.text.length && this.text[this.i] !== delim) {
|
|
||||||
if (this.text[this.i] === "\\" && this.text[this.i+1] === delim) {
|
|
||||||
str += this.text[this.i+1];
|
|
||||||
this.i++;
|
|
||||||
} else {
|
|
||||||
str += this.text[this.i];
|
|
||||||
}
|
|
||||||
this.i++;
|
|
||||||
}
|
|
||||||
this.i++;
|
|
||||||
|
|
||||||
str = str.replace(/&#([0-9]{2,7});?/g, function (m, p1) {
|
|
||||||
return String.fromCharCode(parseInt(p1));
|
|
||||||
});
|
|
||||||
|
|
||||||
str = str.replace(/&#x([0-9a-fA-F]{2,7});?/g, function (m, p1) {
|
|
||||||
return String.fromCharCode(parseInt(p1, 16));
|
|
||||||
});
|
|
||||||
|
|
||||||
str = str.replace(/[\x00-\x1f]/g, "");
|
|
||||||
return str;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Attempts to parse a tagname and attributes from an
|
|
||||||
XML tag.
|
|
||||||
NOTE: Does not actually parse a DOM node, only parses
|
|
||||||
the tag between '<' and '>' because that's all I need
|
|
||||||
to do XSS filtering, I don't care what's between a tag
|
|
||||||
and its end tag (if it's another tag I handle that
|
|
||||||
separately)
|
|
||||||
*/
|
|
||||||
TagParser.prototype.parse = function () {
|
|
||||||
this.i = this.text.indexOf("<");
|
|
||||||
// Not a tag
|
|
||||||
if (this.i === -1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.i++;
|
|
||||||
this.skipWhitespace();
|
|
||||||
|
|
||||||
// First non-whitespace string after the opening '<' is the tag name
|
|
||||||
var tname = this.readLiteral();
|
|
||||||
|
|
||||||
var attrs = {};
|
|
||||||
// Continue parsing attributes until the end of string is reached or
|
|
||||||
// the end of tag is reached
|
|
||||||
while (this.i < this.text.length && this.text[this.i] !== ">") {
|
|
||||||
// Read any string not containing equals, possibly delimited by
|
|
||||||
// " or '
|
|
||||||
var key = this.readLiteralOrString(/[^\s=>]/);
|
|
||||||
this.skipWhitespace();
|
|
||||||
// It's possible for tags to have attributes with no value, where
|
|
||||||
// the equals sign is not necessary
|
|
||||||
if (this.text[this.i] !== "=") {
|
|
||||||
if (key.trim().length > 0) {
|
|
||||||
attrs[key] = "";
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.i++;
|
|
||||||
//this.skipWhitespace();
|
|
||||||
var value = this.readLiteralOrString();
|
|
||||||
if (key.trim().length > 0) {
|
|
||||||
attrs[key] = value;
|
|
||||||
}
|
|
||||||
this.skipWhitespace();
|
|
||||||
}
|
|
||||||
|
|
||||||
// If end-of-string was not reached, consume the ending '>'
|
|
||||||
if (this.i < this.text.length) {
|
|
||||||
this.i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
tagName: tname,
|
|
||||||
attributes: attrs,
|
|
||||||
text: this.text.substring(0, this.i) // Original text (for replacement)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Some of these may not even be HTML tags, I borrowed them from the
|
|
||||||
[now deprecated] XSS module of node-validator
|
|
||||||
*/
|
|
||||||
const badTags = new RegExp([
|
|
||||||
"alert",
|
|
||||||
"applet",
|
|
||||||
"audio",
|
|
||||||
"basefont",
|
|
||||||
"base",
|
|
||||||
"behavior",
|
|
||||||
"bgsound",
|
|
||||||
"blink",
|
|
||||||
"body",
|
|
||||||
"embed",
|
|
||||||
"expression",
|
|
||||||
"form",
|
|
||||||
"frameset",
|
|
||||||
"frame",
|
|
||||||
"head",
|
|
||||||
"html",
|
|
||||||
"ilayer",
|
|
||||||
"iframe",
|
|
||||||
"input",
|
|
||||||
"layer",
|
|
||||||
"link",
|
|
||||||
"meta",
|
|
||||||
"object",
|
|
||||||
"style",
|
"style",
|
||||||
"script",
|
|
||||||
"textarea",
|
|
||||||
"title",
|
"title",
|
||||||
"video",
|
"valign",
|
||||||
"xml",
|
"width"
|
||||||
"xss"
|
];
|
||||||
].join("|"), "i");
|
|
||||||
|
|
||||||
/* Nasty attributes. Anything starting with "on" is probably a javascript
|
var ATTRIBUTE_MAP = {
|
||||||
callback, and I hope you see why formaction is a bad idea.
|
a: ["href", "name", "target"],
|
||||||
*/
|
font: ["size"],
|
||||||
const badAttrs = new RegExp([
|
img: ["src"],
|
||||||
"\\bon\\S*",
|
marquee: ["behavior", "behaviour", "direction", "scrollamount"],
|
||||||
"\\bformaction",
|
table: ["cellpadding", "cellspacing"],
|
||||||
"\\baction"
|
th: ["colspan", "rowspan"],
|
||||||
].join("|"), "i");
|
td: ["colspan", "rowspan"]
|
||||||
|
|
||||||
function sanitizeHTML(str) {
|
|
||||||
var i = str.indexOf("<");
|
|
||||||
if (i === -1) {
|
|
||||||
// No HTML tags in the string
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loop across all tag delimiters '<' in string, parse each one,
|
|
||||||
// and replace the results with sanitized tags
|
|
||||||
while (i !== -1) {
|
|
||||||
var t = new TagParser(str.substring(i)).tag;
|
|
||||||
if (t.tagName.replace("/", "").match(badTags)) {
|
|
||||||
// Note: Important that I replace the tag with a nonempty value,
|
|
||||||
// otherwise <scr<script>ipt> would possibly defeat the filter.
|
|
||||||
str = str.replace(t.text, "[tag removed]");
|
|
||||||
i = str.indexOf("<", i+1);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (var k in t.attributes) {
|
|
||||||
// Keys should not contain non-word characters.
|
|
||||||
var k2 = k.replace(/[^\w]/g, "");
|
|
||||||
if (k2 !== k) {
|
|
||||||
t.attributes[k2] = t.attributes[k];
|
|
||||||
delete t.attributes[k];
|
|
||||||
k = k2;
|
|
||||||
}
|
|
||||||
// If it's an evil attribute, just nuke it entirely
|
|
||||||
if (k.match(badAttrs)) {
|
|
||||||
delete t.attributes[k];
|
|
||||||
} else {
|
|
||||||
if (t.attributes[k].replace(/\s/g, "").indexOf("javascript:") !== -1) {
|
|
||||||
t.attributes[k] = "[removed]";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Build the sanitized tag
|
|
||||||
var fmt = "<" + t.tagName;
|
|
||||||
for (var k in t.attributes) {
|
|
||||||
if (k.trim().length > 0) {
|
|
||||||
fmt += " " + k;
|
|
||||||
if (t.attributes[k].trim().length > 0) {
|
|
||||||
var delim = '"';
|
|
||||||
if (t.attributes[k].match(/[^\\]"/)) {
|
|
||||||
delim = "'";
|
|
||||||
if (t.attributes[k].match(/[^\\]'/)) {
|
|
||||||
delim = "`";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt += "=" + delim + t.attributes[k] + delim;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
str = str.replace(t.text, fmt + ">");
|
|
||||||
i = str.indexOf("<", i + fmt.length + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return str;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* WIP: Sanitize a string where HTML is prohibited */
|
for (var key in ATTRIBUTE_MAP) {
|
||||||
|
ALLOWED_ATTRIBUTES.forEach(function (attr) {
|
||||||
|
ATTRIBUTE_MAP[key].push(attr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sanitizeHTML.defaults.allowedTags.concat(ALLOWED_TAGS).forEach(function (tag) {
|
||||||
|
if (!(tag in ATTRIBUTE_MAP)) {
|
||||||
|
ATTRIBUTE_MAP[tag] = ALLOWED_ATTRIBUTES;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const SETTINGS = {
|
||||||
|
allowedTags: sanitizeHTML.defaults.allowedTags.concat(ALLOWED_TAGS),
|
||||||
|
allowedAttributes: ATTRIBUTE_MAP
|
||||||
|
};
|
||||||
|
|
||||||
function sanitizeText(str) {
|
function sanitizeText(str) {
|
||||||
str = str.replace(/&/g, "&")
|
str = str.replace(/&/g, "&")
|
||||||
.replace(/</g, "<")
|
.replace(/</g, "<")
|
||||||
|
@ -280,6 +84,9 @@ function decodeText(str) {
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.sanitizeHTML = sanitizeHTML;
|
module.exports.sanitizeHTML = function (html) {
|
||||||
|
return sanitizeHTML(html, SETTINGS);
|
||||||
|
};
|
||||||
|
|
||||||
module.exports.sanitizeText = sanitizeText;
|
module.exports.sanitizeText = sanitizeText;
|
||||||
module.exports.decodeText = decodeText;
|
module.exports.decodeText = decodeText;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"author": "Calvin Montgomery",
|
"author": "Calvin Montgomery",
|
||||||
"name": "CyTube",
|
"name": "CyTube",
|
||||||
"description": "Online media synchronizer and chat",
|
"description": "Online media synchronizer and chat",
|
||||||
"version": "3.6.1",
|
"version": "3.6.3",
|
||||||
"repository": {
|
"repository": {
|
||||||
"url": "http://github.com/calzoneman/sync"
|
"url": "http://github.com/calzoneman/sync"
|
||||||
},
|
},
|
||||||
|
@ -11,7 +11,7 @@
|
||||||
"body-parser": "^1.6.5",
|
"body-parser": "^1.6.5",
|
||||||
"compression": "^1.2.0",
|
"compression": "^1.2.0",
|
||||||
"cookie-parser": "^1.3.2",
|
"cookie-parser": "^1.3.2",
|
||||||
"cytubefilters": "git://github.com/calzoneman/cytubefilters#a5a99642",
|
"cytubefilters": "git://github.com/calzoneman/cytubefilters#89f56fee",
|
||||||
"express": "^4.8.5",
|
"express": "^4.8.5",
|
||||||
"express-minify": "0.0.11",
|
"express-minify": "0.0.11",
|
||||||
"graceful-fs": "^3.0.5",
|
"graceful-fs": "^3.0.5",
|
||||||
|
@ -22,8 +22,9 @@
|
||||||
"nodemailer": "^1.2.0",
|
"nodemailer": "^1.2.0",
|
||||||
"oauth": "^0.9.12",
|
"oauth": "^0.9.12",
|
||||||
"q": "^1.0.1",
|
"q": "^1.0.1",
|
||||||
|
"sanitize-html": "git://github.com/calzoneman/sanitize-html",
|
||||||
"serve-static": "^1.5.3",
|
"serve-static": "^1.5.3",
|
||||||
"socket.io": "^1.1.0",
|
"socket.io": "^1.3.2",
|
||||||
"yamljs": "^0.1.5"
|
"yamljs": "^0.1.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -175,12 +175,11 @@ Callbacks = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setMotd: function(data) {
|
setMotd: function(motd) {
|
||||||
CHANNEL.motd = data.html;
|
CHANNEL.motd = motd;
|
||||||
CHANNEL.motd_text = data.motd;
|
$("#motd").html(motd);
|
||||||
$("#motd").html(CHANNEL.motd);
|
$("#cs-motdtext").val(motd);
|
||||||
$("#cs-motdtext").val(CHANNEL.motd_text);
|
if (motd != "") {
|
||||||
if (data.motd != "") {
|
|
||||||
$("#motdwrap").show();
|
$("#motdwrap").show();
|
||||||
$("#motd").show();
|
$("#motd").show();
|
||||||
$("#togglemotd").find(".glyphicon-plus")
|
$("#togglemotd").find(".glyphicon-plus")
|
||||||
|
|
|
@ -19,7 +19,6 @@ var CHANNEL = {
|
||||||
css: "",
|
css: "",
|
||||||
js: "",
|
js: "",
|
||||||
motd: "",
|
motd: "",
|
||||||
motd_text: "",
|
|
||||||
name: false,
|
name: false,
|
||||||
usercount: 0,
|
usercount: 0,
|
||||||
emotes: []
|
emotes: []
|
||||||
|
|
|
@ -18,6 +18,7 @@ var YouTubePlayer = function (data) {
|
||||||
self.videoId = data.id;
|
self.videoId = data.id;
|
||||||
self.videoLength = data.seconds;
|
self.videoLength = data.seconds;
|
||||||
self.theYouTubeDevsNeedToFixThisShit = false;
|
self.theYouTubeDevsNeedToFixThisShit = false;
|
||||||
|
self.whyDoesSetPlaybackQualityHaveARaceCondition = true;
|
||||||
var wmode = USEROPTS.wmode_transparent ? "transparent" : "opaque";
|
var wmode = USEROPTS.wmode_transparent ? "transparent" : "opaque";
|
||||||
self.player = new YT.Player("ytapiplayer", {
|
self.player = new YT.Player("ytapiplayer", {
|
||||||
videoId: data.id,
|
videoId: data.id,
|
||||||
|
@ -34,6 +35,13 @@ var YouTubePlayer = function (data) {
|
||||||
PLAYER.setVolume(VOLUME);
|
PLAYER.setVolume(VOLUME);
|
||||||
},
|
},
|
||||||
onStateChange: function (ev) {
|
onStateChange: function (ev) {
|
||||||
|
if (self.whyDoesSetPlaybackQualityHaveARaceCondition) {
|
||||||
|
self.whyDoesSetPlaybackQualityHaveARaceCondition = false;
|
||||||
|
|
||||||
|
if (USEROPTS.default_quality) {
|
||||||
|
self.player.setPlaybackQuality(USEROPTS.default_quality);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Race conditions suck.
|
* Race conditions suck.
|
||||||
|
@ -42,7 +50,10 @@ var YouTubePlayer = function (data) {
|
||||||
*/
|
*/
|
||||||
if (ev.data === YT.PlayerState.PLAYING &&
|
if (ev.data === YT.PlayerState.PLAYING &&
|
||||||
self.theYouTubeDevsNeedToFixThisShit) {
|
self.theYouTubeDevsNeedToFixThisShit) {
|
||||||
PLAYER.seek(0.000001);
|
|
||||||
|
if (USEROPTS.default_quality) {
|
||||||
|
self.player.setPlaybackQuality(USEROPTS.default_quality);
|
||||||
|
}
|
||||||
PLAYER.pause();
|
PLAYER.pause();
|
||||||
self.theYouTubeDevsNeedToFixThisShit = false;
|
self.theYouTubeDevsNeedToFixThisShit = false;
|
||||||
}
|
}
|
||||||
|
@ -69,9 +80,10 @@ var YouTubePlayer = function (data) {
|
||||||
self.load = function (data) {
|
self.load = function (data) {
|
||||||
if(self.player && self.player.loadVideoById) {
|
if(self.player && self.player.loadVideoById) {
|
||||||
self.player.loadVideoById(data.id, data.currentTime);
|
self.player.loadVideoById(data.id, data.currentTime);
|
||||||
|
self.whyDoesSetPlaybackQualityHaveARaceCondition = true;
|
||||||
if (USEROPTS.default_quality) {
|
if (USEROPTS.default_quality) {
|
||||||
self.player.setPlaybackQuality(USEROPTS.default_quality);
|
// Try to set it ahead of time, if that works
|
||||||
// What's that? Another stupid hack for the HTML5 player?
|
// If not, the onStateChange callback will try again anyways
|
||||||
self.player.setPlaybackQuality(USEROPTS.default_quality);
|
self.player.setPlaybackQuality(USEROPTS.default_quality);
|
||||||
}
|
}
|
||||||
self.videoId = data.id;
|
self.videoId = data.id;
|
||||||
|
@ -428,6 +440,7 @@ var SoundcloudPlayer = function (data) {
|
||||||
// Go figure
|
// Go figure
|
||||||
self.soundcloudIsSeriouslyFuckingBroken = VOLUME;
|
self.soundcloudIsSeriouslyFuckingBroken = VOLUME;
|
||||||
self.videoId = data.id;
|
self.videoId = data.id;
|
||||||
|
self.scuri = data.meta.scuri || self.videoId;
|
||||||
self.videoLength = data.seconds;
|
self.videoLength = data.seconds;
|
||||||
waitUntilDefined(window, "SC", function () {
|
waitUntilDefined(window, "SC", function () {
|
||||||
unfixSoundcloudShit();
|
unfixSoundcloudShit();
|
||||||
|
@ -436,7 +449,7 @@ var SoundcloudPlayer = function (data) {
|
||||||
iframe.appendTo($("#ytapiplayer"));
|
iframe.appendTo($("#ytapiplayer"));
|
||||||
|
|
||||||
iframe.attr("id", "scplayer");
|
iframe.attr("id", "scplayer");
|
||||||
iframe.attr("src", "https://w.soundcloud.com/player/?url="+self.videoId);
|
iframe.attr("src", "https://w.soundcloud.com/player/?url="+self.scuri);
|
||||||
iframe.css("height", "166px");
|
iframe.css("height", "166px");
|
||||||
iframe.css("border", "none");
|
iframe.css("border", "none");
|
||||||
|
|
||||||
|
@ -456,7 +469,7 @@ var SoundcloudPlayer = function (data) {
|
||||||
self.player = SC.Widget("scplayer");
|
self.player = SC.Widget("scplayer");
|
||||||
|
|
||||||
self.player.bind(SC.Widget.Events.READY, function () {
|
self.player.bind(SC.Widget.Events.READY, function () {
|
||||||
self.player.load(self.videoId, { auto_play: true });
|
self.player.load(self.scuri, { auto_play: true });
|
||||||
|
|
||||||
self.player.bind(SC.Widget.Events.PAUSE, function () {
|
self.player.bind(SC.Widget.Events.PAUSE, function () {
|
||||||
PLAYER.paused = true;
|
PLAYER.paused = true;
|
||||||
|
@ -487,9 +500,10 @@ var SoundcloudPlayer = function (data) {
|
||||||
|
|
||||||
self.load = function (data) {
|
self.load = function (data) {
|
||||||
self.videoId = data.id;
|
self.videoId = data.id;
|
||||||
|
self.scuri = data.meta.scuri || self.videoId;
|
||||||
self.videoLength = data.seconds;
|
self.videoLength = data.seconds;
|
||||||
if(self.player && self.player.load) {
|
if(self.player && self.player.load) {
|
||||||
self.player.load(data.id, { auto_play: true });
|
self.player.load(self.scuri, { auto_play: true });
|
||||||
var soundcloudNeedsToFuckingFixTheirPlayer = function () {
|
var soundcloudNeedsToFuckingFixTheirPlayer = function () {
|
||||||
self.setVolume(VOLUME);
|
self.setVolume(VOLUME);
|
||||||
self.player.unbind(SC.Widget.Events.PLAY_PROGRESS);
|
self.player.unbind(SC.Widget.Events.PLAY_PROGRESS);
|
||||||
|
|
|
@ -86,6 +86,24 @@ function formatUserlistItem(div) {
|
||||||
name.addClass(getNameColor(data.rank));
|
name.addClass(getNameColor(data.rank));
|
||||||
div.find(".profile-box").remove();
|
div.find(".profile-box").remove();
|
||||||
|
|
||||||
|
if (data.afk) {
|
||||||
|
div.addClass("userlist_afk");
|
||||||
|
} else {
|
||||||
|
div.removeClass("userlist_afk");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (div.data("meta") && div.data("meta").muted) {
|
||||||
|
div.addClass("userlist_muted");
|
||||||
|
} else {
|
||||||
|
div.removeClass("userlist_muted");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (div.data("meta") && div.data("meta").smuted) {
|
||||||
|
div.addClass("userlist_smuted");
|
||||||
|
} else {
|
||||||
|
div.removeClass("userlist_smuted");
|
||||||
|
}
|
||||||
|
|
||||||
var profile = null;
|
var profile = null;
|
||||||
name.mouseenter(function(ev) {
|
name.mouseenter(function(ev) {
|
||||||
if (profile)
|
if (profile)
|
||||||
|
@ -882,7 +900,7 @@ function handleModPermissions() {
|
||||||
})();
|
})();
|
||||||
$("#cs-csstext").val(CHANNEL.css);
|
$("#cs-csstext").val(CHANNEL.css);
|
||||||
$("#cs-jstext").val(CHANNEL.js);
|
$("#cs-jstext").val(CHANNEL.js);
|
||||||
$("#cs-motdtext").val(CHANNEL.motd_text);
|
$("#cs-motdtext").val(CHANNEL.motd);
|
||||||
setParentVisible("a[href='#cs-motdeditor']", hasPermission("motdedit"));
|
setParentVisible("a[href='#cs-motdeditor']", hasPermission("motdedit"));
|
||||||
setParentVisible("a[href='#cs-permedit']", CLIENT.rank >= 3);
|
setParentVisible("a[href='#cs-permedit']", CLIENT.rank >= 3);
|
||||||
setParentVisible("a[href='#cs-banlist']", hasPermission("ban"));
|
setParentVisible("a[href='#cs-banlist']", hasPermission("ban"));
|
||||||
|
|
Loading…
Reference in New Issue