From bf70d2760b3c68795bdfc5e096ff96cc4a425dc4 Mon Sep 17 00:00:00 2001 From: Calvin Montgomery Date: Sat, 3 Jan 2015 16:03:15 -0500 Subject: [PATCH 01/29] Log when a video is added --- lib/channel/playlist.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/channel/playlist.js b/lib/channel/playlist.js index 4d8ffff0..30ec0944 100644 --- a/lib/channel/playlist.js +++ b/lib/channel/playlist.js @@ -627,7 +627,7 @@ PlaylistModule.prototype.handleJumpTo = function (user, data) { var old = this.current; this.current = to; this.startPlayback(); - this.channel.logger.log("[playlist] " + user.getName() + " skipped" + title); + this.channel.logger.log("[playlist] " + user.getName() + " skipped " + title); if (old && old.temp) { this._delete(old.uid); @@ -937,6 +937,9 @@ PlaylistModule.prototype._addItem = function (media, data, user, cb) { self.meta.count++; self.meta.rawTime += media.seconds; 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; self.channel.users.forEach(function (u) { From 3423f43f2f7667d6859da905913a13dd16c2b2dd Mon Sep 17 00:00:00 2001 From: Calvin Montgomery Date: Sat, 3 Jan 2015 21:36:58 -0500 Subject: [PATCH 02/29] https://www.youtube.com/watch?v=9u6Bfnq3aZk --- www/js/player.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/www/js/player.js b/www/js/player.js index d0d0ef57..ae90590a 100644 --- a/www/js/player.js +++ b/www/js/player.js @@ -42,6 +42,10 @@ var YouTubePlayer = function (data) { */ if (ev.data === YT.PlayerState.PLAYING && self.theYouTubeDevsNeedToFixThisShit) { + + if (USEROPTS.default_quality) { + self.player.setPlaybackQuality(USEROPTS.default_quality); + } PLAYER.seek(0.000001); PLAYER.pause(); self.theYouTubeDevsNeedToFixThisShit = false; @@ -69,11 +73,6 @@ var YouTubePlayer = function (data) { self.load = function (data) { if(self.player && self.player.loadVideoById) { self.player.loadVideoById(data.id, data.currentTime); - if (USEROPTS.default_quality) { - self.player.setPlaybackQuality(USEROPTS.default_quality); - // What's that? Another stupid hack for the HTML5 player? - self.player.setPlaybackQuality(USEROPTS.default_quality); - } self.videoId = data.id; self.videoLength = data.seconds; } From cd22570c405f6d2dcc5dc956e769ed3da0f940af Mon Sep 17 00:00:00 2001 From: Calvin Montgomery Date: Sun, 4 Jan 2015 16:46:40 -0500 Subject: [PATCH 03/29] Hopefully fix youtube setPlaybackQuality once and for all --- www/js/player.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/www/js/player.js b/www/js/player.js index ae90590a..7431bb59 100644 --- a/www/js/player.js +++ b/www/js/player.js @@ -18,6 +18,7 @@ var YouTubePlayer = function (data) { self.videoId = data.id; self.videoLength = data.seconds; self.theYouTubeDevsNeedToFixThisShit = false; + self.whyDoesSetPlaybackQualityHaveARaceCondition = true; var wmode = USEROPTS.wmode_transparent ? "transparent" : "opaque"; self.player = new YT.Player("ytapiplayer", { videoId: data.id, @@ -34,6 +35,13 @@ var YouTubePlayer = function (data) { PLAYER.setVolume(VOLUME); }, onStateChange: function (ev) { + if (self.whyDoesSetPlaybackQualityHaveARaceCondition) { + self.whyDoesSetPlaybackQualityHaveARaceCondition = false; + + if (USEROPTS.default_quality) { + self.player.setPlaybackQuality(USEROPTS.default_quality); + } + } /** * Race conditions suck. @@ -73,6 +81,12 @@ var YouTubePlayer = function (data) { self.load = function (data) { if(self.player && self.player.loadVideoById) { self.player.loadVideoById(data.id, data.currentTime); + self.whyDoesSetPlaybackQualityHaveARaceCondition = true; + if (USEROPTS.default_quality) { + // Try to set it ahead of time, if that works + // If not, the onStateChange callback will try again anyways + self.player.setPlaybackQuality(USEROPTS.default_quality); + } self.videoId = data.id; self.videoLength = data.seconds; } From 414cbfdc5df3c857af15aefd496a87aafabbc60b Mon Sep 17 00:00:00 2001 From: Calvin Montgomery Date: Tue, 6 Jan 2015 10:54:14 -0500 Subject: [PATCH 04/29] Add more safeguards for socket errors --- lib/get-info.js | 24 ++++++++++++++---------- lib/io/ioserver.js | 10 +++++++++- lib/server.js | 14 ++++++++++++++ 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/lib/get-info.js b/lib/get-info.js index e016fe2c..186809be 100644 --- a/lib/get-info.js +++ b/lib/get-info.js @@ -34,6 +34,12 @@ const CONTENT_TYPES = { var urlRetrieve = function (transport, options, callback) { 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 = ""; res.setEncoding("utf-8"); res.on("data", function (chunk) { @@ -979,17 +985,15 @@ function vimeoWorkaround(id, cb) { } }; - http.get(options, function (res) { - res.setEncoding("utf-8"); - var buffer = ""; + urlRetrieve(http, options, function (status, buffer) { + if (status !== 200) { + setImmediate(function () { + cb({}); + }); + return; + } - res.on("data", function (data) { - buffer += data; - }); - - res.on("end", function () { - parse(buffer); - }); + parse(buffer); }); }; diff --git a/lib/io/ioserver.js b/lib/io/ioserver.js index 8d4308d7..4108a830 100644 --- a/lib/io/ioserver.js +++ b/lib/io/ioserver.js @@ -217,7 +217,15 @@ module.exports = { if (id in srv.servers) { io.attach(srv.servers[id]); } 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) { + Logger.errlog.log("clientError on " + id + " - " + err); + try { + socket.destroy(); + } catch (e) { + } + }); + io.attach(server); } bound[id] = null; diff --git a/lib/server.js b/lib/server.js index cca14e9b..85016ceb 100644 --- a/lib/server.js +++ b/lib/server.js @@ -85,8 +85,22 @@ var Server = function () { if (bind.https && Config.get("https.enabled")) { self.servers[id] = https.createServer(opts, self.express) .listen(bind.port, bind.ip); + self.servers[id].on("clientError", function (err, socket) { + Logger.errlog.log("clientError on " + id + " - " + err); + try { + socket.destroy(); + } catch (e) { + } + }); } else if (bind.http) { self.servers[id] = self.express.listen(bind.port, bind.ip); + self.servers[id].on("clientError", function (err, socket) { + Logger.errlog.log("clientError on " + id + " - " + err); + try { + socket.destroy(); + } catch (e) { + } + }); } }); From cf35c92391be4b325e492497ecb4bf38f9883238 Mon Sep 17 00:00:00 2001 From: Calvin Montgomery Date: Tue, 6 Jan 2015 10:58:15 -0500 Subject: [PATCH 05/29] Apparently this happens a lot, don't put it in the logfile --- lib/io/ioserver.js | 2 +- lib/server.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/io/ioserver.js b/lib/io/ioserver.js index 4108a830..f71217bf 100644 --- a/lib/io/ioserver.js +++ b/lib/io/ioserver.js @@ -219,7 +219,7 @@ module.exports = { } else { var server = require("http").createServer().listen(bind.port, bind.ip); server.on("clientError", function (err, socket) { - Logger.errlog.log("clientError on " + id + " - " + err); + console.error("clientError on " + id + " - " + err); try { socket.destroy(); } catch (e) { diff --git a/lib/server.js b/lib/server.js index 85016ceb..61968649 100644 --- a/lib/server.js +++ b/lib/server.js @@ -86,7 +86,7 @@ var Server = function () { self.servers[id] = https.createServer(opts, self.express) .listen(bind.port, bind.ip); self.servers[id].on("clientError", function (err, socket) { - Logger.errlog.log("clientError on " + id + " - " + err); + console.error("clientError on " + id + " - " + err); try { socket.destroy(); } catch (e) { @@ -95,7 +95,7 @@ var Server = function () { } else if (bind.http) { self.servers[id] = self.express.listen(bind.port, bind.ip); self.servers[id].on("clientError", function (err, socket) { - Logger.errlog.log("clientError on " + id + " - " + err); + console.error("clientError on " + id + " - " + err); try { socket.destroy(); } catch (e) { From df42a5e6a65a8485f93b91c9fbd7544462c0dd7e Mon Sep 17 00:00:00 2001 From: Calvin Montgomery Date: Tue, 6 Jan 2015 12:20:48 -0500 Subject: [PATCH 06/29] Replace XSS filter with sanitize-html --- lib/channel/customization.js | 6 +- lib/xss.js | 307 +++++++---------------------------- package.json | 1 + 3 files changed, 61 insertions(+), 253 deletions(-) diff --git a/lib/channel/customization.js b/lib/channel/customization.js index 627c9ae3..1ed7df8c 100644 --- a/lib/channel/customization.js +++ b/lib/channel/customization.js @@ -36,9 +36,11 @@ CustomizationModule.prototype.load = function (data) { if ("motd" in data) { this.motd = { - motd: data.motd.motd || "", - html: data.motd.html || "" + motd: data.motd.motd || "" }; + + this.motd.motd = XSS.sanitizeHTML(this.motd.motd); + this.motd.html = this.motd.motd.replace(/\n/g, "
"); } }; diff --git a/lib/xss.js b/lib/xss.js index 9b02ce2b..c6c0de2e 100644 --- a/lib/xss.js +++ b/lib/xss.js @@ -1,260 +1,62 @@ -/* - WARNING +var sanitizeHTML = require("sanitize-html"); - This file contains an XSS prevention module I wrote myself. It has not - been verified by any external agency, and due to the nature of XSS I cannot - guarantee that it will filter correctly. Feel free to send me bug reports - and I will do my best to fix them, but use at your own risk. +const ALLOWED_TAGS = [ + "button", + "center", + "details", + "font", + "h1", + "h2", + "img", + "marquee", // It pains me to do this, but a lot of people use it... + "section", + "span", + "summary" +]; -*/ - -/* Prototype for a basic XML tag parser */ -function TagParser(text) { - this.text = text; - this.i = 0; - this.tag = this.parse(); -} - -/* 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", +const ALLOWED_ATTRIBUTES = [ + "id", + "aria-hidden", + "border", + "class", + "color", + "data-dismiss", + "data-target", + "height", + "role", "style", - "script", - "textarea", "title", - "video", - "xml", - "xss" -].join("|"), "i"); + "valign", + "width" +]; -/* Nasty attributes. Anything starting with "on" is probably a javascript - callback, and I hope you see why formaction is a bad idea. -*/ -const badAttrs = new RegExp([ - "\\bon\\S*", - "\\bformaction", - "\\baction" -].join("|"), "i"); - -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 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; +var ATTRIBUTE_MAP = { + a: ["href", "name", "target"], + font: ["size"], + img: ["src"], + marquee: ["behavior", "behaviour", "direction", "scrollamount"], + table: ["cellpadding", "cellspacing"], + th: ["colspan", "rowspan"], + td: ["colspan", "rowspan"] } -/* 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) { str = str.replace(/&/g, "&") .replace(/ Date: Tue, 6 Jan 2015 13:00:36 -0500 Subject: [PATCH 07/29] XSS: Glob attributes data-*, aria-* --- lib/xss.js | 7 ++++--- package.json | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/xss.js b/lib/xss.js index c6c0de2e..1ab08576 100644 --- a/lib/xss.js +++ b/lib/xss.js @@ -1,5 +1,7 @@ var sanitizeHTML = require("sanitize-html"); +// These tags are allowed in addition to the defaults +// See https://github.com/punkave/sanitize-html const ALLOWED_TAGS = [ "button", "center", @@ -16,12 +18,11 @@ const ALLOWED_TAGS = [ const ALLOWED_ATTRIBUTES = [ "id", - "aria-hidden", + "aria-*", "border", "class", "color", - "data-dismiss", - "data-target", + "data-*", "height", "role", "style", diff --git a/package.json b/package.json index 10f927ab..62db02fd 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "nodemailer": "^1.2.0", "oauth": "^0.9.12", "q": "^1.0.1", - "sanitize-html": "^1.4.3", + "sanitize-html": "git://github.com/calzoneman/sanitize-html#5022eb6c", "serve-static": "^1.5.3", "socket.io": "^1.1.0", "yamljs": "^0.1.5" From 6e053ae7f45a17418ccd6da281028f2cc394ce1f Mon Sep 17 00:00:00 2001 From: Calvin Montgomery Date: Tue, 6 Jan 2015 13:05:31 -0500 Subject: [PATCH 08/29] deps: update sanitize-html --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 62db02fd..7b2dcca8 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "nodemailer": "^1.2.0", "oauth": "^0.9.12", "q": "^1.0.1", - "sanitize-html": "git://github.com/calzoneman/sanitize-html#5022eb6c", + "sanitize-html": "git://github.com/calzoneman/sanitize-html#9829c6d0", "serve-static": "^1.5.3", "socket.io": "^1.1.0", "yamljs": "^0.1.5" From aabff5d0cc3191075ec117cabd420724fda6de1e Mon Sep 17 00:00:00 2001 From: Calvin Montgomery Date: Tue, 6 Jan 2015 22:40:23 -0500 Subject: [PATCH 09/29] deps: update cytubefilters commit hash --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7b2dcca8..77e643ef 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "body-parser": "^1.6.5", "compression": "^1.2.0", "cookie-parser": "^1.3.2", - "cytubefilters": "git://github.com/calzoneman/cytubefilters#a5a99642", + "cytubefilters": "git://github.com/calzoneman/cytubefilters#89f56fee", "express": "^4.8.5", "express-minify": "0.0.11", "graceful-fs": "^3.0.5", From b56809138ca0cbfafbe3c3fc006a654fb46039bf Mon Sep 17 00:00:00 2001 From: Calvin Montgomery Date: Tue, 6 Jan 2015 22:40:23 -0500 Subject: [PATCH 10/29] deps: update cytubefilters commit hash --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b5934b85..0e4b280e 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "body-parser": "^1.6.5", "compression": "^1.2.0", "cookie-parser": "^1.3.2", - "cytubefilters": "git://github.com/calzoneman/cytubefilters#a5a99642", + "cytubefilters": "git://github.com/calzoneman/cytubefilters#89f56fee", "express": "^4.8.5", "express-minify": "0.0.11", "graceful-fs": "^3.0.5", From 5d843358d2b2a9899668e889fa841113fc5cccf9 Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 6 Jan 2015 22:55:14 -0500 Subject: [PATCH 11/29] site admins should be immune from kick/mute --- lib/channel/chat.js | 6 ++++-- lib/channel/kickban.js | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/channel/chat.js b/lib/channel/chat.js index de63b83c..1e23debd 100644 --- a/lib/channel/chat.js +++ b/lib/channel/chat.js @@ -482,7 +482,8 @@ ChatModule.prototype.handleCmdMute = function (user, msg, meta) { 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", { msg: "/mute failed - " + target.getName() + " has equal or higher rank " + "than you." @@ -531,7 +532,8 @@ ChatModule.prototype.handleCmdSMute = function (user, msg, meta) { 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", { msg: "/smute failed - " + target.getName() + " has equal or higher rank " + "than you." diff --git a/lib/channel/kickban.js b/lib/channel/kickban.js index 233e02f3..9190b68d 100644 --- a/lib/channel/kickban.js +++ b/lib/channel/kickban.js @@ -175,7 +175,8 @@ KickBanModule.prototype.handleCmdKick = function (user, msg, meta) { 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", { msg: "You do not have permission to kick " + target.getName() }); From 21756d7cb2b8cca453251f84afed95afc03bdc85 Mon Sep 17 00:00:00 2001 From: Calvin Montgomery Date: Wed, 7 Jan 2015 10:47:46 -0500 Subject: [PATCH 12/29] sanitize-html: replace github link with npm The change I made to sanitize-html was merged and published to npm, so there's no reason to depend on my fork anymore. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 77e643ef..d40c5286 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "nodemailer": "^1.2.0", "oauth": "^0.9.12", "q": "^1.0.1", - "sanitize-html": "git://github.com/calzoneman/sanitize-html#9829c6d0", + "sanitize-html": "^1.5.0", "serve-static": "^1.5.3", "socket.io": "^1.1.0", "yamljs": "^0.1.5" From c1ef0848cdc49f79427521bc53433470ba183ee9 Mon Sep 17 00:00:00 2001 From: Calvin Montgomery Date: Wed, 7 Jan 2015 15:58:36 -0500 Subject: [PATCH 13/29] Add CSS classes for muted users (#426) - If a user is muted, the `userlist_muted` class is added to the corresponding `.userlist_item`. - If the user is shadowmuted, the `userlist_smuted` and `userlist_muted` classes are added. - If the user is AFK, the `userlist_afk` class is added. --- www/js/util.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/www/js/util.js b/www/js/util.js index aadb8e83..450cb9b1 100644 --- a/www/js/util.js +++ b/www/js/util.js @@ -86,6 +86,24 @@ function formatUserlistItem(div) { name.addClass(getNameColor(data.rank)); 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; name.mouseenter(function(ev) { if (profile) From 032f600746981fb247a907240f05df7bca163345 Mon Sep 17 00:00:00 2001 From: Calvin Montgomery Date: Thu, 8 Jan 2015 08:48:00 -0500 Subject: [PATCH 14/29] Kick/Mute immunity should only be if globalRank is strictly greater --- lib/channel/chat.js | 2 +- lib/channel/kickban.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/channel/chat.js b/lib/channel/chat.js index 1e23debd..9d2d9a0a 100644 --- a/lib/channel/chat.js +++ b/lib/channel/chat.js @@ -483,7 +483,7 @@ ChatModule.prototype.handleCmdMute = function (user, msg, meta) { } if (target.account.effectiveRank >= user.account.effectiveRank - || target.account.globalRank >= user.account.globalRank) { + || target.account.globalRank > user.account.globalRank) { user.socket.emit("errorMsg", { msg: "/mute failed - " + target.getName() + " has equal or higher rank " + "than you." diff --git a/lib/channel/kickban.js b/lib/channel/kickban.js index 9190b68d..4d83e01e 100644 --- a/lib/channel/kickban.js +++ b/lib/channel/kickban.js @@ -176,7 +176,7 @@ KickBanModule.prototype.handleCmdKick = function (user, msg, meta) { } if (target.account.effectiveRank >= user.account.effectiveRank - || target.account.globalRank >= user.account.globalRank) { + || target.account.globalRank > user.account.globalRank) { return user.socket.emit("errorMsg", { msg: "You do not have permission to kick " + target.getName() }); From 4135ec0bf84eaa02ee5a82d9c4d49df49d486b45 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 8 Jan 2015 09:58:44 -0500 Subject: [PATCH 15/29] Kick/Mute immunity should only be if globalRank is strictly greater --- lib/channel/chat.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/channel/chat.js b/lib/channel/chat.js index 9d2d9a0a..24ebc299 100644 --- a/lib/channel/chat.js +++ b/lib/channel/chat.js @@ -533,7 +533,7 @@ ChatModule.prototype.handleCmdSMute = function (user, msg, meta) { } if (target.account.effectiveRank >= user.account.effectiveRank - || target.account.globalRank >= user.account.globalRank) { + || target.account.globalRank > user.account.globalRank) { user.socket.emit("errorMsg", { msg: "/smute failed - " + target.getName() + " has equal or higher rank " + "than you." From 8630c5972c04e2e50bb8b9da8678ad11d92ab4e5 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Thu, 8 Jan 2015 17:57:44 -0600 Subject: [PATCH 16/29] deps: upgrade socket.io to 1.2.1 --- lib/server.js | 2 +- package.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/server.js b/lib/server.js index 61968649..a6866b2e 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1,4 +1,4 @@ -const VERSION = "3.6.1"; +const VERSION = "3.6.2"; var singleton = null; var Config = require("./config"); diff --git a/package.json b/package.json index 0e4b280e..a9ffe93c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Calvin Montgomery", "name": "CyTube", "description": "Online media synchronizer and chat", - "version": "3.6.1", + "version": "3.6.2", "repository": { "url": "http://github.com/calzoneman/sync" }, @@ -23,7 +23,7 @@ "oauth": "^0.9.12", "q": "^1.0.1", "serve-static": "^1.5.3", - "socket.io": "^1.1.0", + "socket.io": "^1.2.1", "yamljs": "^0.1.5" } } From 654573af681b9c0b5afdab5fd4502322259f50f3 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Thu, 8 Jan 2015 20:06:41 -0600 Subject: [PATCH 17/29] Migrate old MOTDs and don't replace \n with
after --- lib/channel/channel.js | 5 +---- lib/channel/customization.js | 27 +++++++++++---------------- www/js/callbacks.js | 9 ++++----- www/js/data.js | 1 - www/js/util.js | 2 +- 5 files changed, 17 insertions(+), 27 deletions(-) diff --git a/lib/channel/channel.js b/lib/channel/channel.js index 843b4261..da9b6028 100644 --- a/lib/channel/channel.js +++ b/lib/channel/channel.js @@ -163,10 +163,7 @@ Channel.prototype.loadState = function () { var errorLoad = function (msg) { if (self.modules.customization) { self.modules.customization.load({ - motd: { - motd: msg, - html: msg - } + motd: msg }); } diff --git a/lib/channel/customization.js b/lib/channel/customization.js index 1ed7df8c..5bbfdd97 100644 --- a/lib/channel/customization.js +++ b/lib/channel/customization.js @@ -17,10 +17,7 @@ function CustomizationModule(channel) { ChannelModule.apply(this, arguments); this.css = ""; this.js = ""; - this.motd = { - motd: "", - html: "" - }; + this.motd = ""; } CustomizationModule.prototype = Object.create(ChannelModule.prototype); @@ -35,12 +32,15 @@ CustomizationModule.prototype.load = function (data) { } if ("motd" in data) { - this.motd = { - motd: data.motd.motd || "" - }; - - this.motd.motd = XSS.sanitizeHTML(this.motd.motd); - this.motd.html = this.motd.motd.replace(/\n/g, "
"); + if (typeof data.motd === "object" && data.motd.motd) { + // Old style MOTD, convert to new + this.motd = XSS.sanitizeHTML(data.motd.motd).replace( + /\n/g, "
\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); + } } }; @@ -51,12 +51,7 @@ CustomizationModule.prototype.save = function (data) { }; CustomizationModule.prototype.setMotd = function (motd) { - motd = XSS.sanitizeHTML(motd); - var html = motd.replace(/\n/g, "
"); - this.motd = { - motd: motd, - html: html - }; + this.motd = XSS.sanitizeHTML(motd); this.sendMotd(this.channel.users); }; diff --git a/www/js/callbacks.js b/www/js/callbacks.js index ef52d164..f3f7f4fa 100644 --- a/www/js/callbacks.js +++ b/www/js/callbacks.js @@ -175,11 +175,10 @@ Callbacks = { } }, - setMotd: function(data) { - CHANNEL.motd = data.html; - CHANNEL.motd_text = data.motd; - $("#motd").html(CHANNEL.motd); - $("#cs-motdtext").val(CHANNEL.motd_text); + setMotd: function(motd) { + CHANNEL.motd = motd; + $("#motd").html(motd); + $("#cs-motdtext").val(motd); if (data.motd != "") { $("#motdwrap").show(); $("#motd").show(); diff --git a/www/js/data.js b/www/js/data.js index 6fef78d5..7f29ddd0 100644 --- a/www/js/data.js +++ b/www/js/data.js @@ -19,7 +19,6 @@ var CHANNEL = { css: "", js: "", motd: "", - motd_text: "", name: false, usercount: 0, emotes: [] diff --git a/www/js/util.js b/www/js/util.js index aadb8e83..418721e1 100644 --- a/www/js/util.js +++ b/www/js/util.js @@ -882,7 +882,7 @@ function handleModPermissions() { })(); $("#cs-csstext").val(CHANNEL.css); $("#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-permedit']", CLIENT.rank >= 3); setParentVisible("a[href='#cs-banlist']", hasPermission("ban")); From 1c3a669279000490a7286fec1ecc331f161f5133 Mon Sep 17 00:00:00 2001 From: Calvin Montgomery Date: Tue, 6 Jan 2015 12:20:48 -0500 Subject: [PATCH 18/29] Replace XSS filter with sanitize-html --- lib/channel/customization.js | 6 +- lib/xss.js | 307 +++++++---------------------------- package.json | 1 + 3 files changed, 61 insertions(+), 253 deletions(-) diff --git a/lib/channel/customization.js b/lib/channel/customization.js index 627c9ae3..1ed7df8c 100644 --- a/lib/channel/customization.js +++ b/lib/channel/customization.js @@ -36,9 +36,11 @@ CustomizationModule.prototype.load = function (data) { if ("motd" in data) { this.motd = { - motd: data.motd.motd || "", - html: data.motd.html || "" + motd: data.motd.motd || "" }; + + this.motd.motd = XSS.sanitizeHTML(this.motd.motd); + this.motd.html = this.motd.motd.replace(/\n/g, "
"); } }; diff --git a/lib/xss.js b/lib/xss.js index 9b02ce2b..c6c0de2e 100644 --- a/lib/xss.js +++ b/lib/xss.js @@ -1,260 +1,62 @@ -/* - WARNING +var sanitizeHTML = require("sanitize-html"); - This file contains an XSS prevention module I wrote myself. It has not - been verified by any external agency, and due to the nature of XSS I cannot - guarantee that it will filter correctly. Feel free to send me bug reports - and I will do my best to fix them, but use at your own risk. +const ALLOWED_TAGS = [ + "button", + "center", + "details", + "font", + "h1", + "h2", + "img", + "marquee", // It pains me to do this, but a lot of people use it... + "section", + "span", + "summary" +]; -*/ - -/* Prototype for a basic XML tag parser */ -function TagParser(text) { - this.text = text; - this.i = 0; - this.tag = this.parse(); -} - -/* 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", +const ALLOWED_ATTRIBUTES = [ + "id", + "aria-hidden", + "border", + "class", + "color", + "data-dismiss", + "data-target", + "height", + "role", "style", - "script", - "textarea", "title", - "video", - "xml", - "xss" -].join("|"), "i"); + "valign", + "width" +]; -/* Nasty attributes. Anything starting with "on" is probably a javascript - callback, and I hope you see why formaction is a bad idea. -*/ -const badAttrs = new RegExp([ - "\\bon\\S*", - "\\bformaction", - "\\baction" -].join("|"), "i"); - -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 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; +var ATTRIBUTE_MAP = { + a: ["href", "name", "target"], + font: ["size"], + img: ["src"], + marquee: ["behavior", "behaviour", "direction", "scrollamount"], + table: ["cellpadding", "cellspacing"], + th: ["colspan", "rowspan"], + td: ["colspan", "rowspan"] } -/* 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) { str = str.replace(/&/g, "&") .replace(/ Date: Tue, 6 Jan 2015 13:00:36 -0500 Subject: [PATCH 19/29] XSS: Glob attributes data-*, aria-* --- lib/xss.js | 7 ++++--- package.json | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/xss.js b/lib/xss.js index c6c0de2e..1ab08576 100644 --- a/lib/xss.js +++ b/lib/xss.js @@ -1,5 +1,7 @@ var sanitizeHTML = require("sanitize-html"); +// These tags are allowed in addition to the defaults +// See https://github.com/punkave/sanitize-html const ALLOWED_TAGS = [ "button", "center", @@ -16,12 +18,11 @@ const ALLOWED_TAGS = [ const ALLOWED_ATTRIBUTES = [ "id", - "aria-hidden", + "aria-*", "border", "class", "color", - "data-dismiss", - "data-target", + "data-*", "height", "role", "style", diff --git a/package.json b/package.json index c5399365..b39eec34 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "nodemailer": "^1.2.0", "oauth": "^0.9.12", "q": "^1.0.1", - "sanitize-html": "^1.4.3", + "sanitize-html": "git://github.com/calzoneman/sanitize-html#5022eb6c", "serve-static": "^1.5.3", "socket.io": "^1.2.1", "yamljs": "^0.1.5" From eca80143695028fc793411a9783d8ac4b5a24afd Mon Sep 17 00:00:00 2001 From: Calvin Montgomery Date: Tue, 6 Jan 2015 13:05:31 -0500 Subject: [PATCH 20/29] deps: update sanitize-html --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b39eec34..97099a7c 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "nodemailer": "^1.2.0", "oauth": "^0.9.12", "q": "^1.0.1", - "sanitize-html": "git://github.com/calzoneman/sanitize-html#5022eb6c", + "sanitize-html": "git://github.com/calzoneman/sanitize-html#9829c6d0", "serve-static": "^1.5.3", "socket.io": "^1.2.1", "yamljs": "^0.1.5" From 56d6eb80262187f6f5bc6d24d46a0fb13ad533a4 Mon Sep 17 00:00:00 2001 From: Calvin Montgomery Date: Wed, 7 Jan 2015 10:47:46 -0500 Subject: [PATCH 21/29] sanitize-html: replace github link with npm The change I made to sanitize-html was merged and published to npm, so there's no reason to depend on my fork anymore. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 97099a7c..f8cc60e0 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "nodemailer": "^1.2.0", "oauth": "^0.9.12", "q": "^1.0.1", - "sanitize-html": "git://github.com/calzoneman/sanitize-html#9829c6d0", + "sanitize-html": "^1.5.0", "serve-static": "^1.5.3", "socket.io": "^1.2.1", "yamljs": "^0.1.5" From 80c4c90bcfe01b8129fb91a3a78b70dbd2c1ad13 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Thu, 8 Jan 2015 20:06:41 -0600 Subject: [PATCH 22/29] Migrate old MOTDs and don't replace \n with
after --- lib/channel/channel.js | 5 +---- lib/channel/customization.js | 27 +++++++++++---------------- www/js/callbacks.js | 9 ++++----- www/js/data.js | 1 - www/js/util.js | 2 +- 5 files changed, 17 insertions(+), 27 deletions(-) diff --git a/lib/channel/channel.js b/lib/channel/channel.js index 843b4261..da9b6028 100644 --- a/lib/channel/channel.js +++ b/lib/channel/channel.js @@ -163,10 +163,7 @@ Channel.prototype.loadState = function () { var errorLoad = function (msg) { if (self.modules.customization) { self.modules.customization.load({ - motd: { - motd: msg, - html: msg - } + motd: msg }); } diff --git a/lib/channel/customization.js b/lib/channel/customization.js index 1ed7df8c..5bbfdd97 100644 --- a/lib/channel/customization.js +++ b/lib/channel/customization.js @@ -17,10 +17,7 @@ function CustomizationModule(channel) { ChannelModule.apply(this, arguments); this.css = ""; this.js = ""; - this.motd = { - motd: "", - html: "" - }; + this.motd = ""; } CustomizationModule.prototype = Object.create(ChannelModule.prototype); @@ -35,12 +32,15 @@ CustomizationModule.prototype.load = function (data) { } if ("motd" in data) { - this.motd = { - motd: data.motd.motd || "" - }; - - this.motd.motd = XSS.sanitizeHTML(this.motd.motd); - this.motd.html = this.motd.motd.replace(/\n/g, "
"); + if (typeof data.motd === "object" && data.motd.motd) { + // Old style MOTD, convert to new + this.motd = XSS.sanitizeHTML(data.motd.motd).replace( + /\n/g, "
\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); + } } }; @@ -51,12 +51,7 @@ CustomizationModule.prototype.save = function (data) { }; CustomizationModule.prototype.setMotd = function (motd) { - motd = XSS.sanitizeHTML(motd); - var html = motd.replace(/\n/g, "
"); - this.motd = { - motd: motd, - html: html - }; + this.motd = XSS.sanitizeHTML(motd); this.sendMotd(this.channel.users); }; diff --git a/www/js/callbacks.js b/www/js/callbacks.js index ef52d164..f3f7f4fa 100644 --- a/www/js/callbacks.js +++ b/www/js/callbacks.js @@ -175,11 +175,10 @@ Callbacks = { } }, - setMotd: function(data) { - CHANNEL.motd = data.html; - CHANNEL.motd_text = data.motd; - $("#motd").html(CHANNEL.motd); - $("#cs-motdtext").val(CHANNEL.motd_text); + setMotd: function(motd) { + CHANNEL.motd = motd; + $("#motd").html(motd); + $("#cs-motdtext").val(motd); if (data.motd != "") { $("#motdwrap").show(); $("#motd").show(); diff --git a/www/js/data.js b/www/js/data.js index 6fef78d5..7f29ddd0 100644 --- a/www/js/data.js +++ b/www/js/data.js @@ -19,7 +19,6 @@ var CHANNEL = { css: "", js: "", motd: "", - motd_text: "", name: false, usercount: 0, emotes: [] diff --git a/www/js/util.js b/www/js/util.js index 450cb9b1..2f0c9119 100644 --- a/www/js/util.js +++ b/www/js/util.js @@ -900,7 +900,7 @@ function handleModPermissions() { })(); $("#cs-csstext").val(CHANNEL.css); $("#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-permedit']", CLIENT.rank >= 3); setParentVisible("a[href='#cs-banlist']", hasPermission("ban")); From 139825168f81ee0a0082a397800546cbbf8533bc Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sun, 11 Jan 2015 12:10:09 -0600 Subject: [PATCH 23/29] Fix for private, but embeddable soundcloud tracks --- lib/database.js | 3 ++- lib/database/channels.js | 3 ++- lib/get-info.js | 6 +++++- lib/media.js | 3 ++- www/js/player.js | 8 +++++--- 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/lib/database.js b/lib/database.js index b77b68f8..ac4e0678 100644 --- a/lib/database.js +++ b/lib/database.js @@ -409,7 +409,8 @@ module.exports.saveUserPlaylist = function (pl, username, plname, callback) { type: pl[i].media.type, meta: { 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; diff --git a/lib/database/channels.js b/lib/database/channels.js index dd68f083..30e99b31 100644 --- a/lib/database/channels.js +++ b/lib/database/channels.js @@ -426,7 +426,8 @@ module.exports = { var meta = JSON.stringify({ bitrate: media.meta.bitrate, - codec: media.meta.codec + codec: media.meta.codec, + scuri: media.meta.scuri }); db.query("INSERT INTO `channel_libraries` " + diff --git a/lib/get-info.js b/lib/get-info.js index 186809be..504427d9 100644 --- a/lib/get-info.js +++ b/lib/get-info.js @@ -552,7 +552,11 @@ var Getters = { data = JSON.parse(data); var seconds = data.duration / 1000; 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); } catch(e) { callback(e, null); diff --git a/lib/media.js b/lib/media.js index 442481c1..4faec19e 100644 --- a/lib/media.js +++ b/lib/media.js @@ -36,7 +36,8 @@ Media.prototype = { gpdirect: this.meta.gpdirect, restricted: this.meta.restricted, codec: this.meta.codec, - bitrate: this.meta.bitrate + bitrate: this.meta.bitrate, + scuri: this.meta.scuri } }; }, diff --git a/www/js/player.js b/www/js/player.js index 7431bb59..9ab9fe9a 100644 --- a/www/js/player.js +++ b/www/js/player.js @@ -441,6 +441,7 @@ var SoundcloudPlayer = function (data) { // Go figure self.soundcloudIsSeriouslyFuckingBroken = VOLUME; self.videoId = data.id; + self.scuri = data.meta.scuri || self.videoId; self.videoLength = data.seconds; waitUntilDefined(window, "SC", function () { unfixSoundcloudShit(); @@ -449,7 +450,7 @@ var SoundcloudPlayer = function (data) { iframe.appendTo($("#ytapiplayer")); 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("border", "none"); @@ -469,7 +470,7 @@ var SoundcloudPlayer = function (data) { self.player = SC.Widget("scplayer"); 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 () { PLAYER.paused = true; @@ -500,9 +501,10 @@ var SoundcloudPlayer = function (data) { self.load = function (data) { self.videoId = data.id; + self.scuri = data.meta.scuri || self.videoId; self.videoLength = data.seconds; 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 () { self.setVolume(VOLUME); self.player.unbind(SC.Widget.Events.PLAY_PROGRESS); From 4f3adef1d3e0af2b88c3bda166e7e6cc7ff28aab Mon Sep 17 00:00:00 2001 From: calzoneman Date: Fri, 16 Jan 2015 19:27:41 -0600 Subject: [PATCH 24/29] Revert to git based sanitize-html for package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f8cc60e0..3e69a490 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "nodemailer": "^1.2.0", "oauth": "^0.9.12", "q": "^1.0.1", - "sanitize-html": "^1.5.0", + "sanitize-html": "git://github.com/calzoneman/sanitize-html", "serve-static": "^1.5.3", "socket.io": "^1.2.1", "yamljs": "^0.1.5" From e76fd7b1c40004574c15a3424e2935ffee55f3b9 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Fri, 16 Jan 2015 19:35:26 -0600 Subject: [PATCH 25/29] Fix client motd issue --- www/js/callbacks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/js/callbacks.js b/www/js/callbacks.js index f3f7f4fa..25e0a804 100644 --- a/www/js/callbacks.js +++ b/www/js/callbacks.js @@ -179,7 +179,7 @@ Callbacks = { CHANNEL.motd = motd; $("#motd").html(motd); $("#cs-motdtext").val(motd); - if (data.motd != "") { + if (motd != "") { $("#motdwrap").show(); $("#motd").show(); $("#togglemotd").find(".glyphicon-plus") From 9fc1cbd81c2aeae84dbf676d56f742fac31be659 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Mon, 19 Jan 2015 01:26:46 -0600 Subject: [PATCH 26/29] Whitelist tags for filters --- lib/xss.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/xss.js b/lib/xss.js index 1ab08576..b8a379e9 100644 --- a/lib/xss.js +++ b/lib/xss.js @@ -11,6 +11,7 @@ const ALLOWED_TAGS = [ "h2", "img", "marquee", // It pains me to do this, but a lot of people use it... + "s", "section", "span", "summary" From 7d2015620a85ce21189f712909416ce79ff535eb Mon Sep 17 00:00:00 2001 From: calzoneman Date: Mon, 19 Jan 2015 17:43:22 -0600 Subject: [PATCH 27/29] socket.io: upgrade to 1.3 --- lib/server.js | 2 +- package.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/server.js b/lib/server.js index a6866b2e..c6bf196f 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1,4 +1,4 @@ -const VERSION = "3.6.2"; +const VERSION = "3.6.3"; var singleton = null; var Config = require("./config"); diff --git a/package.json b/package.json index 3e69a490..c93229b7 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Calvin Montgomery", "name": "CyTube", "description": "Online media synchronizer and chat", - "version": "3.6.2", + "version": "3.6.3", "repository": { "url": "http://github.com/calzoneman/sync" }, @@ -24,7 +24,7 @@ "q": "^1.0.1", "sanitize-html": "git://github.com/calzoneman/sanitize-html", "serve-static": "^1.5.3", - "socket.io": "^1.2.1", + "socket.io": "^1.3.2", "yamljs": "^0.1.5" } } From 5cde74cbd40cbf5ca4d114e394249a6ad1b9bb41 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Thu, 22 Jan 2015 16:53:36 -0600 Subject: [PATCH 28/29] Fix potential cause for playlist timer problem --- lib/channel/playlist.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/channel/playlist.js b/lib/channel/playlist.js index 30ec0944..f414576a 100644 --- a/lib/channel/playlist.js +++ b/lib/channel/playlist.js @@ -1049,7 +1049,12 @@ PlaylistModule.prototype.startPlayback = function (time) { self.channel.notifyModules("onMediaChange", [self.current.media]); /* 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._leadInterval = setInterval(function() { self._leadLoop(); From 5a95bacee400c2b616d6510b01ac22cf81e404f8 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Thu, 22 Jan 2015 23:34:39 -0600 Subject: [PATCH 29/29] Fix youtube...again --- www/js/player.js | 1 - 1 file changed, 1 deletion(-) diff --git a/www/js/player.js b/www/js/player.js index 9ab9fe9a..a35588d6 100644 --- a/www/js/player.js +++ b/www/js/player.js @@ -54,7 +54,6 @@ var YouTubePlayer = function (data) { if (USEROPTS.default_quality) { self.player.setPlaybackQuality(USEROPTS.default_quality); } - PLAYER.seek(0.000001); PLAYER.pause(); self.theYouTubeDevsNeedToFixThisShit = false; }