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);