Quellcode durchsuchen

add custom alphabet for link address

Pouria Ezzati vor 1 Jahr
Ursprung
Commit
028b8a0e57
4 geänderte Dateien mit 21 neuen und 6 gelöschten Zeilen
  1. 4 0
      .example.env
  2. 6 0
      server/env.js
  3. 2 2
      server/handlers/validators.handler.js
  4. 9 4
      server/utils/utils.js

+ 4 - 0
.example.env

@@ -26,6 +26,10 @@ DB_POOL_MAX=10
 # Optional - Generated link length
 LINK_LENGTH=6
 
+# Optional - Alphabet used to generate custom addresses
+# Default value omits o, O, 0, i, I, l, 1, and j to avoid confusion when reading the URL
+LINK_CUSTOM_ALPHABET=abcdefghkmnpqrstuvwxyzABCDEFGHKLMNPQRSTUVWXYZ23456789
+
 # Optional - Redis host and port
 REDIS_ENABLED=false
 REDIS_HOST=127.0.0.1

+ 6 - 0
server/env.js

@@ -10,11 +10,17 @@ const supportedDBClients = [
   "mysql2"
 ];
 
+// make sure custom alphabet is not empty
+if (process.env.LINK_CUSTOM_ALPHABET === "") {
+  delete process.env.LINK_CUSTOM_ALPHABET;
+}
+
 const env = cleanEnv(process.env, {
   PORT: num({ default: 3000 }),
   SITE_NAME: str({ example: "Kutt", default: "Kutt" }),
   DEFAULT_DOMAIN: str({ example: "kutt.it", default: "localhost:3000" }),
   LINK_LENGTH: num({ default: 6 }),
+  LINK_CUSTOM_ALPHABET: str({ default: "abcdefghkmnpqrstuvwxyzABCDEFGHKLMNPQRSTUVWXYZ23456789" }),
   DB_CLIENT: str({ choices: supportedDBClients, default: "sqlite3" }),
   DB_FILENAME: str({ default: "db/data" }),
   DB_HOST: str({ default: "localhost" }),

+ 2 - 2
server/handlers/validators.handler.js

@@ -45,7 +45,7 @@ const createLink = [
     .trim()
     .isLength({ min: 1, max: 64 })
     .withMessage("Custom URL length must be between 1 and 64.")
-    .custom(value => /^[a-zA-Z0-9-_]+$/g.test(value))
+    .custom(value => utils.customAddressRegex.test(value) || utils.customAlphabetRegex.test(value))
     .withMessage("Custom URL is not valid.")
     .custom(value => !utils.preservedURLs.some(url => url.toLowerCase() === value))
     .withMessage("You can't use this custom URL."),
@@ -120,7 +120,7 @@ const editLink = [
     .trim()
     .isLength({ min: 1, max: 64 })
     .withMessage("Custom URL length must be between 1 and 64.")
-    .custom(value => /^[a-zA-Z0-9-_]+$/g.test(value))
+    .custom(value => utils.customAddressRegex.test(value) || utils.customAlphabetRegex.test(value))
     .withMessage("Custom URL is not valid")
     .custom(value => !utils.preservedURLs.some(url => url.toLowerCase() === value))
     .withMessage("You can't use this custom URL."),

+ 9 - 4
server/utils/utils.js

@@ -11,10 +11,7 @@ const knexUtils = require("./knex");
 const knex = require("../knex");
 const env = require("../env");
 
-const nanoid = customAlphabet(
-  "abcdefghkmnpqrstuvwxyzABCDEFGHKLMNPQRSTUVWXYZ23456789",
-  env.LINK_LENGTH
-);
+const nanoid = customAlphabet(env.LINK_CUSTOM_ALPHABET, env.LINK_LENGTH);
 
 class CustomError extends Error {
   constructor(message, statusCode, data) {
@@ -27,6 +24,12 @@ class CustomError extends Error {
 
 const urlRegex = /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62})?[a-z0-9\u00a1-\uffff]\.)+(?:[a-z\u00a1-\uffff]{2,}\.?))(?::\d{2,5})?(?:[/?#]\S*)?$/i;
 
+const charsNeedEscapeInRegExp = ".$*+?()[]{}|^-";
+const customAlphabetEscaped = env.LINK_CUSTOM_ALPHABET
+  .split("").map(c => charsNeedEscapeInRegExp.includes(c) ? "\\" + c : c).join("");
+const customAlphabetRegex = new RegExp(`^[${customAlphabetEscaped}_-]+$`);
+const customAddressRegex = new RegExp("^[a-zA-Z0-9-_]+$");
+
 function isAdmin(user) {
   return user.role === ROLES.ADMIN;
 }
@@ -390,6 +393,8 @@ function getCustomCSSFileNames() {
 
 module.exports = {
   addProtocol,
+  customAddressRegex,
+  customAlphabetRegex,
   CustomError,
   dateToUTC,
   deleteCurrentToken,