From 77179c3db9276ce858e5b62e9039eadca4a6b3a6 Mon Sep 17 00:00:00 2001
From: Juliusz Chroboczek <jch@irif.fr>
Date: Wed, 16 Sep 2020 01:42:39 +0200
Subject: [PATCH] Allow usernames with spaces.

This requires a proper parser for commands.
---
 static/sfu.js | 58 +++++++++++++++++++++++++++++++++++++--------------
 webclient.go  | 19 -----------------
 2 files changed, 42 insertions(+), 35 deletions(-)

diff --git a/static/sfu.js b/static/sfu.js
index cf3bb89..5380ef9 100644
--- a/static/sfu.js
+++ b/static/sfu.js
@@ -992,6 +992,40 @@ function clearChat() {
     document.getElementById('box').textContent = '';
 }
 
+/**
+ * parseCommand splits a string into two space-separated parts.  The first
+ * part may be quoted and may include backslash escapes.
+ *
+ * @param {string} line
+ * @returns {Array.<string>}
+ */
+function parseCommand(line) {
+    let i = 0;
+    while(i < line.length && line[i] === ' ')
+        i++;
+    let start = ' ';
+    if(i < line.length && line[i] === '"' || line[i] === "'") {
+        start = line[i];
+        i++;
+    }
+    let first = "";
+    while(i < line.length) {
+        if(line[i] === start) {
+            if(start !== ' ')
+                i++;
+            break;
+        }
+        if(line[i] === '\\' && i < line.length - 1)
+            i++;
+        first = first + line[i];
+        i++;
+    }
+
+    while(i < line.length && line[i] === ' ')
+        i++;
+    return [first, line.slice(i)];
+}
+
 function handleInput() {
     let input = document.getElementById('input');
     let data = input.value;
@@ -1002,8 +1036,8 @@ function handleInput() {
     if(data === '')
         return;
 
-    if(data.charAt(0) === '/') {
-        if(data.charAt(1) === '/') {
+    if(data[0] === '/') {
+        if(data.length > 1 && data[1] === '/') {
             message = data.substring(1);
             me = false;
         } else {
@@ -1057,31 +1091,23 @@ function handleInput() {
                     displayError("You're not an operator");
                     return;
                 }
-                let username, message;
-                let space = rest.indexOf(' ');
-                if(space < 0) {
-                    username = rest;
-                    message = null;
-                } else {
-                    username = rest.slice(0, space);
-                    message = rest.slice(space + 1).trim();
-                }
+                let parsed = parseCommand(rest);
                 let id;
-                if(username in users) {
-                    id = rest;
+                if(parsed[0] in users) {
+                    id = parsed[0];
                 } else {
                     for(let i in users) {
-                        if(users[i] === username) {
+                        if(users[i] === parsed[0]) {
                             id = i;
                             break;
                         }
                     }
                 }
                 if(!id) {
-                    displayError('Unknown user ' + username);
+                    displayError('Unknown user ' + parsed[0]);
                     return;
                 }
-                serverConnection.userAction(cmd.slice(1), id, message);
+                serverConnection.userAction(cmd.slice(1), id, parsed[1]);
                 return;
             }
             default:
diff --git a/webclient.go b/webclient.go
index 7b81959..f979b6b 100644
--- a/webclient.go
+++ b/webclient.go
@@ -10,7 +10,6 @@ import (
 	"errors"
 	"log"
 	"os"
-	"strings"
 	"sync"
 	"time"
 
@@ -645,24 +644,6 @@ func startClient(conn *websocket.Conn) (err error) {
 		return
 	}
 
-	if strings.ContainsRune(m.Username, ' ') {
-		// at this point, the writer is not running yet, so format
-		// the message ourselves
-		conn.WriteJSON(clientMessage{
-			Type:  "usermessage",
-			Kind:  "error",
-			Value: "don't put spaces in your username",
-		})
-		conn.WriteMessage(websocket.CloseMessage,
-			websocket.FormatCloseMessage(
-				websocket.CloseProtocolError,
-				"don't put spaces in your username",
-			),
-		)
-		conn.Close()
-		return
-	}
-
 	c := &webClient{
 		id: m.Id,
 		credentials: clientCredentials{
