1*4882a593SmuzhiyunFrom ba166e9adebfe5343f826c6a9e02299d35414ffd Mon Sep 17 00:00:00 2001
2*4882a593SmuzhiyunFrom: Lorenzo Miniero <lminiero@gmail.com>
3*4882a593SmuzhiyunDate: Thu, 25 Nov 2021 17:20:53 +0100
4*4882a593SmuzhiyunSubject: [PATCH] Fix potential Cross-site Scripting (XSS) exploits in demos
5*4882a593Smuzhiyun (#2817)
6*4882a593Smuzhiyun
7*4882a593Smuzhiyun[Retrieved (and backported) from:
8*4882a593Smuzhiyunhttps://github.com/meetecho/janus-gateway/commit/ba166e9adebfe5343f826c6a9e02299d35414ffd]
9*4882a593SmuzhiyunSigned-off-by: Fabrice Fontaine <fontaine.fabrice@gmail.com>
10*4882a593Smuzhiyun---
11*4882a593Smuzhiyun html/audiobridgetest.js   | 17 +++++++++++++----
12*4882a593Smuzhiyun html/recordplaytest.js    | 13 +++++++++++--
13*4882a593Smuzhiyun html/screensharingtest.js | 11 ++++++++++-
14*4882a593Smuzhiyun html/streamingtest.js     | 13 +++++++++++--
15*4882a593Smuzhiyun html/textroomtest.js      | 23 ++++++++++++++---------
16*4882a593Smuzhiyun html/videocalltest.js     | 15 ++++++++++++---
17*4882a593Smuzhiyun html/videoroomtest.js     | 13 +++++++++++--
18*4882a593Smuzhiyun html/vp9svctest.js        | 13 +++++++++++--
19*4882a593Smuzhiyun 8 files changed, 93 insertions(+), 25 deletions(-)
20*4882a593Smuzhiyun
21*4882a593Smuzhiyundiff --git a/html/audiobridgetest.js b/html/audiobridgetest.js
22*4882a593Smuzhiyunindex 18e1cc1839..f757789708 100644
23*4882a593Smuzhiyun--- a/html/audiobridgetest.js
24*4882a593Smuzhiyun+++ b/html/audiobridgetest.js
25*4882a593Smuzhiyun@@ -178,7 +178,7 @@ $(document).ready(function() {
26*4882a593Smuzhiyun 												Janus.debug("Got a list of participants:", list);
27*4882a593Smuzhiyun 												for(var f in list) {
28*4882a593Smuzhiyun 													var id = list[f]["id"];
29*4882a593Smuzhiyun-													var display = list[f]["display"];
30*4882a593Smuzhiyun+													var display = escapeXmlTags(list[f]["display"]);
31*4882a593Smuzhiyun 													var setup = list[f]["setup"];
32*4882a593Smuzhiyun 													var muted = list[f]["muted"];
33*4882a593Smuzhiyun 													var spatial = list[f]["spatial_position"];
34*4882a593Smuzhiyun@@ -222,7 +222,7 @@ $(document).ready(function() {
35*4882a593Smuzhiyun 												Janus.debug("Got a list of participants:", list);
36*4882a593Smuzhiyun 												for(var f in list) {
37*4882a593Smuzhiyun 													var id = list[f]["id"];
38*4882a593Smuzhiyun-													var display = list[f]["display"];
39*4882a593Smuzhiyun+													var display = escapeXmlTags(list[f]["display"]);
40*4882a593Smuzhiyun 													var setup = list[f]["setup"];
41*4882a593Smuzhiyun 													var muted = list[f]["muted"];
42*4882a593Smuzhiyun 													var spatial = list[f]["spatial_position"];
43*4882a593Smuzhiyun@@ -267,7 +267,7 @@ $(document).ready(function() {
44*4882a593Smuzhiyun 												Janus.debug("Got a list of participants:", list);
45*4882a593Smuzhiyun 												for(var f in list) {
46*4882a593Smuzhiyun 													var id = list[f]["id"];
47*4882a593Smuzhiyun-													var display = list[f]["display"];
48*4882a593Smuzhiyun+													var display = escapeXmlTags(list[f]["display"]);
49*4882a593Smuzhiyun 													var setup = list[f]["setup"];
50*4882a593Smuzhiyun 													var muted = list[f]["muted"];
51*4882a593Smuzhiyun 													var spatial = list[f]["spatial_position"];
52*4882a593Smuzhiyun@@ -429,7 +429,7 @@ function registerUsername() {
53*4882a593Smuzhiyun 			return;
54*4882a593Smuzhiyun 		}
55*4882a593Smuzhiyun 		var register = { request: "join", room: myroom, display: username };
56*4882a593Smuzhiyun-		myusername = username;
57*4882a593Smuzhiyun+		myusername = escapeXmlTags(username);
58*4882a593Smuzhiyun 		mixertest.send({ message: register});
59*4882a593Smuzhiyun 	}
60*4882a593Smuzhiyun }
61*4882a593Smuzhiyun@@ -448,3 +448,12 @@ function getQueryStringValue(name) {
62*4882a593Smuzhiyun 		results = regex.exec(location.search);
63*4882a593Smuzhiyun 	return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
64*4882a593Smuzhiyun }
65*4882a593Smuzhiyun+
66*4882a593Smuzhiyun+// Helper to escape XML tags
67*4882a593Smuzhiyun+function escapeXmlTags(value) {
68*4882a593Smuzhiyun+	if(value) {
69*4882a593Smuzhiyun+		var escapedValue = value.replace(new RegExp('<', 'g'), '&lt');
70*4882a593Smuzhiyun+		escapedValue = escapedValue.replace(new RegExp('>', 'g'), '&gt');
71*4882a593Smuzhiyun+		return escapedValue;
72*4882a593Smuzhiyun+	}
73*4882a593Smuzhiyun+}
74*4882a593Smuzhiyundiff --git a/html/recordplaytest.js b/html/recordplaytest.js
75*4882a593Smuzhiyunindex 74ee7bed95..52b5ccbc4c 100644
76*4882a593Smuzhiyun--- a/html/recordplaytest.js
77*4882a593Smuzhiyun+++ b/html/recordplaytest.js
78*4882a593Smuzhiyun@@ -423,11 +423,11 @@ function updateRecsList() {
79*4882a593Smuzhiyun 			Janus.debug("Got a list of available recordings:", list);
80*4882a593Smuzhiyun 			for(var mp in list) {
81*4882a593Smuzhiyun 				Janus.debug("  >> [" + list[mp]["id"] + "] " + list[mp]["name"] + " (" + list[mp]["date"] + ")");
82*4882a593Smuzhiyun-				$('#recslist').append("<li><a href='#' id='" + list[mp]["id"] + "'>" + list[mp]["name"] + " [" + list[mp]["date"] + "]" + "</a></li>");
83*4882a593Smuzhiyun+				$('#recslist').append("<li><a href='#' id='" + list[mp]["id"] + "'>" + escapeXmlTags(list[mp]["name"]) + " [" + list[mp]["date"] + "]" + "</a></li>");
84*4882a593Smuzhiyun 			}
85*4882a593Smuzhiyun 			$('#recslist a').unbind('click').click(function() {
86*4882a593Smuzhiyun 				selectedRecording = $(this).attr("id");
87*4882a593Smuzhiyun-				selectedRecordingInfo = $(this).text();
88*4882a593Smuzhiyun+				selectedRecordingInfo = escapeXmlTags($(this).text());
89*4882a593Smuzhiyun 				$('#recset').html($(this).html()).parent().removeClass('open');
90*4882a593Smuzhiyun 				$('#play').removeAttr('disabled').click(startPlayout);
91*4882a593Smuzhiyun 				return false;
92*4882a593Smuzhiyun@@ -545,3 +545,12 @@ function getQueryStringValue(name) {
93*4882a593Smuzhiyun 		results = regex.exec(location.search);
94*4882a593Smuzhiyun 	return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
95*4882a593Smuzhiyun }
96*4882a593Smuzhiyun+
97*4882a593Smuzhiyun+// Helper to escape XML tags
98*4882a593Smuzhiyun+function escapeXmlTags(value) {
99*4882a593Smuzhiyun+	if(value) {
100*4882a593Smuzhiyun+		var escapedValue = value.replace(new RegExp('<', 'g'), '&lt');
101*4882a593Smuzhiyun+		escapedValue = escapedValue.replace(new RegExp('>', 'g'), '&gt');
102*4882a593Smuzhiyun+		return escapedValue;
103*4882a593Smuzhiyun+	}
104*4882a593Smuzhiyun+}
105*4882a593Smuzhiyundiff --git a/html/screensharingtest.js b/html/screensharingtest.js
106*4882a593Smuzhiyunindex 61eac70f43..c64d8dbd67 100644
107*4882a593Smuzhiyun--- a/html/screensharingtest.js
108*4882a593Smuzhiyun+++ b/html/screensharingtest.js
109*4882a593Smuzhiyun@@ -161,7 +161,7 @@ $(document).ready(function() {
110*4882a593Smuzhiyun 										if(event === "joined") {
111*4882a593Smuzhiyun 											myid = msg["id"];
112*4882a593Smuzhiyun 											$('#session').html(room);
113*4882a593Smuzhiyun-											$('#title').html(msg["description"]);
114*4882a593Smuzhiyun+											$('#title').html(escapeXmlTags(msg["description"]));
115*4882a593Smuzhiyun 											Janus.log("Successfully joined room " + msg["room"] + " with ID " + myid);
116*4882a593Smuzhiyun 											if(role === "publisher") {
117*4882a593Smuzhiyun 												// This is our session, publish our stream
118*4882a593Smuzhiyun@@ -514,3 +514,12 @@ function newRemoteFeed(id, display) {
119*4882a593Smuzhiyun 			}
120*4882a593Smuzhiyun 		});
121*4882a593Smuzhiyun }
122*4882a593Smuzhiyun+
123*4882a593Smuzhiyun+// Helper to escape XML tags
124*4882a593Smuzhiyun+function escapeXmlTags(value) {
125*4882a593Smuzhiyun+	if(value) {
126*4882a593Smuzhiyun+		var escapedValue = value.replace(new RegExp('<', 'g'), '&lt');
127*4882a593Smuzhiyun+		escapedValue = escapedValue.replace(new RegExp('>', 'g'), '&gt');
128*4882a593Smuzhiyun+		return escapedValue;
129*4882a593Smuzhiyun+	}
130*4882a593Smuzhiyun+}
131*4882a593Smuzhiyundiff --git a/html/streamingtest.js b/html/streamingtest.js
132*4882a593Smuzhiyunindex 7dd2e1f681..3f9937f11c 100644
133*4882a593Smuzhiyun--- a/html/streamingtest.js
134*4882a593Smuzhiyun+++ b/html/streamingtest.js
135*4882a593Smuzhiyun@@ -323,7 +323,7 @@ function updateStreamsList() {
136*4882a593Smuzhiyun 			Janus.debug(list);
137*4882a593Smuzhiyun 			for(var mp in list) {
138*4882a593Smuzhiyun 				Janus.debug("  >> [" + list[mp]["id"] + "] " + list[mp]["description"] + " (" + list[mp]["type"] + ")");
139*4882a593Smuzhiyun-				$('#streamslist').append("<li><a href='#' id='" + list[mp]["id"] + "'>" + list[mp]["description"] + " (" + list[mp]["type"] + ")" + "</a></li>");
140*4882a593Smuzhiyun+				$('#streamslist').append("<li><a href='#' id='" + list[mp]["id"] + "'>" + escapeXmlTags(list[mp]["description"]) + " (" + list[mp]["type"] + ")" + "</a></li>");
141*4882a593Smuzhiyun 			}
142*4882a593Smuzhiyun 			$('#streamslist a').unbind('click').click(function() {
143*4882a593Smuzhiyun 				selectedStream = $(this).attr("id");
144*4882a593Smuzhiyun@@ -345,7 +345,7 @@ function getStreamInfo() {
145*4882a593Smuzhiyun 	var body = { request: "info", id: parseInt(selectedStream) || selectedStream };
146*4882a593Smuzhiyun 	streaming.send({ message: body, success: function(result) {
147*4882a593Smuzhiyun 		if(result && result.info && result.info.metadata) {
148*4882a593Smuzhiyun-			$('#metadata').html(result.info.metadata);
149*4882a593Smuzhiyun+			$('#metadata').html(escapeXmlTags(result.info.metadata));
150*4882a593Smuzhiyun 			$('#info').removeClass('hide').show();
151*4882a593Smuzhiyun 		}
152*4882a593Smuzhiyun 	}});
153*4882a593Smuzhiyun@@ -394,6 +394,15 @@ function stopStream() {
154*4882a593Smuzhiyun 	simulcastStarted = false;
155*4882a593Smuzhiyun }
156*4882a593Smuzhiyun
157*4882a593Smuzhiyun+// Helper to escape XML tags
158*4882a593Smuzhiyun+function escapeXmlTags(value) {
159*4882a593Smuzhiyun+	if(value) {
160*4882a593Smuzhiyun+		var escapedValue = value.replace(new RegExp('<', 'g'), '&lt');
161*4882a593Smuzhiyun+		escapedValue = escapedValue.replace(new RegExp('>', 'g'), '&gt');
162*4882a593Smuzhiyun+		return escapedValue;
163*4882a593Smuzhiyun+	}
164*4882a593Smuzhiyun+}
165*4882a593Smuzhiyun+
166*4882a593Smuzhiyun // Helpers to create Simulcast-related UI, if enabled
167*4882a593Smuzhiyun function addSimulcastButtons() {
168*4882a593Smuzhiyun 	$('#curres').parent().append(
169*4882a593Smuzhiyundiff --git a/html/textroomtest.js b/html/textroomtest.js
170*4882a593Smuzhiyunindex 082ae44905..3d0697e35a 100644
171*4882a593Smuzhiyun--- a/html/textroomtest.js
172*4882a593Smuzhiyun+++ b/html/textroomtest.js
173*4882a593Smuzhiyun@@ -153,9 +153,7 @@ $(document).ready(function() {
174*4882a593Smuzhiyun 									var what = json["textroom"];
175*4882a593Smuzhiyun 									if(what === "message") {
176*4882a593Smuzhiyun 										// Incoming message: public or private?
177*4882a593Smuzhiyun-										var msg = json["text"];
178*4882a593Smuzhiyun-										msg = msg.replace(new RegExp('<', 'g'), '&lt');
179*4882a593Smuzhiyun-										msg = msg.replace(new RegExp('>', 'g'), '&gt');
180*4882a593Smuzhiyun+										var msg = escapeXmlTags(json["text"]);
181*4882a593Smuzhiyun 										var from = json["from"];
182*4882a593Smuzhiyun 										var dateString = getDateString(json["date"]);
183*4882a593Smuzhiyun 										var whisper = json["whisper"];
184*4882a593Smuzhiyun@@ -170,9 +168,7 @@ $(document).ready(function() {
185*4882a593Smuzhiyun 										}
186*4882a593Smuzhiyun 									} else if(what === "announcement") {
187*4882a593Smuzhiyun 										// Room announcement
188*4882a593Smuzhiyun-										var msg = json["text"];
189*4882a593Smuzhiyun-										msg = msg.replace(new RegExp('<', 'g'), '&lt');
190*4882a593Smuzhiyun-										msg = msg.replace(new RegExp('>', 'g'), '&gt');
191*4882a593Smuzhiyun+										var msg = escapeXmlTags(json["text"]);
192*4882a593Smuzhiyun 										var dateString = getDateString(json["date"]);
193*4882a593Smuzhiyun 										$('#chatroom').append('<p style="color: purple;">[' + dateString + '] <i>' + msg + '</i>');
194*4882a593Smuzhiyun 										$('#chatroom').get(0).scrollTop = $('#chatroom').get(0).scrollHeight;
195*4882a593Smuzhiyun@@ -180,7 +176,7 @@ $(document).ready(function() {
196*4882a593Smuzhiyun 										// Somebody joined
197*4882a593Smuzhiyun 										var username = json["username"];
198*4882a593Smuzhiyun 										var display = json["display"];
199*4882a593Smuzhiyun-										participants[username] = display ? display : username;
200*4882a593Smuzhiyun+										participants[username] = escapeXmlTags(display ? display : username);
201*4882a593Smuzhiyun 										if(username !== myid && $('#rp' + username).length === 0) {
202*4882a593Smuzhiyun 											// Add to the participants list
203*4882a593Smuzhiyun 											$('#list').append('<li id="rp' + username + '" class="list-group-item">' + participants[username] + '</li>');
204*4882a593Smuzhiyun@@ -282,7 +278,7 @@ function registerUsername() {
205*4882a593Smuzhiyun 			username: myid,
206*4882a593Smuzhiyun 			display: username
207*4882a593Smuzhiyun 		};
208*4882a593Smuzhiyun-		myusername = username;
209*4882a593Smuzhiyun+		myusername = escapeXmlTags(username);
210*4882a593Smuzhiyun 		transactions[transaction] = function(response) {
211*4882a593Smuzhiyun 			if(response["textroom"] === "error") {
212*4882a593Smuzhiyun 				// Something went wrong
213*4882a593Smuzhiyun@@ -312,7 +308,7 @@ function registerUsername() {
214*4882a593Smuzhiyun 			if(response.participants && response.participants.length > 0) {
215*4882a593Smuzhiyun 				for(var i in response.participants) {
216*4882a593Smuzhiyun 					var p = response.participants[i];
217*4882a593Smuzhiyun-					participants[p.username] = p.display ? p.display : p.username;
218*4882a593Smuzhiyun+					participants[p.username] = escapeXmlTags(p.display ? p.display : p.username);
219*4882a593Smuzhiyun 					if(p.username !== myid && $('#rp' + p.username).length === 0) {
220*4882a593Smuzhiyun 						// Add to the participants list
221*4882a593Smuzhiyun 						$('#list').append('<li id="rp' + p.username + '" class="list-group-item">' + participants[p.username] + '</li>');
222*4882a593Smuzhiyun@@ -418,3 +414,12 @@ function getQueryStringValue(name) {
223*4882a593Smuzhiyun 		results = regex.exec(location.search);
224*4882a593Smuzhiyun 	return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
225*4882a593Smuzhiyun }
226*4882a593Smuzhiyun+
227*4882a593Smuzhiyun+// Helper to escape XML tags
228*4882a593Smuzhiyun+function escapeXmlTags(value) {
229*4882a593Smuzhiyun+	if(value) {
230*4882a593Smuzhiyun+		var escapedValue = value.replace(new RegExp('<', 'g'), '&lt');
231*4882a593Smuzhiyun+		escapedValue = escapedValue.replace(new RegExp('>', 'g'), '&gt');
232*4882a593Smuzhiyun+		return escapedValue;
233*4882a593Smuzhiyun+	}
234*4882a593Smuzhiyun+}
235*4882a593Smuzhiyundiff --git a/html/videocalltest.js b/html/videocalltest.js
236*4882a593Smuzhiyunindex d1c1ab8d07..18ccbc2c47 100644
237*4882a593Smuzhiyun--- a/html/videocalltest.js
238*4882a593Smuzhiyun+++ b/html/videocalltest.js
239*4882a593Smuzhiyun@@ -148,7 +148,7 @@ $(document).ready(function() {
240*4882a593Smuzhiyun 										} else if(result["event"]) {
241*4882a593Smuzhiyun 											var event = result["event"];
242*4882a593Smuzhiyun 											if(event === 'registered') {
243*4882a593Smuzhiyun-												myusername = result["username"];
244*4882a593Smuzhiyun+												myusername = escapeXmlTags(result["username"]);
245*4882a593Smuzhiyun 												Janus.log("Successfully registered as " + myusername + "!");
246*4882a593Smuzhiyun 												$('#youok').removeClass('hide').show().html("Registered as '" + myusername + "'");
247*4882a593Smuzhiyun 												// Get a list of available peers, just for fun
248*4882a593Smuzhiyun@@ -163,7 +163,7 @@ $(document).ready(function() {
249*4882a593Smuzhiyun 												bootbox.alert("Waiting for the peer to answer...");
250*4882a593Smuzhiyun 											} else if(event === 'incomingcall') {
251*4882a593Smuzhiyun 												Janus.log("Incoming call from " + result["username"] + "!");
252*4882a593Smuzhiyun-												yourusername = result["username"];
253*4882a593Smuzhiyun+												yourusername = escapeXmlTags(result["username"]);
254*4882a593Smuzhiyun 												// Notify user
255*4882a593Smuzhiyun 												bootbox.hideAll();
256*4882a593Smuzhiyun 												incoming = bootbox.dialog({
257*4882a593Smuzhiyun@@ -213,7 +213,7 @@ $(document).ready(function() {
258*4882a593Smuzhiyun 												});
259*4882a593Smuzhiyun 											} else if(event === 'accepted') {
260*4882a593Smuzhiyun 												bootbox.hideAll();
261*4882a593Smuzhiyun-												var peer = result["username"];
262*4882a593Smuzhiyun+												var peer = escapeXmlTags(result["username"]);
263*4882a593Smuzhiyun 												if(!peer) {
264*4882a593Smuzhiyun 													Janus.log("Call started!");
265*4882a593Smuzhiyun 												} else {
266*4882a593Smuzhiyun@@ -598,6 +598,15 @@ function getQueryStringValue(name) {
267*4882a593Smuzhiyun 	return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
268*4882a593Smuzhiyun }
269*4882a593Smuzhiyun
270*4882a593Smuzhiyun+// Helper to escape XML tags
271*4882a593Smuzhiyun+function escapeXmlTags(value) {
272*4882a593Smuzhiyun+	if(value) {
273*4882a593Smuzhiyun+		var escapedValue = value.replace(new RegExp('<', 'g'), '&lt');
274*4882a593Smuzhiyun+		escapedValue = escapedValue.replace(new RegExp('>', 'g'), '&gt');
275*4882a593Smuzhiyun+		return escapedValue;
276*4882a593Smuzhiyun+	}
277*4882a593Smuzhiyun+}
278*4882a593Smuzhiyun+
279*4882a593Smuzhiyun // Helpers to create Simulcast-related UI, if enabled
280*4882a593Smuzhiyun function addSimulcastButtons(temporal) {
281*4882a593Smuzhiyun 	$('#curres').parent().append(
282*4882a593Smuzhiyundiff --git a/html/videoroomtest.js b/html/videoroomtest.js
283*4882a593Smuzhiyunindex 6a566891d8..5a3ade9be9 100644
284*4882a593Smuzhiyun--- a/html/videoroomtest.js
285*4882a593Smuzhiyun+++ b/html/videoroomtest.js
286*4882a593Smuzhiyun@@ -400,7 +400,7 @@ function registerUsername() {
287*4882a593Smuzhiyun 			ptype: "publisher",
288*4882a593Smuzhiyun 			display: username
289*4882a593Smuzhiyun 		};
290*4882a593Smuzhiyun-		myusername = username;
291*4882a593Smuzhiyun+		myusername = escapeXmlTags(username);
292*4882a593Smuzhiyun 		sfutest.send({ message: register });
293*4882a593Smuzhiyun 	}
294*4882a593Smuzhiyun }
295*4882a593Smuzhiyun@@ -530,7 +530,7 @@ function newRemoteFeed(id, display, audio, video) {
296*4882a593Smuzhiyun 							}
297*4882a593Smuzhiyun 						}
298*4882a593Smuzhiyun 						remoteFeed.rfid = msg["id"];
299*4882a593Smuzhiyun-						remoteFeed.rfdisplay = msg["display"];
300*4882a593Smuzhiyun+						remoteFeed.rfdisplay = escapeXmlTags(msg["display"]);
301*4882a593Smuzhiyun 						if(!remoteFeed.spinner) {
302*4882a593Smuzhiyun 							var target = document.getElementById('videoremote'+remoteFeed.rfindex);
303*4882a593Smuzhiyun 							remoteFeed.spinner = new Spinner({top:100}).spin(target);
304*4882a593Smuzhiyun@@ -685,6 +685,15 @@ function getQueryStringValue(name) {
305*4882a593Smuzhiyun 	return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
306*4882a593Smuzhiyun }
307*4882a593Smuzhiyun
308*4882a593Smuzhiyun+// Helper to escape XML tags
309*4882a593Smuzhiyun+function escapeXmlTags(value) {
310*4882a593Smuzhiyun+	if(value) {
311*4882a593Smuzhiyun+		var escapedValue = value.replace(new RegExp('<', 'g'), '&lt');
312*4882a593Smuzhiyun+		escapedValue = escapedValue.replace(new RegExp('>', 'g'), '&gt');
313*4882a593Smuzhiyun+		return escapedValue;
314*4882a593Smuzhiyun+	}
315*4882a593Smuzhiyun+}
316*4882a593Smuzhiyun+
317*4882a593Smuzhiyun // Helpers to create Simulcast-related UI, if enabled
318*4882a593Smuzhiyun function addSimulcastButtons(feed, temporal) {
319*4882a593Smuzhiyun 	var index = feed;
320*4882a593Smuzhiyundiff --git a/html/vp9svctest.js b/html/vp9svctest.js
321*4882a593Smuzhiyunindex eca0239c32..b22ccf3340 100644
322*4882a593Smuzhiyun--- a/html/vp9svctest.js
323*4882a593Smuzhiyun+++ b/html/vp9svctest.js
324*4882a593Smuzhiyun@@ -387,7 +387,7 @@ function registerUsername() {
325*4882a593Smuzhiyun 			ptype: "publisher",
326*4882a593Smuzhiyun 			display: username
327*4882a593Smuzhiyun 		};
328*4882a593Smuzhiyun-		myusername = username;
329*4882a593Smuzhiyun+		myusername = escapeXmlTags(username);
330*4882a593Smuzhiyun 		sfutest.send({ message: register });
331*4882a593Smuzhiyun 	}
332*4882a593Smuzhiyun }
333*4882a593Smuzhiyun@@ -486,7 +486,7 @@ function newRemoteFeed(id, display, audio, video) {
334*4882a593Smuzhiyun 							}
335*4882a593Smuzhiyun 						}
336*4882a593Smuzhiyun 						remoteFeed.rfid = msg["id"];
337*4882a593Smuzhiyun-						remoteFeed.rfdisplay = msg["display"];
338*4882a593Smuzhiyun+						remoteFeed.rfdisplay = escapeXmlTags(msg["display"]);
339*4882a593Smuzhiyun 						if(!remoteFeed.spinner) {
340*4882a593Smuzhiyun 							var target = document.getElementById('videoremote'+remoteFeed.rfindex);
341*4882a593Smuzhiyun 							remoteFeed.spinner = new Spinner({top:100}).spin(target);
342*4882a593Smuzhiyun@@ -630,6 +630,15 @@ function newRemoteFeed(id, display, audio, video) {
343*4882a593Smuzhiyun 		});
344*4882a593Smuzhiyun }
345*4882a593Smuzhiyun
346*4882a593Smuzhiyun+// Helper to escape XML tags
347*4882a593Smuzhiyun+function escapeXmlTags(value) {
348*4882a593Smuzhiyun+	if(value) {
349*4882a593Smuzhiyun+		var escapedValue = value.replace(new RegExp('<', 'g'), '&lt');
350*4882a593Smuzhiyun+		escapedValue = escapedValue.replace(new RegExp('>', 'g'), '&gt');
351*4882a593Smuzhiyun+		return escapedValue;
352*4882a593Smuzhiyun+	}
353*4882a593Smuzhiyun+}
354*4882a593Smuzhiyun+
355*4882a593Smuzhiyun // Helpers to create SVC-related UI for a new viewer
356*4882a593Smuzhiyun function addSvcButtons(feed) {
357*4882a593Smuzhiyun 	var index = feed;
358