Allow only valid hrefs in newShare for map

This commit is contained in:
Max Berger
2026-03-07 23:57:44 +01:00
parent 25e571ad63
commit 93610213e3
4 changed files with 212 additions and 199 deletions

View File

@@ -23,18 +23,19 @@ import { create_collection, edit_collection } from "./api.js";
import { COLOR_RE } from "./constants.js"; import { COLOR_RE } from "./constants.js";
import { Collection, CollectionType } from "./models.js"; import { Collection, CollectionType } from "./models.js";
import { Scene, pop_scene, push_scene, scene_stack } from "./scene_manager.js"; import { Scene, pop_scene, push_scene, scene_stack } from "./scene_manager.js";
import { cleanHREFinput, isValidHREF, random_hex, random_uuid } from "./utils.js"; import { cleanHREFinput, isValidHREF, onCleanHREFinput, random_hex, random_uuid } from "./utils.js";
/** /**
* @constructor
* @implements {Scene} * @implements {Scene}
*/
export class CreateEditCollectionScene {
/**
* @param {string} user * @param {string} user
* @param {string} password * @param {string} password
* @param {Collection} collection if it's a principal collection, a new * @param {Collection} collection if it's a principal collection, a new
* collection will be created inside of it. * collection will be created inside of it.
* Otherwise the collection will be edited. * Otherwise the collection will be edited.
*/ */
export class CreateEditCollectionScene {
constructor(user, password, collection) { constructor(user, password, collection) {
let edit = collection.type !== CollectionType.PRINCIPAL; let edit = collection.type !== CollectionType.PRINCIPAL;
let html_scene = document.getElementById(edit ? "editcollectionscene" : "createcollectionscene"); let html_scene = document.getElementById(edit ? "editcollectionscene" : "createcollectionscene");
@@ -69,7 +70,7 @@ export class CreateEditCollectionScene {
let color = edit && collection.color ? collection.color : "#" + random_hex(6); let color = edit && collection.color ? collection.color : "#" + random_hex(6);
if (!edit) { if (!edit) {
href_form.addEventListener("keydown", cleanHREFinput); href_form.addEventListener("input", onCleanHREFinput);
} }
function remove_invalid_types() { function remove_invalid_types() {

View File

@@ -20,6 +20,7 @@
import { add_share_by_map, add_share_by_token } from "./api.js"; import { add_share_by_map, add_share_by_token } from "./api.js";
import { Scene, pop_scene, scene_stack } from "./scene_manager.js"; import { Scene, pop_scene, scene_stack } from "./scene_manager.js";
import { onCleanHREFinput } from "./utils.js";
/** /**
* @implements {Scene} * @implements {Scene}
@@ -45,6 +46,8 @@ export class NewShareScene {
/** @type {HTMLInputElement} */ let properties_input = html_scene.querySelector("[data-name=properties]"); /** @type {HTMLInputElement} */ let properties_input = html_scene.querySelector("[data-name=properties]");
/** @type {HTMLElement} */ let cancel_btn = html_scene.querySelector("[data-name=cancel]"); /** @type {HTMLElement} */ let cancel_btn = html_scene.querySelector("[data-name=cancel]");
sharehref_input.addEventListener("input", onCleanHREFinput);
/** @type {?number} */ let scene_index = null; /** @type {?number} */ let scene_index = null;
function oncancel() { function oncancel() {

View File

@@ -20,17 +20,19 @@
import { Scene, pop_scene, scene_stack } from "./scene_manager.js"; import { Scene, pop_scene, scene_stack } from "./scene_manager.js";
import { Collection } from "./models.js"; import { Collection } from "./models.js";
import { cleanHREFinput, isValidHREF, random_uuid } from "./utils.js"; import { cleanHREFinput, isValidHREF, onCleanHREFinput, random_uuid } from "./utils.js";
import { upload_collection } from "./api.js"; import { upload_collection } from "./api.js";
/** /**
* @constructor
* @implements {Scene} * @implements {Scene}
*/
export class UploadCollectionScene {
/**
* @param {string} user * @param {string} user
* @param {string} password * @param {string} password
* @param {Collection} collection parent collection * @param {Collection} collection parent collection
*/ */
export function UploadCollectionScene(user, password, collection) { constructor(user, password, collection) {
/** @type {HTMLElement} */ let html_scene = document.getElementById("uploadcollectionscene"); /** @type {HTMLElement} */ let html_scene = document.getElementById("uploadcollectionscene");
/** @type {HTMLElement} */ let template = html_scene.querySelector("[data-name=filetemplate]"); /** @type {HTMLElement} */ let template = html_scene.querySelector("[data-name=filetemplate]");
/** @type {HTMLElement} */ let upload_btn = html_scene.querySelector("[data-name=submit]"); /** @type {HTMLElement} */ let upload_btn = html_scene.querySelector("[data-name=submit]");
@@ -43,7 +45,7 @@ export function UploadCollectionScene(user, password, collection) {
/** @type {HTMLElement} */ let pending_html = html_scene.querySelector("[data-name=pending]"); /** @type {HTMLElement} */ let pending_html = html_scene.querySelector("[data-name=pending]");
let files = uploadfile_form.files; let files = uploadfile_form.files;
href_form.addEventListener("keydown", cleanHREFinput); href_form.addEventListener("input", onCleanHREFinput);
upload_btn.onclick = upload_start; upload_btn.onclick = upload_start;
uploadfile_form.onchange = onfileschange; uploadfile_form.onchange = onfileschange;
@@ -57,7 +59,7 @@ export function UploadCollectionScene(user, password, collection) {
function upload_start() { function upload_start() {
try { try {
if(!read_form()){ if (!read_form()) {
return false; return false;
} }
uploadfile_form.classList.add("hidden"); uploadfile_form.classList.add("hidden");
@@ -73,7 +75,7 @@ export function UploadCollectionScene(user, password, collection) {
nodes = []; nodes = [];
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
let file = files[i]; let file = files[i];
/** @type {HTMLElement} */ let node = template.cloneNode(true); let node = /** @type {HTMLElement} */ (template.cloneNode(true));
node.classList.remove("hidden"); node.classList.remove("hidden");
let name_form = node.querySelector("[data-name=name]"); let name_form = node.querySelector("[data-name=name]");
name_form.textContent = file.name; name_form.textContent = file.name;
@@ -83,32 +85,32 @@ export function UploadCollectionScene(user, password, collection) {
template.parentNode.insertBefore(node, template); template.parentNode.insertBefore(node, template);
} }
upload_next(); upload_next();
} catch(err) { } catch (err) {
console.error(err); console.error(err);
} }
return false; return false;
} }
function upload_next(){ function upload_next() {
try{ try {
if (files.length === results.length) { if (files.length === results.length) {
pending_html.classList.add("hidden"); pending_html.classList.add("hidden");
close_btn.classList.remove("hidden"); close_btn.classList.remove("hidden");
return; return;
} else { } else {
let file = files[results.length]; let file = files[results.length];
if(files.length > 1 || href.length == 0){ if (files.length > 1 || href.length == 0) {
href = random_uuid(); href = random_uuid();
} }
let upload_href = collection.href + href + "/"; let upload_href = collection.href + href + "/";
upload_req = upload_collection(user, password, upload_href, file, function(result) { upload_req = upload_collection(user, password, upload_href, file, function (result) {
upload_req = null; upload_req = null;
results.push(result); results.push(result);
updateFileStatus(results.length - 1); updateFileStatus(results.length - 1);
upload_next(); upload_next();
}); });
} }
}catch(err){ } catch (err) {
console.error(err); console.error(err);
} }
} }
@@ -116,7 +118,7 @@ export function UploadCollectionScene(user, password, collection) {
function onclose() { function onclose() {
try { try {
pop_scene(scene_index - 1); pop_scene(scene_index - 1);
} catch(err) { } catch (err) {
console.error(err); console.error(err);
} }
return false; return false;
@@ -146,13 +148,13 @@ export function UploadCollectionScene(user, password, collection) {
function read_form() { function read_form() {
cleanHREFinput(href_form); cleanHREFinput(href_form);
let newhreftxtvalue = href_form.value.trim().toLowerCase(); let newhreftxtvalue = href_form.value.trim().toLowerCase();
if(!isValidHREF(newhreftxtvalue)){ if (!isValidHREF(newhreftxtvalue)) {
alert("You must enter a valid HREF"); alert("You must enter a valid HREF");
return false; return false;
} }
href = newhreftxtvalue; href = newhreftxtvalue;
if(uploadfile_form.files.length == 0){ if (uploadfile_form.files.length == 0) {
alert("You must select at least one file to upload"); alert("You must select at least one file to upload");
return false; return false;
} }
@@ -162,12 +164,12 @@ export function UploadCollectionScene(user, password, collection) {
function onfileschange() { function onfileschange() {
files = uploadfile_form.files; files = uploadfile_form.files;
if(files.length > 1){ if (files.length > 1) {
hreflimitmsg_html.classList.remove("hidden"); hreflimitmsg_html.classList.remove("hidden");
href_form.classList.add("hidden"); href_form.classList.add("hidden");
href_label.classList.add("hidden"); href_label.classList.add("hidden");
href_form.value = random_uuid(); // dummy, will be replaced on upload href_form.value = random_uuid(); // dummy, will be replaced on upload
}else{ } else {
hreflimitmsg_html.classList.add("hidden"); hreflimitmsg_html.classList.add("hidden");
href_form.classList.remove("hidden"); href_form.classList.remove("hidden");
href_label.classList.remove("hidden"); href_label.classList.remove("hidden");
@@ -176,13 +178,13 @@ export function UploadCollectionScene(user, password, collection) {
return false; return false;
} }
this.show = function() { this.show = function () {
scene_index = scene_stack.length - 1; scene_index = scene_stack.length - 1;
html_scene.classList.remove("hidden"); html_scene.classList.remove("hidden");
close_btn.onclick = onclose; close_btn.onclick = onclose;
}; };
this.hide = function() { this.hide = function () {
html_scene.classList.add("hidden"); html_scene.classList.add("hidden");
close_btn.classList.remove("hidden"); close_btn.classList.remove("hidden");
upload_btn.classList.remove("hidden"); upload_btn.classList.remove("hidden");
@@ -196,19 +198,20 @@ export function UploadCollectionScene(user, password, collection) {
upload_btn.onclick = null; upload_btn.onclick = null;
href_form.value = ""; href_form.value = "";
uploadfile_form.value = ""; uploadfile_form.value = "";
if(nodes == null){ if (nodes == null) {
return; return;
} }
nodes.forEach(function(node) { nodes.forEach(function (node) {
node.parentNode.removeChild(node); node.parentNode.removeChild(node);
}); });
nodes = null; nodes = null;
}; };
this.release = function() { this.release = function () {
scene_index = null; scene_index = null;
if (upload_req !== null) { if (upload_req !== null) {
upload_req.abort(); upload_req.abort();
upload_req = null; upload_req = null;
} }
}; };
}
} }

View File

@@ -47,19 +47,18 @@ export function random_uuid() {
export function random_hex(length) { export function random_hex(length) {
let bytes = new Uint8Array(Math.ceil(length / 2)); let bytes = new Uint8Array(Math.ceil(length / 2));
window.crypto.getRandomValues(bytes); window.crypto.getRandomValues(bytes);
return bytes.reduce((s, b) => s + b.toString(16).padStart(2, "0"), "").substring(0, length); // Fallback for compatibility with older browsers which may not have padStart
return bytes.reduce((s, b) => {
let hex = b.toString(16);
return s + (String.prototype["padStart"] ? hex["padStart"](2, "0") : ("0" + hex).slice(-2));
}, "").substring(0, length);
} }
/** /**
* Removed invalid HREF characters for a collection HREF. * Removed invalid HREF characters for a collection HREF.
* * @param {HTMLInputElement} href_form A valid Input element or an onchange Event of an Input element.
* @param a A valid Input element or an onchange Event of an Input element.
*/ */
export function cleanHREFinput(a) { export function cleanHREFinput(href_form) {
let href_form = a;
if (a.target) {
href_form = a.target;
}
let currentTxtVal = href_form.value.trim().toLowerCase(); let currentTxtVal = href_form.value.trim().toLowerCase();
//Clean the HREF to remove not permitted chars //Clean the HREF to remove not permitted chars
currentTxtVal = currentTxtVal.replace(/(?![0-9a-z\-\_\.])./g, ''); currentTxtVal = currentTxtVal.replace(/(?![0-9a-z\-\_\.])./g, '');
@@ -68,11 +67,19 @@ export function cleanHREFinput(a) {
href_form.value = currentTxtVal; href_form.value = currentTxtVal;
} }
/**
* Event listener for cleaning HREF input.
* @param {Event} event
*/
export function onCleanHREFinput(event) {
if (event.target instanceof HTMLInputElement) {
cleanHREFinput(event.target);
}
}
/** /**
* Checks if a proposed HREF for a collection has a valid format and syntax. * Checks if a proposed HREF for a collection has a valid format and syntax.
* * @param {string} href String of the proposed HREF.
* @param href String of the porposed HREF.
*
* @return Boolean results if the HREF is valid. * @return Boolean results if the HREF is valid.
*/ */
export function isValidHREF(href) { export function isValidHREF(href) {
@@ -88,16 +95,15 @@ export function isValidHREF(href) {
/** /**
* Format bytes to human-readable text. * Format bytes to human-readable text.
* * @param {number} bytes Number of bytes.
* @param bytes Number of bytes.
*
* @return Formatted string. * @return Formatted string.
*/ */
export function bytesToHumanReadable(bytes, dp=1) { export function bytesToHumanReadable(bytes) {
let isNumber = !isNaN(parseFloat(bytes)) && !isNaN(bytes - 0); if (isNaN(bytes - 0)) {
if(!isNumber){
return ""; return "";
} }
var i = bytes == 0 ? 0 : Math.floor(Math.log(bytes) / Math.log(1024)); const units = ['b', 'kb', 'mb', 'gb', 'tb'];
return (bytes / Math.pow(1024, i)).toFixed(dp) * 1 + ' ' + ['b', 'kb', 'mb', 'gb', 'tb'][i]; let i = bytes == 0 ? 0 : Math.floor(Math.log(bytes) / Math.log(1024));
i = Math.min(i, units.length - 1);
return (bytes / Math.pow(1024, i)) + ' ' + units[i];
} }