From 3d63fde28398e2b3da1816e7018b4e8d4509c652 Mon Sep 17 00:00:00 2001 From: ShareVB Date: Mon, 6 May 2024 00:14:13 +0200 Subject: [PATCH] feat(Chmod Calculator): implement symbol form input Fix #418 and #522 --- components.d.ts | 3 + .../chmod-calculator.service.test.ts | 117 +++++++++++++++++- .../chmod-calculator.service.ts | 27 +++- .../chmod-calculator/chmod-calculator.vue | 56 +++++++-- src/tools/chmod-calculator/index.ts | 5 +- 5 files changed, 195 insertions(+), 13 deletions(-) diff --git a/components.d.ts b/components.d.ts index e31119b3..39fe7154 100644 --- a/components.d.ts +++ b/components.d.ts @@ -127,6 +127,7 @@ declare module '@vue/runtime-core' { MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default'] MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default'] NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default'] + NCheckbox: typeof import('naive-ui')['NCheckbox'] NCode: typeof import('naive-ui')['NCode'] NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] NConfigProvider: typeof import('naive-ui')['NConfigProvider'] @@ -145,6 +146,7 @@ declare module '@vue/runtime-core' { NMenu: typeof import('naive-ui')['NMenu'] NScrollbar: typeof import('naive-ui')['NScrollbar'] NSpin: typeof import('naive-ui')['NSpin'] + NTable: typeof import('naive-ui')['NTable'] NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default'] OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default'] PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default'] @@ -159,6 +161,7 @@ declare module '@vue/runtime-core' { RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] RsaKeyPairGenerator: typeof import('./src/tools/rsa-key-pair-generator/rsa-key-pair-generator.vue')['default'] + SafelinkDecoder: typeof import('./src/tools/safelink-decoder/safelink-decoder.vue')['default'] SlugifyString: typeof import('./src/tools/slugify-string/slugify-string.vue')['default'] SpanCopyable: typeof import('./src/components/SpanCopyable.vue')['default'] SqlPrettify: typeof import('./src/tools/sql-prettify/sql-prettify.vue')['default'] diff --git a/src/tools/chmod-calculator/chmod-calculator.service.test.ts b/src/tools/chmod-calculator/chmod-calculator.service.test.ts index 5264734c..a7f94ca8 100644 --- a/src/tools/chmod-calculator/chmod-calculator.service.test.ts +++ b/src/tools/chmod-calculator/chmod-calculator.service.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { computeChmodOctalRepresentation, computeChmodSymbolicRepresentation, computePermissionsFromChmodOctalRepresentation } from './chmod-calculator.service'; +import { computeChmodOctalRepresentation, computeChmodSymbolicRepresentation, computePermissionsFromChmodOctalRepresentation, computePermissionsFromChmodSymbolicRepresentation } from './chmod-calculator.service'; describe('chmod-calculator', () => { describe('computeChmodOctalRepresentation', () => { @@ -339,4 +339,119 @@ describe('chmod-calculator', () => { }); }); }); + describe('computePermissionsFromChmodSymbolicRepresentation', () => { + it('throws on invalid symbolic values', () => { + expect(() => computePermissionsFromChmodSymbolicRepresentation('rr---')).to.throw(); + expect(() => computePermissionsFromChmodSymbolicRepresentation('rwxrwx--w')).to.throw(); + }); + + it('get permissions from symbolic representation', () => { + expect( + computePermissionsFromChmodSymbolicRepresentation('dr-xr-xr-x'), + ).to.eql({ + owner: { read: true, write: false, execute: true }, + group: { read: true, write: false, execute: true }, + public: { read: true, write: false, execute: true }, + flags: { setuid: false, setgid: false, stickybit: false }, + }); + expect( + computePermissionsFromChmodSymbolicRepresentation('-rw-r--r--'), + ).to.eql({ + owner: { read: true, write: true, execute: false }, + group: { read: true, write: false, execute: false }, + public: { read: true, write: false, execute: false }, + flags: { setuid: false, setgid: false, stickybit: false }, + }); + + expect( + computePermissionsFromChmodSymbolicRepresentation('rwxrwxrwx'), + ).to.eql({ + owner: { read: true, write: true, execute: true }, + group: { read: true, write: true, execute: true }, + public: { read: true, write: true, execute: true }, + flags: { setuid: false, setgid: false, stickybit: false }, + }); + + expect( + computePermissionsFromChmodSymbolicRepresentation('---------'), + ).to.eql({ + owner: { read: false, write: false, execute: false }, + group: { read: false, write: false, execute: false }, + public: { read: false, write: false, execute: false }, + flags: { setuid: false, setgid: false, stickybit: false }, + }); + + expect( + computePermissionsFromChmodSymbolicRepresentation('r---wxr-x'), + ).to.eql({ + owner: { read: true, write: false, execute: false }, + group: { read: false, write: true, execute: true }, + public: { read: true, write: false, execute: true }, + flags: { setuid: false, setgid: false, stickybit: false }, + }); + + expect( + computePermissionsFromChmodSymbolicRepresentation('r---w---x'), + ).to.eql({ + owner: { read: true, write: false, execute: false }, + group: { read: false, write: true, execute: false }, + public: { read: false, write: false, execute: true }, + flags: { setuid: false, setgid: false, stickybit: false }, + }); + + expect( + computePermissionsFromChmodSymbolicRepresentation('--x-w-r--'), + ).to.eql({ + owner: { read: false, write: false, execute: true }, + group: { read: false, write: true, execute: false }, + public: { read: true, write: false, execute: false }, + flags: { setuid: false, setgid: false, stickybit: false }, + }); + + expect( + computePermissionsFromChmodSymbolicRepresentation('-w--w--w-'), + ).to.eql({ + owner: { read: false, write: true, execute: false }, + group: { read: false, write: true, execute: false }, + public: { read: false, write: true, execute: false }, + flags: { setuid: false, setgid: false, stickybit: false }, + }); + + expect( + computePermissionsFromChmodSymbolicRepresentation('-ws-ws-wt'), + ).to.eql({ + owner: { read: false, write: true, execute: true }, + group: { read: false, write: true, execute: true }, + public: { read: false, write: true, execute: true }, + flags: { setuid: true, setgid: true, stickybit: true }, + }); + + expect( + computePermissionsFromChmodSymbolicRepresentation('-ws-w--w-'), + ).to.eql({ + owner: { read: false, write: true, execute: true }, + group: { read: false, write: true, execute: false }, + public: { read: false, write: true, execute: false }, + flags: { setuid: true, setgid: false, stickybit: false }, + }); + + expect( + computePermissionsFromChmodSymbolicRepresentation('-w--ws-w-'), + ).to.eql({ + owner: { read: false, write: true, execute: false }, + group: { read: false, write: true, execute: true }, + public: { read: false, write: true, execute: false }, + flags: { setuid: false, setgid: true, stickybit: false }, + }); + + expect( + computePermissionsFromChmodSymbolicRepresentation('-w--w--wt'), + ).to.eql({ + owner: { read: false, write: true, execute: false }, + group: { read: false, write: true, execute: false }, + public: { read: false, write: true, execute: true }, + flags: { setuid: false, setgid: false, stickybit: true }, + }); + }); + }); }); diff --git a/src/tools/chmod-calculator/chmod-calculator.service.ts b/src/tools/chmod-calculator/chmod-calculator.service.ts index 511745f9..9a1ced4c 100644 --- a/src/tools/chmod-calculator/chmod-calculator.service.ts +++ b/src/tools/chmod-calculator/chmod-calculator.service.ts @@ -1,7 +1,7 @@ import _ from 'lodash'; import type { GroupPermissions, Permissions, SpecialPermissions } from './chmod-calculator.types'; -export { computeChmodOctalRepresentation, computeChmodSymbolicRepresentation, computePermissionsFromChmodOctalRepresentation }; +export { computeChmodOctalRepresentation, computeChmodSymbolicRepresentation, computePermissionsFromChmodOctalRepresentation, computePermissionsFromChmodSymbolicRepresentation }; function computeChmodOctalRepresentation({ permissions }: { permissions: Permissions }): string { const permissionValue = { read: 4, write: 2, execute: 1 }; @@ -61,3 +61,28 @@ function computePermissionsFromChmodOctalRepresentation(octalPermissions: string flags: computePermissionObject(specialPermissionValue, flagsPosition), }; } + +function computePermissionsFromChmodSymbolicRepresentation(symbolicPermissions: string): Permissions { + const formatRegex = /^[-dlbcsp]?([r-])([w-])([xs-])([r-])([w-])([xs-])([r-])([w-])([xt-])$/; + if (!symbolicPermissions || !symbolicPermissions.match(formatRegex)) { + throw new Error(`Invalid string permissions (must be in form 'rwxrwxrwx'): ${symbolicPermissions}`); + } + + const [_, rOwner, wOwner, xOwner, rGroup, wGroup, xGroup, rAll, wAll, xAll] = formatRegex.exec(symbolicPermissions) || []; + const getOctal = (flag: string, flagLetter: string, flagValue: number) => flag === flagLetter ? flagValue : 0; + const owner = getOctal(rOwner, 'r', 4) + + getOctal(wOwner, 'w', 2) + + getOctal(xOwner, 'x', 1) + getOctal(xOwner, 's', 1); + const groups = getOctal(rGroup, 'r', 4) + + getOctal(wGroup, 'w', 2) + + getOctal(xGroup, 'x', 1) + getOctal(xGroup, 's', 1); + const all = getOctal(rAll, 'r', 4) + + getOctal(wAll, 'w', 2) + + getOctal(xAll, 'x', 1) + getOctal(xAll, 't', 1); + const flags = getOctal(xOwner, 's', 4) + + getOctal(xGroup, 's', 2) + + getOctal(xAll, 't', 1); + const octalString = `${(flags > 0 ? flags : '')}${owner}${groups}${all}`; + + return computePermissionsFromChmodOctalRepresentation(octalString); +} diff --git a/src/tools/chmod-calculator/chmod-calculator.vue b/src/tools/chmod-calculator/chmod-calculator.vue index 93f9f3ae..ed7ec9cb 100644 --- a/src/tools/chmod-calculator/chmod-calculator.vue +++ b/src/tools/chmod-calculator/chmod-calculator.vue @@ -2,7 +2,7 @@ import { useThemeVars } from 'naive-ui'; import InputCopyable from '../../components/InputCopyable.vue'; -import { computeChmodOctalRepresentation, computeChmodSymbolicRepresentation, computePermissionsFromChmodOctalRepresentation } from './chmod-calculator.service'; +import { computeChmodOctalRepresentation, computeChmodSymbolicRepresentation, computePermissionsFromChmodOctalRepresentation, computePermissionsFromChmodSymbolicRepresentation } from './chmod-calculator.service'; import type { Group, Scope } from './chmod-calculator.types'; import { useValidation } from '@/composable/validation'; @@ -23,9 +23,9 @@ const permissions = ref({ flags: { setuid: false, setgid: false, stickybit: false }, }); -const permissionsInput = ref('000'); -const permissionsInputValidation = useValidation({ - source: permissionsInput, +const octalPermissionsInput = ref('000'); +const octalPermissionsInputValidation = useValidation({ + source: octalPermissionsInput, rules: [ { message: 'Invalid octal permission string', @@ -42,15 +42,43 @@ const permissionsInputValidation = useValidation({ ], }); watch( - permissionsInput, + octalPermissionsInput, (newPermissions) => { - if (!permissionsInputValidation.isValid) { + if (!octalPermissionsInputValidation.isValid) { return; } permissions.value = computePermissionsFromChmodOctalRepresentation(newPermissions.trim()); }, ); +const symbolicPermissionsInput = ref('---------'); +const symbolicPermissionsInputValidation = useValidation({ + source: symbolicPermissionsInput, + rules: [ + { + message: 'Invalid symbolic permission string', + validator: (value) => { + try { + computePermissionsFromChmodSymbolicRepresentation(value.trim()); + return true; + } + catch { + return false; + } + }, + }, + ], +}); +watch( + symbolicPermissionsInput, + (newPermissions) => { + if (!symbolicPermissionsInputValidation.isValid) { + return; + } + permissions.value = computePermissionsFromChmodSymbolicRepresentation(newPermissions.trim()); + }, +); + const octal = computed(() => computeChmodOctalRepresentation({ permissions: permissions.value })); const symbolic = computed(() => computeChmodSymbolicRepresentation({ permissions: permissions.value })); @@ -58,13 +86,25 @@ const symbolic = computed(() => computeChmodSymbolicRepresentation({ permissions