diff --git a/components.d.ts b/components.d.ts index 89f41f808..e9b50afa2 100644 --- a/components.d.ts +++ b/components.d.ts @@ -129,6 +129,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'] @@ -143,6 +144,8 @@ declare module '@vue/runtime-core' { NLayoutSider: typeof import('naive-ui')['NLayoutSider'] NMenu: typeof import('naive-ui')['NMenu'] NScrollbar: typeof import('naive-ui')['NScrollbar'] + NSpin: typeof import('naive-ui')['NSpin'] + NTable: typeof import('naive-ui')['NTable'] NSlider: typeof import('naive-ui')['NSlider'] NSwitch: typeof import('naive-ui')['NSwitch'] NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.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 426c4a56c..1a994de8a 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 } from './chmod-calculator.service'; +import { computeChmodOctalRepresentation, computeChmodSymbolicRepresentation, computePermissionsFromChmodOctalRepresentation, computePermissionsFromChmodSymbolicRepresentation, computeUmaskRepresentation } from './chmod-calculator.service'; describe('chmod-calculator', () => { describe('computeChmodOctalRepresentation', () => { @@ -10,6 +10,7 @@ describe('chmod-calculator', () => { 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 }, }, }), ).to.eql('777'); @@ -20,6 +21,7 @@ describe('chmod-calculator', () => { 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 }, }, }), ).to.eql('000'); @@ -30,6 +32,7 @@ describe('chmod-calculator', () => { owner: { read: false, write: true, execute: false }, group: { read: false, write: true, execute: true }, public: { read: true, write: false, execute: true }, + flags: { setuid: false, setgid: false, stickybit: false }, }, }), ).to.eql('235'); @@ -40,6 +43,7 @@ describe('chmod-calculator', () => { 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 }, }, }), ).to.eql('421'); @@ -50,6 +54,7 @@ describe('chmod-calculator', () => { 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 }, }, }), ).to.eql('124'); @@ -60,11 +65,57 @@ describe('chmod-calculator', () => { 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 }, }, }), ).to.eql('222'); - }); + expect( + computeChmodOctalRepresentation({ + permissions: { + owner: { read: false, write: true, execute: false }, + group: { read: false, write: true, execute: false }, + public: { read: false, write: true, execute: false }, + flags: { setuid: true, setgid: true, stickybit: true }, + }, + }), + ).to.eql('7222'); + + expect( + computeChmodOctalRepresentation({ + permissions: { + owner: { read: false, write: true, execute: false }, + group: { read: false, write: true, execute: false }, + public: { read: false, write: true, execute: false }, + flags: { setuid: true, setgid: false, stickybit: false }, + }, + }), + ).to.eql('4222'); + + expect( + computeChmodOctalRepresentation({ + permissions: { + 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: true, stickybit: false }, + }, + }), + ).to.eql('2222'); + + expect( + computeChmodOctalRepresentation({ + permissions: { + 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: true }, + }, + }), + ).to.eql('1222'); + }); + }); + describe('computeChmodSymbolicRepresentation', () => { it('get the symbolic representation from permissions', () => { expect( computeChmodSymbolicRepresentation({ @@ -72,6 +123,7 @@ describe('chmod-calculator', () => { 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 }, }, }), ).to.eql('rwxrwxrwx'); @@ -82,6 +134,7 @@ describe('chmod-calculator', () => { 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 }, }, }), ).to.eql('---------'); @@ -92,6 +145,7 @@ describe('chmod-calculator', () => { owner: { read: false, write: true, execute: false }, group: { read: false, write: true, execute: true }, public: { read: true, write: false, execute: true }, + flags: { setuid: false, setgid: false, stickybit: false }, }, }), ).to.eql('-w--wxr-x'); @@ -102,6 +156,7 @@ describe('chmod-calculator', () => { 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 }, }, }), ).to.eql('r---w---x'); @@ -112,6 +167,7 @@ describe('chmod-calculator', () => { 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 }, }, }), ).to.eql('--x-w-r--'); @@ -122,9 +178,382 @@ describe('chmod-calculator', () => { 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 }, }, }), ).to.eql('-w--w--w-'); + + expect( + computeChmodSymbolicRepresentation({ + permissions: { + 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: true }, + }, + }), + ).to.eql('--x-w-r-t'); + + expect( + computeChmodSymbolicRepresentation({ + permissions: { + 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: true, stickybit: true }, + }, + }), + ).to.eql('--x-wsr-t'); + + expect( + computeChmodSymbolicRepresentation({ + permissions: { + owner: { read: false, write: false, execute: true }, + group: { read: false, write: true, execute: false }, + public: { read: true, write: false, execute: false }, + flags: { setuid: true, setgid: true, stickybit: true }, + }, + }), + ).to.eql('--s-wsr-t'); + + expect( + computeChmodSymbolicRepresentation({ + permissions: { + owner: { read: true, write: false, execute: true }, + group: { read: true, write: true, execute: false }, + public: { read: true, write: false, execute: false }, + flags: { setuid: false, setgid: false, stickybit: false }, + }, + }), + ).to.eql('r-xrw-r--'); + + expect( + computeChmodSymbolicRepresentation({ + permissions: { + owner: { read: true, write: true, execute: true }, + group: { read: true, write: true, execute: true }, + public: { read: true, write: true, execute: true }, + flags: { setuid: true, setgid: true, stickybit: true }, + }, + }), + ).to.eql('rwsrwsrwt'); + }); + }); + describe('computePermissionsFromChmodOctalRepresentation', () => { + it('throws on invalid octal values', () => { + expect(() => computePermissionsFromChmodOctalRepresentation('12')).to.throw(); + expect(() => computePermissionsFromChmodOctalRepresentation('12345')).to.throw(); + expect(() => computePermissionsFromChmodOctalRepresentation('999')).to.throw(); + expect(() => computePermissionsFromChmodOctalRepresentation('9999')).to.throw(); + }); + + it('get permissions from octal representation', () => { + expect( + computePermissionsFromChmodOctalRepresentation('777'), + ).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( + computePermissionsFromChmodOctalRepresentation('000'), + ).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( + computePermissionsFromChmodOctalRepresentation('235'), + ).to.eql({ + owner: { read: false, write: true, execute: false }, + group: { read: false, write: true, execute: true }, + public: { read: true, write: false, execute: true }, + flags: { setuid: false, setgid: false, stickybit: false }, + }); + + expect( + computePermissionsFromChmodOctalRepresentation('421'), + ).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( + computePermissionsFromChmodOctalRepresentation('124'), + ).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( + computePermissionsFromChmodOctalRepresentation('222'), + ).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( + computePermissionsFromChmodOctalRepresentation('7222'), + ).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: true, setgid: true, stickybit: true }, + }); + + expect( + computePermissionsFromChmodOctalRepresentation('4222'), + ).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: true, setgid: false, stickybit: false }, + }); + + expect( + computePermissionsFromChmodOctalRepresentation('2222'), + ).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: true, stickybit: false }, + }); + + expect( + computePermissionsFromChmodOctalRepresentation('1222'), + ).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: true }, + }); + }); + }); + 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 }, + }); + }); + }); + + describe('computeUmaskRepresentation', () => { + it('get the umask from permissions', () => { + expect( + computeUmaskRepresentation({ + permissions: { + 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 }, + }, + }), + ).to.deep.eq({ + octal: '000', + symbolic: 'umask u=rwx,g=rwx,o=rwx', + }); + + expect( + computeUmaskRepresentation({ + permissions: { + 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 }, + }, + }), + ).to.deep.eq({ + octal: '777', + symbolic: 'umask u=,g=,o=', + }); + + expect( + computeUmaskRepresentation({ + permissions: { + owner: { read: false, write: true, execute: false }, + group: { read: false, write: true, execute: true }, + public: { read: true, write: false, execute: true }, + flags: { setuid: false, setgid: false, stickybit: false }, + }, + }), + ).to.deep.eq({ + octal: '542', + symbolic: 'umask u=w,g=wx,o=rx', + }); + + expect( + computeUmaskRepresentation({ + permissions: { + 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 }, + }, + }), + ).to.deep.eq({ + octal: '356', + symbolic: 'umask u=r,g=w,o=x', + }); + + expect( + computeUmaskRepresentation({ + permissions: { + 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 }, + }, + }), + ).to.deep.eq({ + octal: '653', + symbolic: 'umask u=x,g=w,o=r', + }); + + expect( + computeUmaskRepresentation({ + permissions: { + 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 }, + }, + }), + ).to.deep.eq({ + octal: '555', + symbolic: 'umask u=w,g=w,o=w', + }); + + expect( + computeUmaskRepresentation({ + permissions: { + owner: { read: false, write: false, execute: true }, + group: { read: false, write: true, execute: false }, + public: { read: true, write: false, execute: false }, + flags: { setuid: true, setgid: true, stickybit: true }, + }, + }), + ).to.deep.eq({ + octal: '653', + symbolic: 'umask u=x,g=w,o=r', + }); }); }); }); diff --git a/src/tools/chmod-calculator/chmod-calculator.service.ts b/src/tools/chmod-calculator/chmod-calculator.service.ts index e00467179..e8a069f6e 100644 --- a/src/tools/chmod-calculator/chmod-calculator.service.ts +++ b/src/tools/chmod-calculator/chmod-calculator.service.ts @@ -1,15 +1,21 @@ import _ from 'lodash'; -import type { GroupPermissions, Permissions } from './chmod-calculator.types'; +import type { GroupPermissions, Permissions, SpecialPermissions } from './chmod-calculator.types'; -export { computeChmodOctalRepresentation, computeChmodSymbolicRepresentation }; +export { computeUmaskRepresentation, computeChmodOctalRepresentation, computeChmodSymbolicRepresentation, computePermissionsFromChmodOctalRepresentation, computePermissionsFromChmodSymbolicRepresentation }; function computeChmodOctalRepresentation({ permissions }: { permissions: Permissions }): string { const permissionValue = { read: 4, write: 2, execute: 1 }; + const specialPermissionValue = { setuid: 4, setgid: 2, stickybit: 1 }; const getGroupPermissionValue = (permission: GroupPermissions) => _.reduce(permission, (acc, isPermSet, key) => acc + (isPermSet ? _.get(permissionValue, key, 0) : 0), 0); + const getSpecialPermissionValue = (permission: SpecialPermissions) => { + const octalValue = _.reduce(permission, (acc, isPermSet, key) => acc + (isPermSet ? _.get(specialPermissionValue, key, 0) : 0), 0); + return octalValue > 0 ? octalValue.toString() : ''; + }; return [ + getSpecialPermissionValue(permissions.flags || { setuid: false, setgid: false, stickybit: false }), getGroupPermissionValue(permissions.owner), getGroupPermissionValue(permissions.group), getGroupPermissionValue(permissions.public), @@ -18,13 +24,92 @@ function computeChmodOctalRepresentation({ permissions }: { permissions: Permiss function computeChmodSymbolicRepresentation({ permissions }: { permissions: Permissions }): string { const permissionValue = { read: 'r', write: 'w', execute: 'x' }; + const specialFlagPermission = 'execute'; - const getGroupPermissionValue = (permission: GroupPermissions) => - _.reduce(permission, (acc, isPermSet, key) => acc + (isPermSet ? _.get(permissionValue, key, '') : '-'), ''); + const getGroupPermissionValue = (permission: GroupPermissions, specialFlag: null | 's' | 't') => + _.reduce(permission, (acc, isPermSet, key) => acc + ((key === specialFlagPermission ? specialFlag : undefined) + || (isPermSet ? _.get(permissionValue, key, '') : '-')), ''); return [ - getGroupPermissionValue(permissions.owner), - getGroupPermissionValue(permissions.group), - getGroupPermissionValue(permissions.public), + getGroupPermissionValue(permissions.owner, permissions.flags?.setuid ? 's' : null), + getGroupPermissionValue(permissions.group, permissions.flags?.setgid ? 's' : null), + getGroupPermissionValue(permissions.public, permissions.flags?.stickybit ? 't' : null), ].join(''); } + +function computePermissionsFromChmodOctalRepresentation(octalPermissions: string): Permissions { + const permissionValue = { read: 4, write: 2, execute: 1 }; + const specialPermissionValue = { setuid: 4, setgid: 2, stickybit: 1 }; + + if (!octalPermissions || !octalPermissions.match(/^[0-7]{3,4}$/)) { + throw new Error(`Invalid octal permissions (must be 3 or 4 octal digits): ${octalPermissions}`); + } + const fullOctalPermissions = octalPermissions.length === 3 ? `0${octalPermissions}` : octalPermissions; + + const hasSet = (position: number, flagValue: number) => (Number(fullOctalPermissions[position]) & flagValue) === flagValue; + function computePermissionObject(permissionSet: object, position: number): T { + return _.reduce(permissionSet, (acc, flag, key) => ({ ...acc, [key]: hasSet(position, flag) }), {}) as T; + } + const flagsPosition = 0; + const ownerPosition = 1; + const groupPosition = 2; + const publicPosition = 3; + return { + owner: computePermissionObject(permissionValue, ownerPosition), + group: computePermissionObject(permissionValue, groupPosition), + public: computePermissionObject(permissionValue, publicPosition), + 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); +} + +function computeUmaskRepresentation({ permissions }: { permissions: Permissions }): { + octal: string + symbolic: string +} { + const permissionValue = { read: 'r', write: 'w', execute: 'x' }; + const getGroupPermissionValue = (permission: GroupPermissions) => + _.reduce(permission, (acc, isPermSet, key) => acc + ((isPermSet ? _.get(permissionValue, key, '') : '')), ''); + + const symbolic = `umask u=${getGroupPermissionValue(permissions.owner)},g=${getGroupPermissionValue(permissions.group)},o=${getGroupPermissionValue(permissions.public)}`; + const octal = (0o777 - Number.parseInt( + computeChmodOctalRepresentation({ + permissions: + { + owner: permissions.owner, + group: permissions.group, + public: permissions.public, + flags: { setuid: false, setgid: false, stickybit: false }, + }, + }), 8)) + .toString(8) + .padStart(3, '0'); + + return { + symbolic, octal, + }; +} diff --git a/src/tools/chmod-calculator/chmod-calculator.types.ts b/src/tools/chmod-calculator/chmod-calculator.types.ts index d3dc915f9..30a7ad010 100644 --- a/src/tools/chmod-calculator/chmod-calculator.types.ts +++ b/src/tools/chmod-calculator/chmod-calculator.types.ts @@ -1,10 +1,17 @@ export type Scope = 'read' | 'write' | 'execute'; export type Group = 'owner' | 'group' | 'public'; +export type SpecialFlags = 'setuid' | 'setgid' | 'stickybit'; export type GroupPermissions = { [k in Scope]: boolean; }; +export type SpecialPermissions = { + [k in SpecialFlags]: boolean; +}; + export type Permissions = { [k in Group]: GroupPermissions; +} & { + flags: SpecialPermissions }; diff --git a/src/tools/chmod-calculator/chmod-calculator.vue b/src/tools/chmod-calculator/chmod-calculator.vue index ba6f4498e..502f84917 100644 --- a/src/tools/chmod-calculator/chmod-calculator.vue +++ b/src/tools/chmod-calculator/chmod-calculator.vue @@ -2,9 +2,10 @@ import { useThemeVars } from 'naive-ui'; import InputCopyable from '../../components/InputCopyable.vue'; -import { computeChmodOctalRepresentation, computeChmodSymbolicRepresentation } from './chmod-calculator.service'; +import { computeChmodOctalRepresentation, computeChmodSymbolicRepresentation, computePermissionsFromChmodOctalRepresentation, computePermissionsFromChmodSymbolicRepresentation, computeUmaskRepresentation } from './chmod-calculator.service'; import type { Group, Scope } from './chmod-calculator.types'; +import { useValidation } from '@/composable/validation'; const themeVars = useThemeVars(); @@ -19,14 +20,92 @@ const permissions = ref({ 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 }, }); +const octalPermissionsInput = ref('000'); +const octalPermissionsInputValidation = useValidation({ + source: octalPermissionsInput, + rules: [ + { + message: 'Invalid octal permission string', + validator: (value) => { + try { + computePermissionsFromChmodOctalRepresentation(value.trim()); + return true; + } + catch { + return false; + } + }, + }, + ], +}); +watch( + octalPermissionsInput, + (newPermissions) => { + 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 })); +const umask = computed(() => computeUmaskRepresentation({ permissions: permissions.value })); diff --git a/src/tools/chmod-calculator/index.ts b/src/tools/chmod-calculator/index.ts index e6b39df80..73e7d709d 100644 --- a/src/tools/chmod-calculator/index.ts +++ b/src/tools/chmod-calculator/index.ts @@ -1,11 +1,10 @@ import { FileInvoice } from '@vicons/tabler'; import { defineTool } from '../tool'; -import { translate } from '@/plugins/i18n.plugin'; export const tool = defineTool({ - name: translate('tools.chmod-calculator.title'), + name: 'Chmod calculator', path: '/chmod-calculator', - description: translate('tools.chmod-calculator.description'), + description: 'Compute your chmod permissions and commands with this online chmod calculator.', keywords: [ 'chmod', 'calculator', @@ -17,6 +16,7 @@ export const tool = defineTool({ 'recursive', 'generator', 'octal', + 'umask', ], component: () => import('./chmod-calculator.vue'), icon: FileInvoice,