diff --git a/client-data/board.html b/client-data/board.html index f603cd62..04644a20 100644 --- a/client-data/board.html +++ b/client-data/board.html @@ -96,6 +96,7 @@ + diff --git a/client-data/tools/document/document.js b/client-data/tools/document/document.js new file mode 100644 index 00000000..77fc532a --- /dev/null +++ b/client-data/tools/document/document.js @@ -0,0 +1,103 @@ +(function documents() { //Code isolation + + + var xlinkNS = "http://www.w3.org/1999/xlink"; + var imgCount = 1; + + function assert_count() { + if (Tools.svg.querySelectorAll("image").length >= Tools.server_config.MAX_DOCUMENT_COUNT) { + alert("Too many documents exist already"); + throw new Error("Too many documents exist already"); + } + } + + function onstart() { + var fileInput = document.createElement("input"); + fileInput.type = "file"; + fileInput.accept = "image/*"; + fileInput.click(); + fileInput.addEventListener("change", function () { + assert_count(); + + var reader = new FileReader(); + reader.readAsDataURL(fileInput.files[0]); + + reader.onload = function (e) { + // use canvas to compress image + var image = new Image(); + image.src = e.target.result; + image.onload = function () { + + assert_count(); + + var uid = Tools.generateUID("doc"); // doc for document + + var ctx, size; + var scale = 1; + + do { + // Todo give feedback of processing effort + + ctx = document.createElement("canvas").getContext("2d"); + ctx.canvas.width = image.width * scale; + ctx.canvas.height = image.height * scale; + ctx.drawImage(image, 0, 0, image.width * scale, image.height * scale); + var dataURL = ctx.canvas.toDataURL("image/webp", 0.7); + + // Compressed file size as data url, approximately 1/3 larger than as bytestream + size = dataURL.length; + + // attempt again with an image that is at least 10% smaller + scale = scale * Math.sqrt(Math.min( + 0.9, + Tools.server_config.MAX_DOCUMENT_SIZE / size + )); + } while (size > Tools.server_config.MAX_DOCUMENT_SIZE); + + var msg = { + id: uid, + type: "doc", + data: dataURL, + size: size, + w: this.width * scale || 300, + h: this.height * scale || 300, + x: (100 + document.documentElement.scrollLeft) / Tools.scale + 10 * imgCount, + y: (100 + document.documentElement.scrollTop) / Tools.scale + 10 * imgCount + //fileType: fileInput.files[0].type + }; + + assert_count(); + + draw(msg); + Tools.send(msg,"Document"); + imgCount++; + }; + }; + // Tools.change(Tools.prevToolName); + }); + } + + function draw(msg) { + var aspect = msg.w/msg.h; + var img = Tools.createSVGElement("image"); + img.id=msg.id; + img.setAttribute("class", "layer-"+Tools.layer); + img.setAttributeNS(xlinkNS, "href", msg.data); + img.x.baseVal.value = msg['x']; + img.y.baseVal.value = msg['y']; + img.setAttribute("width", 400*aspect); + img.setAttribute("height", 400); + Tools.drawingArea.appendChild(img); + + } + + Tools.add({ + "name": "Document", + //"shortcut": "", + "draw": draw, + "onstart": onstart, + "oneTouch":true, + "icon": "/tools/document/icon.svg", + }); + +})(); //End of code isolation diff --git a/client-data/tools/document/icon.svg b/client-data/tools/document/icon.svg new file mode 100644 index 00000000..ce64b0db --- /dev/null +++ b/client-data/tools/document/icon.svg @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/server/boardData.js b/server/boardData.js index 1539a44f..24b1f559 100644 --- a/server/boardData.js +++ b/server/boardData.js @@ -231,7 +231,11 @@ BoardData.load = async function loadBoard(name) { try { data = await fs.promises.readFile(boardData.file); boardData.board = JSON.parse(data); - for (id in boardData.board) boardData.validate(boardData.board[id]); + boardData.existingDocuments = 0; + for (id in boardData.board) { + boardData.validate(boardData.board[id]); + if (boardData.board[id].type === "doc") existingDocuments += 1; + } log('disk load', { 'board': boardData.name }); } catch (e) { log('empty board creation', { diff --git a/server/client_configuration.js b/server/client_configuration.js index 039992ed..d6d2eae7 100644 --- a/server/client_configuration.js +++ b/server/client_configuration.js @@ -2,6 +2,8 @@ const config = require("./configuration"); /** Settings that should be handed through to the clients */ module.exports = { + "MAX_DOCUMENT_SIZE": config.MAX_DOCUMENT_SIZE, + "MAX_DOCUMENT_COUNT": config.MAX_DOCUMENT_COUNT, "MAX_BOARD_SIZE": config.MAX_BOARD_SIZE, "MAX_EMIT_COUNT": config.MAX_EMIT_COUNT, "MAX_EMIT_COUNT_PERIOD": config.MAX_EMIT_COUNT_PERIOD, diff --git a/server/configuration.js b/server/configuration.js index f03e0c55..1a6a2535 100644 --- a/server/configuration.js +++ b/server/configuration.js @@ -26,6 +26,12 @@ module.exports = { /** Maximum value for any x or y on the board */ MAX_BOARD_SIZE: parseInt(process.env['WBO_MAX_BOARD_SIZE']) || 65536, + /** Maximum size of uploaded documents default 1MB */ + MAX_DOCUMENT_SIZE: parseInt(process.env['WBO_MAX_DOCUMENT_SIZE']) || 1048576, + + /** Maximum number of documents allowed */ + MAX_DOCUMENT_COUNT: parseInt(process.env['WBO_MAX_DOCUMENT_COUNT']) || 5, + /** Maximum messages per user over the given time period before banning them */ MAX_EMIT_COUNT: parseInt(process.env['WBO_MAX_EMIT_COUNT']) || 192, diff --git a/server/sockets.js b/server/sockets.js index 806c954f..f152c41a 100644 --- a/server/sockets.js +++ b/server/sockets.js @@ -49,6 +49,7 @@ function socketConnection(socket) { var board = await getBoard(name); board.users.add(socket.id); log('board joined', { 'board': board.name, 'users': board.users.size }); + return board; } @@ -66,7 +67,7 @@ function socketConnection(socket) { var lastEmitSecond = Date.now() / config.MAX_EMIT_COUNT_PERIOD | 0; var emitCount = 0; - socket.on('broadcast', noFail(function onBroadcast(message) { + socket.on('broadcast', noFail(async function onBroadcast(message) { var currentSecond = Date.now() / config.MAX_EMIT_COUNT_PERIOD | 0; if (currentSecond === lastEmitSecond) { emitCount++; @@ -96,10 +97,33 @@ function socketConnection(socket) { return; } - if (!message.data.tool || config.BLOCKED_TOOLS.includes(message.data.tool)) { + if (!message.data.tool || config.BLOCKED_TOOLS.includes(message.data.tool)) { log('BLOCKED MESSAGE', message.data); return; } + + var boardData; + if (message.data.type === "doc") { + boardData = await getBoard(boardName); + + if (boardData.existingDocuments >= config.MAX_DOCUMENT_COUNT) { + console.warn("Received too many documents"); + return; + } + + if (message.data.data.length > config.MAX_DOCUMENT_SIZE) { + console.warn("Received too large file"); + return; + } + + boardData.existingDocuments += 1; + } else if (message.data.type === "delete") { + boardData = await getBoard(boardName); + + if (boardData.board[message.data.id].type === "doc") { + boardData.existingDocuments -= 1; + } + } // Save the message in the board handleMessage(boardName, data, socket);