diff --git a/src/tools/chmod-calculator/chmod-calculator.service.test.ts b/src/tools/chmod-calculator/chmod-calculator.service.test.ts index 426c4a56..4b52ee2e 100644 --- a/src/tools/chmod-calculator/chmod-calculator.service.test.ts +++ b/src/tools/chmod-calculator/chmod-calculator.service.test.ts @@ -1,5 +1,7 @@ import { describe, expect, it } from 'vitest'; -import { computeChmodOctalRepresentation, computeChmodSymbolicRepresentation } from './chmod-calculator.service'; +import { + checkSymbolicString, computeChmodOctalRepresentation, computeChmodSymbolicRepresentation, symbolicToOctal, +} from './chmod-calculator.service'; describe('chmod-calculator', () => { describe('computeChmodOctalRepresentation', () => { @@ -127,4 +129,175 @@ describe('chmod-calculator', () => { ).to.eql('-w--w--w-'); }); }); + describe('symbolicToNumeric', () => { + it('should return the correct octal value for symbolic strings', () => { + expect(symbolicToOctal('rwxrwxrwx')).toBe(777); + expect(symbolicToOctal('drwxrwxrwx')).toBe(777); + expect(symbolicToOctal('-rwxrwxrwx')).toBe(777); + expect(symbolicToOctal('lrwxrwxrwx')).toBe(777); + + expect(symbolicToOctal('rwxr-xr-x')).toBe(755); + expect(symbolicToOctal('drwxr-xr-x')).toBe(755); + expect(symbolicToOctal('-rwxr-xr-x')).toBe(755); + expect(symbolicToOctal('lrwxr-xr-x')).toBe(755); + + expect(symbolicToOctal('rwxrwxr-x')).toBe(775); + expect(symbolicToOctal('drwxrwxr-x')).toBe(775); + expect(symbolicToOctal('-rwxrwxr-x')).toBe(775); + expect(symbolicToOctal('lrwxrwxr-x')).toBe(775); + + expect(symbolicToOctal('rw-r--r--')).toBe(644); + expect(symbolicToOctal('drw-r--r--')).toBe(644); + expect(symbolicToOctal('-rw-r--r--')).toBe(644); + expect(symbolicToOctal('lrw-r--r--')).toBe(644); + + expect(symbolicToOctal('rw-------')).toBe(600); + expect(symbolicToOctal('drw-------')).toBe(600); + expect(symbolicToOctal('-rw-------')).toBe(600); + expect(symbolicToOctal('lrw-------')).toBe(600); + + expect(symbolicToOctal('rwx------')).toBe(700); + expect(symbolicToOctal('drwx------')).toBe(700); + expect(symbolicToOctal('-rwx------')).toBe(700); + expect(symbolicToOctal('lrwx------')).toBe(700); + + expect(symbolicToOctal('rw-rw-rw-')).toBe(666); + expect(symbolicToOctal('drw-rw-rw-')).toBe(666); + expect(symbolicToOctal('-rw-rw-rw-')).toBe(666); + expect(symbolicToOctal('lrw-rw-rw-')).toBe(666); + + expect(symbolicToOctal('r--------')).toBe(400); + expect(symbolicToOctal('dr--------')).toBe(400); + expect(symbolicToOctal('-r--------')).toBe(400); + expect(symbolicToOctal('lr--------')).toBe(400); + + expect(symbolicToOctal('rw-rw-r--')).toBe(664); + expect(symbolicToOctal('drw-rw-r--')).toBe(664); + expect(symbolicToOctal('-rw-rw-r--')).toBe(664); + expect(symbolicToOctal('lrw-rw-r--')).toBe(664); + + expect(symbolicToOctal('rwxr--r--')).toBe(744); + expect(symbolicToOctal('drwxr--r--')).toBe(744); + expect(symbolicToOctal('-rwxr--r--')).toBe(744); + expect(symbolicToOctal('lrwxr--r--')).toBe(744); + + expect(symbolicToOctal('rwxrwx---')).toBe(770); + expect(symbolicToOctal('drwxrwx---')).toBe(770); + expect(symbolicToOctal('-rwxrwx---')).toBe(770); + expect(symbolicToOctal('lrwxrwx---')).toBe(770); + + expect(symbolicToOctal('r--r--r--')).toBe(444); + expect(symbolicToOctal('dr--r--r--')).toBe(444); + expect(symbolicToOctal('-r--r--r--')).toBe(444); + expect(symbolicToOctal('lr--r--r--')).toBe(444); + + expect(symbolicToOctal('r-xr-xr-x')).toBe(555); + expect(symbolicToOctal('dr-xr-xr-x')).toBe(555); + expect(symbolicToOctal('-r-xr-xr-x')).toBe(555); + expect(symbolicToOctal('lr-xr-xr-x')).toBe(555); + + expect(symbolicToOctal('---------')).toBe(0o00); + expect(symbolicToOctal('d---------')).toBe(0o00); + expect(symbolicToOctal('----------')).toBe(0o00); + expect(symbolicToOctal('l---------')).toBe(0o00); + }); + }); + + describe('validateSymbolicInput', () => { + it('should return a non-empty error message for invalid duplicate entity permissions', () => { + expect(checkSymbolicString('rrwrwxrwx')).not.toBe(''); + expect(checkSymbolicString('drrwrwxrwx')).not.toBe(''); + expect(checkSymbolicString('-rrwrwxrwx')).not.toBe(''); + expect(checkSymbolicString('lrrwrwxrwx')).not.toBe(''); + + expect(checkSymbolicString('rrwxrwxrw')).not.toBe(''); + expect(checkSymbolicString('drrwxrwxrw')).not.toBe(''); + expect(checkSymbolicString('-rrwxrwxrw')).not.toBe(''); + expect(checkSymbolicString('lrrwxrwxrw')).not.toBe(''); + + expect(checkSymbolicString('rwrwrrxwx')).not.toBe(''); + expect(checkSymbolicString('drwrwrrxwx')).not.toBe(''); + expect(checkSymbolicString('-rwrwrrxwx')).not.toBe(''); + expect(checkSymbolicString('lwrwrrxwx')).not.toBe(''); + + expect(checkSymbolicString('rwrwxrrwx')).not.toBe(''); + expect(checkSymbolicString('drwrwxrrwx')).not.toBe(''); + expect(checkSymbolicString('-rwrwxrrwx')).not.toBe(''); + expect(checkSymbolicString('lrwrwxrrwx')).not.toBe(''); + + expect(checkSymbolicString('rwxrrwrwx')).not.toBe(''); + expect(checkSymbolicString('drwxrrwrwx')).not.toBe(''); + expect(checkSymbolicString('-rwxrrwrwx')).not.toBe(''); + expect(checkSymbolicString('lwxrrwrwx')).not.toBe(''); + + expect(checkSymbolicString('rwxrwrwrx')).not.toBe(''); + expect(checkSymbolicString('drwxrwrwrx')).not.toBe(''); + expect(checkSymbolicString('-rwxrwrwrx')).not.toBe(''); + expect(checkSymbolicString('lwxrwrwrx')).not.toBe(''); + + expect(checkSymbolicString('rrwrwrwxw')).not.toBe(''); + expect(checkSymbolicString('drrwrwrwxw')).not.toBe(''); + expect(checkSymbolicString('-rrwrwrwxw')).not.toBe(''); + expect(checkSymbolicString('lrrwrwrwxw')).not.toBe(''); + + expect(checkSymbolicString('rrwxwrrwx')).not.toBe(''); + expect(checkSymbolicString('drrwxwrrwx')).not.toBe(''); + expect(checkSymbolicString('-rrwxwrrwx')).not.toBe(''); + expect(checkSymbolicString('lrrwxwrrwx')).not.toBe(''); + + expect(checkSymbolicString('rwrwrrwxr')).not.toBe(''); + expect(checkSymbolicString('drwrwrrwxr')).not.toBe(''); + expect(checkSymbolicString('-rwrwrrwxr')).not.toBe(''); + expect(checkSymbolicString('lwrwrrwxr')).not.toBe(''); + + expect(checkSymbolicString('rwrwxrwrw')).not.toBe(''); + expect(checkSymbolicString('drwrwxrwrw')).not.toBe(''); + expect(checkSymbolicString('-rwrwxrwrw')).not.toBe(''); + expect(checkSymbolicString('lwrwxrwrw')).not.toBe(''); + + expect(checkSymbolicString('rwxrrwxrw')).not.toBe(''); + expect(checkSymbolicString('drwxrrwxrw')).not.toBe(''); + expect(checkSymbolicString('-rwxrrwxrw')).not.toBe(''); + expect(checkSymbolicString('lrwxrrwxrw')).not.toBe(''); + + expect(checkSymbolicString('rww--rwrw-')).not.toBe(''); + expect(checkSymbolicString('drww--rwrw-')).not.toBe(''); + expect(checkSymbolicString('-rww--rwrw-')).not.toBe(''); + expect(checkSymbolicString('lrww--rwrw-')).not.toBe(''); + + expect(checkSymbolicString('r--wrrwxr')).not.toBe(''); + expect(checkSymbolicString('dr--wrrwxr')).not.toBe(''); + expect(checkSymbolicString('-r--wrrwxr')).not.toBe(''); + expect(checkSymbolicString('lr--wrrwxr')).not.toBe(''); + + expect(checkSymbolicString('rw--rrwxr-')).not.toBe(''); + expect(checkSymbolicString('drw--rrwxr-')).not.toBe(''); + expect(checkSymbolicString('-rw--rrwxr-')).not.toBe(''); + expect(checkSymbolicString('lrw--rrwxr-')).not.toBe(''); + }); + }); + describe('validateSymbolicInput', () => { + describe('validateOrder', () => { + it('should correctly validate the order of permissions', () => { + expect(checkSymbolicString('-rwxrwxrwx')).toBe(''); + expect(checkSymbolicString('drwxrwxrwx')).toBe(''); + expect(checkSymbolicString('lrwxrwxrwx')).toBe(''); + expect(checkSymbolicString('brwxrwxrwx')).toBe(''); + expect(checkSymbolicString('crwxrwxrwx')).toBe(''); + expect(checkSymbolicString('srwxrwxrwx')).toBe(''); + expect(checkSymbolicString('prwxrwxrwx')).toBe(''); + + expect(checkSymbolicString('rwxrwxrwx')).toBe(''); + expect(checkSymbolicString('rw-r--r--')).toBe(''); + expect(checkSymbolicString('r-xr-xr-x')).toBe(''); + expect(checkSymbolicString('-wxrw-r-x')).toBe(''); + expect(checkSymbolicString('rwxrw-r-x')).toBe(''); + expect(checkSymbolicString('rwxr-xrw-')).toBe(''); + expect(checkSymbolicString('rwxr-xrw-')).toBe(''); + expect(checkSymbolicString('rxwr-xrw-')).toBe('User permissions should be in the order of \'r\', \'w\', \'x\'.'); + expect(checkSymbolicString('rwxr-wrw-')).toBe('Group permissions should be in the order of \'r\', \'w\', \'x\'.'); + expect(checkSymbolicString('rwxr-xr-w')).toBe('Public permissions should be in the order of \'r\', \'w\', \'x\'.'); + }); + }); + }); }); diff --git a/src/tools/chmod-calculator/chmod-calculator.service.ts b/src/tools/chmod-calculator/chmod-calculator.service.ts index e0046717..f30a2321 100644 --- a/src/tools/chmod-calculator/chmod-calculator.service.ts +++ b/src/tools/chmod-calculator/chmod-calculator.service.ts @@ -1,7 +1,13 @@ import _ from 'lodash'; import type { GroupPermissions, Permissions } from './chmod-calculator.types'; -export { computeChmodOctalRepresentation, computeChmodSymbolicRepresentation }; +export { + computeChmodOctalRepresentation, + computeChmodSymbolicRepresentation, + symbolicToOctal, + checkSymbolicString, + validateCharPositions, +}; function computeChmodOctalRepresentation({ permissions }: { permissions: Permissions }): string { const permissionValue = { read: 4, write: 2, execute: 1 }; @@ -28,3 +34,79 @@ function computeChmodSymbolicRepresentation({ permissions }: { permissions: Perm getGroupPermissionValue(permissions.public), ].join(''); } + +function validateCharPositions(permissionSet: string): boolean { + const validChars = ['r', 'w', 'x']; + for (let i = 0; i < 3; i++) { + const permSetCharCurrent = permissionSet.charAt(i); + const validCharCurent = validChars[i]; + if (permissionSet.charAt(i) !== '-' && permSetCharCurrent !== validCharCurent) { + return false; + } + } + return true; +} + +function checkDuplicateInGroup(group: string): boolean { + const validPermissions = ['r', 'w', 'x']; + const permissions = group.split('').filter(p => validPermissions.includes(p)); + const uniquePermissions = new Set(permissions); + return permissions.length !== uniquePermissions.size; +} + +function symbolicToOctal(symbolic: string): number { + const symbolicMap: Record = { + 'r': 4, + 'w': 2, + 'x': 1, + '-': 0, + }; + + const userPart = symbolic.length === 10 ? symbolic.slice(1, 4) : symbolic.slice(0, 3); + const groupPart = symbolic.length === 10 ? symbolic.slice(4, 7) : symbolic.slice(3, 6); + const publicPart = symbolic.length === 10 ? symbolic.slice(7, 10) : symbolic.slice(6, 9); + + const userNumeric = userPart.split('').reduce((sum, value) => sum + (symbolicMap[value] || 0), 0); + const groupNumeric = groupPart.split('').reduce((sum, value) => sum + (symbolicMap[value] || 0), 0); + const publicNumeric = publicPart.split('').reduce((sum, value) => sum + (symbolicMap[value] || 0), 0); + + return userNumeric * 100 + groupNumeric * 10 + publicNumeric; +} + +function checkSymbolicString(symbolicInput: string): string { + const permissionsRegex = /^([-dlbcsp])?[rwx-]{9}$/; + + if (symbolicInput.length === 0) { + return ''; + } + if (symbolicInput.length > 10) { + return 'Invalid length.'; + } + if (!permissionsRegex.test(symbolicInput)) { + return 'Invalid permission pattern.'; + } + + const userPermissions = symbolicInput.slice(-9, -6); + const groupPermissions = symbolicInput.slice(-6, -3); + const publicPermissions = symbolicInput.slice(-3); + + if (checkDuplicateInGroup(userPermissions)) { + return 'Duplicate rights are not allowed in the user section.'; + } + if (checkDuplicateInGroup(groupPermissions)) { + return 'Duplicate rights are not allowed in the group section.'; + } + if (checkDuplicateInGroup(publicPermissions)) { + return 'Duplicate rights are not allowed in the public section.'; + } + if (!validateCharPositions(userPermissions)) { + return 'User permissions should be in the order of \'r\', \'w\', \'x\'.'; + } + if (!validateCharPositions(groupPermissions)) { + return 'Group permissions should be in the order of \'r\', \'w\', \'x\'.'; + } + if (!validateCharPositions(publicPermissions)) { + return 'Public permissions should be in the order of \'r\', \'w\', \'x\'.'; + } + return ''; +} diff --git a/src/tools/chmod-calculator/chmod-calculator.vue b/src/tools/chmod-calculator/chmod-calculator.vue index ba6f4498..50dd6ed8 100644 --- a/src/tools/chmod-calculator/chmod-calculator.vue +++ b/src/tools/chmod-calculator/chmod-calculator.vue @@ -1,8 +1,13 @@