diff options
| -rw-r--r-- | build/server.js | 25 | ||||
| -rw-r--r-- | default-config.json | 4 | ||||
| -rw-r--r-- | res/client.js | 84 | ||||
| -rw-r--r-- | res/langs/en.json | 2 | ||||
| -rw-r--r-- | res/langs/ru.json | 2 | ||||
| -rw-r--r-- | src/client/Main.hx | 25 | ||||
| -rw-r--r-- | src/client/Player.hx | 15 | ||||
| -rw-r--r-- | src/server/Main.hx | 20 |
8 files changed, 127 insertions, 50 deletions
diff --git a/build/server.js b/build/server.js index d6a0c65..52d878d 100644 --- a/build/server.js +++ b/build/server.js @@ -637,6 +637,7 @@ var server_Main = function(port,wsPort) { port = 4200; } this.loadedClientsCount = 0; + this.htmlChars = new EReg("[&^<>'\"]",""); this.messages = []; this.videoTimer = new server_VideoTimer(); this.videoList = []; @@ -797,6 +798,7 @@ server_Main.prototype = { switch(data.type) { case "AddVideo": var item = data.addVideo.item; + item.author = client.name; var localOrigin = "" + this.localIp + ":" + this.port; if(item.url.indexOf(localOrigin) != -1) { item.url = StringTools.replace(item.url,localOrigin,"" + this.globalIp + ":" + this.port); @@ -837,11 +839,11 @@ server_Main.prototype = { break; case "Login": var name = data.login.clientName; - if(name.length == 0 || name.length > this.config.maxLoginLength || ClientTools.getByName(this.clients,name) != null) { + if(this.badNickName(name) || name.length > this.config.maxLoginLength || ClientTools.getByName(this.clients,name) != null) { this.send(client,{ type : "LoginError"}); return; } - client.name = data.login.clientName; + client.name = name; client.setGroupFlag(ClientGroup.User,true); this.send(client,{ type : data.type, login : { isUnknownClient : true, clientName : client.name, clients : this.clientList()}}); this.sendClientList(); @@ -897,15 +899,16 @@ server_Main.prototype = { return; } var url = data.removeVideo.url; - if(this.videoList[0].url == url) { + var isFirst = this.videoList[0].url == url; + if(isFirst) { this.videoTimer.stop(); - if(this.videoList.length > 0) { - this.restartWaitTimer(); - } } HxOverrides.remove(this.videoList,Lambda.find(this.videoList,function(item1) { return item1.url == url; })); + if(isFirst && this.videoList.length > 0) { + this.restartWaitTimer(); + } this.broadcast(data); break; case "Rewind": @@ -993,6 +996,15 @@ server_Main.prototype = { client.ws.send(json,null); } } + ,badNickName: function(name) { + if(name.length == 0) { + return true; + } + if(this.htmlChars.match(name)) { + return true; + } + return false; + } ,restartWaitTimer: function() { if(this.waitVideoStart != null) { this.waitVideoStart.stop(); @@ -1153,6 +1165,7 @@ function $getIterator(o) { if( o instanceof Array ) return HxOverrides.iter(o); function $bind(o,m) { if( m == null ) return null; if( m.__id__ == null ) m.__id__ = $global.$haxeUID++; var f; if( o.hx__closures__ == null ) o.hx__closures__ = {}; else f = o.hx__closures__[m.__id__]; if( f == null ) { f = m.bind(o); o.hx__closures__[m.__id__] = f; } return f; } $global.$haxeUID |= 0; var __map_reserved = {}; +if( String.fromCodePoint == null ) String.fromCodePoint = function(c) { return c < 0x10000 ? String.fromCharCode(c) : String.fromCharCode((c>>10)+0xD7C0)+String.fromCharCode((c&0x3FF)+0xDC00); } String.__name__ = true; Array.__name__ = true; Object.defineProperty(js__$Boot_HaxeError.prototype,"message",{ get : function() { diff --git a/default-config.json b/default-config.json index efbcf30..ec00b60 100644 --- a/default-config.json +++ b/default-config.json @@ -10,8 +10,8 @@ ], "filters": [ { - "name": "image", "regex": "(http|https)(:\\/\\/.*\\.)(png|jpg|gif|jpeg)", "flags": "g", - "replace": "<a href='$1$2$3' target='_blank'><img src='$1$2$3' style='max-width:200px; max-height:200px' /></a>" + "name": "image", "regex": "(http|https)(:\\/\\/.*\\.)(png|jpg|gif|jpeg)([^ ,]*)", "flags": "g", + "replace": "<a href='$1$2$3' target='_blank'><img src='$1$2$3$4' style='max-width:200px; max-height:200px' /></a>" } ] } diff --git a/res/client.js b/res/client.js index 35e568a..13a9107 100644 --- a/res/client.js +++ b/res/client.js @@ -280,6 +280,52 @@ Std.parseInt = function(x) { }; var StringTools = function() { }; StringTools.__name__ = true; +StringTools.htmlEscape = function(s,quotes) { + var buf_b = ""; + var _g_offset = 0; + var _g_s = s; + while(_g_offset < _g_s.length) { + var s1 = _g_s; + var index = _g_offset++; + var c = s1.charCodeAt(index); + if(c >= 55296 && c <= 56319) { + c = c - 55232 << 10 | s1.charCodeAt(index + 1) & 1023; + } + var c1 = c; + if(c1 >= 65536) { + ++_g_offset; + } + var code = c1; + switch(code) { + case 34: + if(quotes) { + buf_b += """; + } else { + buf_b += String.fromCodePoint(code); + } + break; + case 38: + buf_b += "&"; + break; + case 39: + if(quotes) { + buf_b += "'"; + } else { + buf_b += String.fromCodePoint(code); + } + break; + case 60: + buf_b += "<"; + break; + case 62: + buf_b += ">"; + break; + default: + buf_b += String.fromCodePoint(code); + } + } + return buf_b; +}; StringTools.startsWith = function(s,start) { if(s.length >= start.length) { return s.lastIndexOf(start,0) == 0; @@ -752,7 +798,7 @@ client_Main.prototype = { this.player.addVideoItem(data.addVideo.item,data.addVideo.atEnd); break; case "ClearChat": - window.document.querySelector("#messagebuffer").innerHTML = ""; + this.clearChat(); break; case "ClearPlaylist": this.player.clearItems(); @@ -868,7 +914,7 @@ client_Main.prototype = { if(guestName.value.length > 0) { this.send({ type : "Login", login : { clientName : guestName.value}}); } - window.document.querySelector("#messagebuffer").innerHTML = ""; + this.clearChat(); this.serverMessage(1); var _g = 0; var _g1 = connected.history; @@ -907,7 +953,7 @@ client_Main.prototype = { form.focus(); return; }; - smilesWrap.innerHTML = ""; + smilesWrap.textContent = ""; var _g4 = 0; var _g5 = config.emotes; while(_g4 < _g5.length) { @@ -963,19 +1009,19 @@ client_Main.prototype = { switch(type) { case 1: div.className = "server-msg-reconnect"; - div.innerHTML = Lang.get("msgConnected"); + div.textContent = Lang.get("msgConnected"); break; case 2: div.className = "server-msg-disconnect"; - div.innerHTML = Lang.get("msgDisconnected"); + div.textContent = Lang.get("msgDisconnected"); break; case 3: div.className = "server-whisper"; - div.innerHTML = time + text + " " + Lang.get("entered"); + div.textContent = time + text + " " + Lang.get("entered"); break; case 4: div.className = "server-whisper"; - div.innerHTML = time + text; + div.textContent = time + text; break; default: } @@ -983,7 +1029,7 @@ client_Main.prototype = { msgBuf.scrollTop = msgBuf.scrollHeight; } ,updateUserList: function() { - window.document.querySelector("#usercount").innerHTML = this.clients.length + " " + Lang.get("online"); + window.document.querySelector("#usercount").textContent = this.clients.length + " " + Lang.get("online"); window.document.title = this.getPageTitle(); var list_b = ""; var _g = 0; @@ -1002,6 +1048,9 @@ client_Main.prototype = { ,getPageTitle: function() { return "" + this.pageTitle + " (" + this.clients.length + ")"; } + ,clearChat: function() { + window.document.querySelector("#messagebuffer").textContent = ""; + } ,addMessage: function(name,text,time) { var _gthis = this; var msgBuf = window.document.querySelector("#messagebuffer"); @@ -1012,10 +1061,10 @@ client_Main.prototype = { if(time == null) { time = "[" + new Date().toTimeString().split(" ")[0] + "] "; } - tstamp.innerHTML = time; + tstamp.textContent = time; var nameDiv = window.document.createElement("strong"); nameDiv.className = "username"; - nameDiv.innerHTML = name + ": "; + nameDiv.textContent = name + ": "; var textDiv = window.document.createElement("span"); if(StringTools.startsWith(text,"/")) { if(name == this.personal.name) { @@ -1030,6 +1079,7 @@ client_Main.prototype = { text = text.replace(filter.regex.r,filter.replace); } } + text = StringTools.htmlEscape(text); textDiv.innerHTML = text; var isInChatEnd = msgBuf.scrollHeight - msgBuf.scrollTop == msgBuf.clientHeight; userDiv.appendChild(tstamp); @@ -1150,14 +1200,14 @@ client_Player.prototype = { _gthis.main.send({ type : "Play", play : { time : _gthis.video.currentTime}}); return; }; - this.player.innerHTML = ""; + this.player.textContent = ""; this.player.appendChild(this.video); - window.document.querySelector("#currenttitle").innerHTML = item.title; + window.document.querySelector("#currenttitle").textContent = item.title; } ,addVideoItem: function(item,atEnd) { var _gthis = this; this.items.push(item); - var itemEl = this.nodeFromString("<li class=\"queue_entry pluid-0 queue_temp queue_active\" title=\"" + Lang.get("addedBy") + ": " + item.author + "\">\n\t\t\t\t<a class=\"qe_title\" href=\"" + item.url + "\" target=\"_blank\">" + item.title + "</a>\n\t\t\t\t<span class=\"qe_time\">" + this.duration(item.duration) + "</span>\n\t\t\t\t<div class=\"qe_clear\"></div>\n\t\t\t\t<div class=\"btn-group\" style=\"display: inline-block;\">\n\t\t\t\t\t<button class=\"btn btn-xs btn-default qbtn-play\">\n\t\t\t\t\t\t<span class=\"glyphicon glyphicon-play\"></span>" + Lang.get("play") + "\n\t\t\t\t\t</button>\n\t\t\t\t\t<button class=\"btn btn-xs btn-default qbtn-next\">\n\t\t\t\t\t\t<span class=\"glyphicon glyphicon-share-alt\"></span>" + Lang.get("skip") + "\n\t\t\t\t\t</button>\n\t\t\t\t\t<button class=\"btn btn-xs btn-default qbtn-tmp\">\n\t\t\t\t\t\t<span class=\"glyphicon glyphicon-flag\"></span>" + Lang.get("makePermanent") + "\n\t\t\t\t\t</button>\n\t\t\t\t\t<button class=\"btn btn-xs btn-default qbtn-delete\" id=\"btn-delete\">\n\t\t\t\t\t\t<span class=\"glyphicon glyphicon-trash\"></span>" + Lang.get("delete") + "\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</li>"); + var itemEl = this.nodeFromString("<li class=\"queue_entry pluid-0 queue_temp queue_active\" title=\"" + Lang.get("addedBy") + ": " + item.author + "\">\n\t\t\t\t<a class=\"qe_title\" href=\"" + item.url + "\" target=\"_blank\">" + StringTools.htmlEscape(item.title) + "</a>\n\t\t\t\t<span class=\"qe_time\">" + this.duration(item.duration) + "</span>\n\t\t\t\t<div class=\"qe_clear\"></div>\n\t\t\t\t<div class=\"btn-group\" style=\"display: inline-block;\">\n\t\t\t\t\t<button class=\"btn btn-xs btn-default qbtn-play\">\n\t\t\t\t\t\t<span class=\"glyphicon glyphicon-play\"></span>" + Lang.get("play") + "\n\t\t\t\t\t</button>\n\t\t\t\t\t<button class=\"btn btn-xs btn-default qbtn-next\">\n\t\t\t\t\t\t<span class=\"glyphicon glyphicon-share-alt\"></span>" + Lang.get("skip") + "\n\t\t\t\t\t</button>\n\t\t\t\t\t<button class=\"btn btn-xs btn-default qbtn-tmp\">\n\t\t\t\t\t\t<span class=\"glyphicon glyphicon-flag\"></span>" + Lang.get("makePermanent") + "\n\t\t\t\t\t</button>\n\t\t\t\t\t<button class=\"btn btn-xs btn-default qbtn-delete\" id=\"btn-delete\">\n\t\t\t\t\t\t<span class=\"glyphicon glyphicon-trash\"></span>" + Lang.get("delete") + "\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</li>"); itemEl.querySelector("#btn-delete").onclick = function(e) { _gthis.main.send({ type : "RemoveVideo", removeVideo : { url : itemEl.querySelector(".qe_title").getAttribute("href")}}); return; @@ -1175,7 +1225,7 @@ client_Player.prototype = { } this.player.removeChild(this.video); this.video = null; - window.document.querySelector("#currenttitle").innerHTML = Lang.get("nothingPlaying"); + window.document.querySelector("#currenttitle").textContent = Lang.get("nothingPlaying"); } ,removeItem: function(url) { var _g = 0; @@ -1204,8 +1254,8 @@ client_Player.prototype = { ,updateCounters: function() { var tmp = "" + this.items.length + " "; var tmp1 = Lang.get("videos"); - window.document.querySelector("#plcount").innerHTML = tmp + tmp1; - window.document.querySelector("#pllength").innerHTML = this.totalDuration(); + window.document.querySelector("#plcount").textContent = tmp + tmp1; + window.document.querySelector("#pllength").textContent = this.totalDuration(); } ,getItems: function() { return this.items; @@ -1223,7 +1273,7 @@ client_Player.prototype = { } ,clearItems: function() { this.items.length = 0; - this.videoItemsEl.innerHTML = ""; + this.videoItemsEl.textContent = ""; this.updateCounters(); } ,refresh: function() { diff --git a/res/langs/en.json b/res/langs/en.json index 3e618fb..145e7ae 100644 --- a/res/langs/en.json +++ b/res/langs/en.json @@ -5,7 +5,7 @@ "joined": "joined", "online": "online", "nothingPlaying": "Nothing Playing", - "usernameError": "Username must be from 1 to $MAX characters and don't repeat another's.", + "usernameError": "Username must be from 1 to $MAX characters and don't repeat another's. Characters &^<>'\" are not allowed.", "addVideoError": "Failed to add video.", "rawVideo": "Raw video", "videos": "videos", diff --git a/res/langs/ru.json b/res/langs/ru.json index 5bccce1..ce5d125 100644 --- a/res/langs/ru.json +++ b/res/langs/ru.json @@ -5,7 +5,7 @@ "joined": "вошел", "online": "онлайн", "nothingPlaying": "Ничего не играет", - "usernameError": "Ник должен быть от 1 до $MAX символов и не повторять чужие.", + "usernameError": "Ник должен быть от 1 до $MAX символов и не повторять чужие. Символы &^<>'\" запрещены.", "addVideoError": "Не удалось добавить видео.", "rawVideo": "Исходное видео", "videos": "видео", diff --git a/src/client/Main.hx b/src/client/Main.hx index bfcc877..3a819b2 100644 --- a/src/client/Main.hx +++ b/src/client/Main.hx @@ -283,7 +283,7 @@ class Main { if (isLeader()) player.setTime(player.getTime(), false); case ClearChat: - ge("#messagebuffer").innerHTML = ""; + clearChat(); case ClearPlaylist: player.clearItems(); @@ -313,7 +313,7 @@ class Main { clientName: guestName.value } }); - ge("#messagebuffer").innerHTML = ""; + clearChat(); serverMessage(1); for (message in connected.history) { addMessage(message.name, message.text, message.time); @@ -349,7 +349,7 @@ class Main { form.value += ' ${el.title}'; form.focus(); } - smilesWrap.innerHTML = ""; + smilesWrap.textContent = ""; for (emote in config.emotes) { final img = document.createImageElement(); img.className = "smile-preview"; @@ -402,16 +402,16 @@ class Main { switch (type) { case 1: div.className = "server-msg-reconnect"; - div.innerHTML = Lang.get("msgConnected"); + div.textContent = Lang.get("msgConnected"); case 2: div.className = "server-msg-disconnect"; - div.innerHTML = Lang.get("msgDisconnected"); + div.textContent = Lang.get("msgDisconnected"); case 3: div.className = "server-whisper"; - div.innerHTML = time + text + " " + Lang.get("entered"); + div.textContent = time + text + " " + Lang.get("entered"); case 4: div.className = "server-whisper"; - div.innerHTML = time + text; + div.textContent = time + text; default: } msgBuf.appendChild(div); @@ -420,7 +420,7 @@ class Main { function updateUserList():Void { final userCount = ge("#usercount"); - userCount.innerHTML = clients.length + " " + Lang.get("online"); + userCount.textContent = clients.length + " " + Lang.get("online"); document.title = getPageTitle(); final list = new StringBuf(); @@ -438,6 +438,10 @@ class Main { return '$pageTitle (${clients.length})'; } + function clearChat():Void { + ge("#messagebuffer").textContent = ""; + } + function addMessage(name:String, text:String, ?time:String):Void { final msgBuf = ge("#messagebuffer"); final userDiv = document.createDivElement(); @@ -446,11 +450,11 @@ class Main { final tstamp = document.createSpanElement(); tstamp.className = "timestamp"; if (time == null) time = "[" + new Date().toTimeString().split(" ")[0] + "] "; - tstamp.innerHTML = time; + tstamp.textContent = time; final nameDiv = document.createElement("strong"); nameDiv.className = "username"; - nameDiv.innerHTML = name + ": "; + nameDiv.textContent = name + ": "; final textDiv = document.createSpanElement(); if (text.startsWith("/")) { @@ -460,6 +464,7 @@ class Main { text = filter.regex.replace(text, filter.replace); } } + text = text.htmlEscape(); textDiv.innerHTML = text; final isInChatEnd = msgBuf.scrollHeight - msgBuf.scrollTop == msgBuf.clientHeight; diff --git a/src/client/Player.hx b/src/client/Player.hx index c9b10a4..bee9a3c 100644 --- a/src/client/Player.hx +++ b/src/client/Player.hx @@ -5,6 +5,7 @@ import js.html.VideoElement; import js.Browser.document; import client.Main.ge; import Types.VideoItem; +using StringTools; using Lambda; class Player { @@ -63,16 +64,16 @@ class Player { } }); } - player.innerHTML = ""; + player.textContent = ""; player.appendChild(video); - ge("#currenttitle").innerHTML = item.title; + ge("#currenttitle").textContent = item.title; } public function addVideoItem(item:VideoItem, atEnd:Bool):Void { items.push(item); final itemEl = nodeFromString( '<li class="queue_entry pluid-0 queue_temp queue_active" title="${Lang.get("addedBy")}: ${item.author}"> - <a class="qe_title" href="${item.url}" target="_blank">${item.title}</a> + <a class="qe_title" href="${item.url}" target="_blank">${item.title.htmlEscape()}</a> <span class="qe_time">${duration(item.duration)}</span> <div class="qe_clear"></div> <div class="btn-group" style="display: inline-block;"> @@ -109,7 +110,7 @@ class Player { if (video == null) return; player.removeChild(video); video = null; - ge("#currenttitle").innerHTML = Lang.get("nothingPlaying"); + ge("#currenttitle").textContent = Lang.get("nothingPlaying"); } public function removeItem(url:String):Void { @@ -132,8 +133,8 @@ class Player { } function updateCounters():Void { - ge("#plcount").innerHTML = '${items.length} ${Lang.get("videos")}'; - ge("#pllength").innerHTML = totalDuration(); + ge("#plcount").textContent = '${items.length} ${Lang.get("videos")}'; + ge("#pllength").textContent = totalDuration(); } public function getItems():Array<VideoItem> { @@ -153,7 +154,7 @@ class Player { public function clearItems():Void { items.resize(0); - videoItemsEl.innerHTML = ""; + videoItemsEl.textContent = ""; updateCounters(); } diff --git a/src/server/Main.hx b/src/server/Main.hx index 271f6ea..9f7534c 100644 --- a/src/server/Main.hx +++ b/src/server/Main.hx @@ -180,12 +180,12 @@ class Main { sendClientList(); case Login: final name = data.login.clientName; - if (name.length == 0 || name.length > config.maxLoginLength + if (badNickName(name) || name.length > config.maxLoginLength || clients.getByName(name) != null) { send(client, {type: LoginError}); return; } - client.name = data.login.clientName; + client.name = name; client.isUser = true; send(client, { type: data.type, @@ -228,6 +228,7 @@ class Main { case AddVideo: final item = data.addVideo.item; + item.author = client.name; final localOrigin = '$localIp:$port'; if (item.url.indexOf(localOrigin) != -1) { item.url = item.url.replace(localOrigin, '$globalIp:$port'); @@ -245,13 +246,12 @@ class Main { case RemoveVideo: if (videoList.length == 0) return; final url = data.removeVideo.url; - if (videoList[0].url == url) { - videoTimer.stop(); - if (videoList.length > 0) restartWaitTimer(); - } + final isFirst = videoList[0].url == url; + if (isFirst) videoTimer.stop(); videoList.remove( videoList.find(item -> item.url == url) ); + if (isFirst && videoList.length > 0) restartWaitTimer(); broadcast(data); case Pause: @@ -368,6 +368,14 @@ class Main { } } + final htmlChars = ~/[&^<>'"]/; + + function badNickName(name:String):Bool { + if (name.length == 0) return true; + if (htmlChars.match(name)) return true; + return false; + } + var waitVideoStart:Timer; var loadedClientsCount = 0; |
