Skip to content

Commit 3ead4d3

Browse files
committed
feat(Chmod Calculator): octal input and special flags
Handle input of octal rights values and special flags Fix CorentinTh#670 and CorentinTh#527
1 parent d3b32cc commit 3ead4d3

File tree

4 files changed

+314
-10
lines changed

4 files changed

+314
-10
lines changed

src/tools/chmod-calculator/chmod-calculator.service.test.ts

Lines changed: 214 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, expect, it } from 'vitest';
2-
import { computeChmodOctalRepresentation, computeChmodSymbolicRepresentation } from './chmod-calculator.service';
2+
import { computeChmodOctalRepresentation, computeChmodSymbolicRepresentation, computePermissionsFromChmodOctalRepresentation } from './chmod-calculator.service';
33

44
describe('chmod-calculator', () => {
55
describe('computeChmodOctalRepresentation', () => {
@@ -10,6 +10,7 @@ describe('chmod-calculator', () => {
1010
owner: { read: true, write: true, execute: true },
1111
group: { read: true, write: true, execute: true },
1212
public: { read: true, write: true, execute: true },
13+
flags: { setuid: false, setgid: false, stickybit: false },
1314
},
1415
}),
1516
).to.eql('777');
@@ -20,6 +21,7 @@ describe('chmod-calculator', () => {
2021
owner: { read: false, write: false, execute: false },
2122
group: { read: false, write: false, execute: false },
2223
public: { read: false, write: false, execute: false },
24+
flags: { setuid: false, setgid: false, stickybit: false },
2325
},
2426
}),
2527
).to.eql('000');
@@ -30,6 +32,7 @@ describe('chmod-calculator', () => {
3032
owner: { read: false, write: true, execute: false },
3133
group: { read: false, write: true, execute: true },
3234
public: { read: true, write: false, execute: true },
35+
flags: { setuid: false, setgid: false, stickybit: false },
3336
},
3437
}),
3538
).to.eql('235');
@@ -40,6 +43,7 @@ describe('chmod-calculator', () => {
4043
owner: { read: true, write: false, execute: false },
4144
group: { read: false, write: true, execute: false },
4245
public: { read: false, write: false, execute: true },
46+
flags: { setuid: false, setgid: false, stickybit: false },
4347
},
4448
}),
4549
).to.eql('421');
@@ -50,6 +54,7 @@ describe('chmod-calculator', () => {
5054
owner: { read: false, write: false, execute: true },
5155
group: { read: false, write: true, execute: false },
5256
public: { read: true, write: false, execute: false },
57+
flags: { setuid: false, setgid: false, stickybit: false },
5358
},
5459
}),
5560
).to.eql('124');
@@ -60,18 +65,65 @@ describe('chmod-calculator', () => {
6065
owner: { read: false, write: true, execute: false },
6166
group: { read: false, write: true, execute: false },
6267
public: { read: false, write: true, execute: false },
68+
flags: { setuid: false, setgid: false, stickybit: false },
6369
},
6470
}),
6571
).to.eql('222');
66-
});
6772

73+
expect(
74+
computeChmodOctalRepresentation({
75+
permissions: {
76+
owner: { read: false, write: true, execute: false },
77+
group: { read: false, write: true, execute: false },
78+
public: { read: false, write: true, execute: false },
79+
flags: { setuid: true, setgid: true, stickybit: true },
80+
},
81+
}),
82+
).to.eql('7222');
83+
84+
expect(
85+
computeChmodOctalRepresentation({
86+
permissions: {
87+
owner: { read: false, write: true, execute: false },
88+
group: { read: false, write: true, execute: false },
89+
public: { read: false, write: true, execute: false },
90+
flags: { setuid: true, setgid: false, stickybit: false },
91+
},
92+
}),
93+
).to.eql('4222');
94+
95+
expect(
96+
computeChmodOctalRepresentation({
97+
permissions: {
98+
owner: { read: false, write: true, execute: false },
99+
group: { read: false, write: true, execute: false },
100+
public: { read: false, write: true, execute: false },
101+
flags: { setuid: false, setgid: true, stickybit: false },
102+
},
103+
}),
104+
).to.eql('2222');
105+
106+
expect(
107+
computeChmodOctalRepresentation({
108+
permissions: {
109+
owner: { read: false, write: true, execute: false },
110+
group: { read: false, write: true, execute: false },
111+
public: { read: false, write: true, execute: false },
112+
flags: { setuid: false, setgid: false, stickybit: true },
113+
},
114+
}),
115+
).to.eql('1222');
116+
});
117+
});
118+
describe('computeChmodSymbolicRepresentation', () => {
68119
it('get the symbolic representation from permissions', () => {
69120
expect(
70121
computeChmodSymbolicRepresentation({
71122
permissions: {
72123
owner: { read: true, write: true, execute: true },
73124
group: { read: true, write: true, execute: true },
74125
public: { read: true, write: true, execute: true },
126+
flags: { setuid: false, setgid: false, stickybit: false },
75127
},
76128
}),
77129
).to.eql('rwxrwxrwx');
@@ -82,6 +134,7 @@ describe('chmod-calculator', () => {
82134
owner: { read: false, write: false, execute: false },
83135
group: { read: false, write: false, execute: false },
84136
public: { read: false, write: false, execute: false },
137+
flags: { setuid: false, setgid: false, stickybit: false },
85138
},
86139
}),
87140
).to.eql('---------');
@@ -92,6 +145,7 @@ describe('chmod-calculator', () => {
92145
owner: { read: false, write: true, execute: false },
93146
group: { read: false, write: true, execute: true },
94147
public: { read: true, write: false, execute: true },
148+
flags: { setuid: false, setgid: false, stickybit: false },
95149
},
96150
}),
97151
).to.eql('-w--wxr-x');
@@ -102,6 +156,7 @@ describe('chmod-calculator', () => {
102156
owner: { read: true, write: false, execute: false },
103157
group: { read: false, write: true, execute: false },
104158
public: { read: false, write: false, execute: true },
159+
flags: { setuid: false, setgid: false, stickybit: false },
105160
},
106161
}),
107162
).to.eql('r---w---x');
@@ -112,6 +167,7 @@ describe('chmod-calculator', () => {
112167
owner: { read: false, write: false, execute: true },
113168
group: { read: false, write: true, execute: false },
114169
public: { read: true, write: false, execute: false },
170+
flags: { setuid: false, setgid: false, stickybit: false },
115171
},
116172
}),
117173
).to.eql('--x-w-r--');
@@ -122,9 +178,165 @@ describe('chmod-calculator', () => {
122178
owner: { read: false, write: true, execute: false },
123179
group: { read: false, write: true, execute: false },
124180
public: { read: false, write: true, execute: false },
181+
flags: { setuid: false, setgid: false, stickybit: false },
125182
},
126183
}),
127184
).to.eql('-w--w--w-');
185+
186+
expect(
187+
computeChmodSymbolicRepresentation({
188+
permissions: {
189+
owner: { read: false, write: false, execute: true },
190+
group: { read: false, write: true, execute: false },
191+
public: { read: true, write: false, execute: false },
192+
flags: { setuid: false, setgid: false, stickybit: true },
193+
},
194+
}),
195+
).to.eql('--x-w-r-t');
196+
197+
expect(
198+
computeChmodSymbolicRepresentation({
199+
permissions: {
200+
owner: { read: false, write: false, execute: true },
201+
group: { read: false, write: true, execute: false },
202+
public: { read: true, write: false, execute: false },
203+
flags: { setuid: false, setgid: true, stickybit: true },
204+
},
205+
}),
206+
).to.eql('--x-wsr-t');
207+
208+
expect(
209+
computeChmodSymbolicRepresentation({
210+
permissions: {
211+
owner: { read: false, write: false, execute: true },
212+
group: { read: false, write: true, execute: false },
213+
public: { read: true, write: false, execute: false },
214+
flags: { setuid: true, setgid: true, stickybit: true },
215+
},
216+
}),
217+
).to.eql('--s-wsr-t');
218+
219+
expect(
220+
computeChmodSymbolicRepresentation({
221+
permissions: {
222+
owner: { read: true, write: false, execute: true },
223+
group: { read: true, write: true, execute: false },
224+
public: { read: true, write: false, execute: false },
225+
flags: { setuid: false, setgid: false, stickybit: false },
226+
},
227+
}),
228+
).to.eql('r-xrw-r--');
229+
230+
expect(
231+
computeChmodSymbolicRepresentation({
232+
permissions: {
233+
owner: { read: true, write: true, execute: true },
234+
group: { read: true, write: true, execute: true },
235+
public: { read: true, write: true, execute: true },
236+
flags: { setuid: true, setgid: true, stickybit: true },
237+
},
238+
}),
239+
).to.eql('rwsrwsrwt');
240+
});
241+
});
242+
describe('computePermissionsFromChmodOctalRepresentation', () => {
243+
it('throws on invalid octal values', () => {
244+
expect(() => computePermissionsFromChmodOctalRepresentation('12')).to.throw();
245+
expect(() => computePermissionsFromChmodOctalRepresentation('12345')).to.throw();
246+
expect(() => computePermissionsFromChmodOctalRepresentation('999')).to.throw();
247+
expect(() => computePermissionsFromChmodOctalRepresentation('9999')).to.throw();
248+
});
249+
250+
it('get permissions from octal representation', () => {
251+
expect(
252+
computePermissionsFromChmodOctalRepresentation('777'),
253+
).to.eql({
254+
owner: { read: true, write: true, execute: true },
255+
group: { read: true, write: true, execute: true },
256+
public: { read: true, write: true, execute: true },
257+
flags: { setuid: false, setgid: false, stickybit: false },
258+
});
259+
260+
expect(
261+
computePermissionsFromChmodOctalRepresentation('000'),
262+
).to.eql({
263+
owner: { read: false, write: false, execute: false },
264+
group: { read: false, write: false, execute: false },
265+
public: { read: false, write: false, execute: false },
266+
flags: { setuid: false, setgid: false, stickybit: false },
267+
});
268+
269+
expect(
270+
computePermissionsFromChmodOctalRepresentation('235'),
271+
).to.eql({
272+
owner: { read: false, write: true, execute: false },
273+
group: { read: false, write: true, execute: true },
274+
public: { read: true, write: false, execute: true },
275+
flags: { setuid: false, setgid: false, stickybit: false },
276+
});
277+
278+
expect(
279+
computePermissionsFromChmodOctalRepresentation('421'),
280+
).to.eql({
281+
owner: { read: true, write: false, execute: false },
282+
group: { read: false, write: true, execute: false },
283+
public: { read: false, write: false, execute: true },
284+
flags: { setuid: false, setgid: false, stickybit: false },
285+
});
286+
287+
expect(
288+
computePermissionsFromChmodOctalRepresentation('124'),
289+
).to.eql({
290+
owner: { read: false, write: false, execute: true },
291+
group: { read: false, write: true, execute: false },
292+
public: { read: true, write: false, execute: false },
293+
flags: { setuid: false, setgid: false, stickybit: false },
294+
});
295+
296+
expect(
297+
computePermissionsFromChmodOctalRepresentation('222'),
298+
).to.eql({
299+
owner: { read: false, write: true, execute: false },
300+
group: { read: false, write: true, execute: false },
301+
public: { read: false, write: true, execute: false },
302+
flags: { setuid: false, setgid: false, stickybit: false },
303+
});
304+
305+
expect(
306+
computePermissionsFromChmodOctalRepresentation('7222'),
307+
).to.eql({
308+
owner: { read: false, write: true, execute: false },
309+
group: { read: false, write: true, execute: false },
310+
public: { read: false, write: true, execute: false },
311+
flags: { setuid: true, setgid: true, stickybit: true },
312+
});
313+
314+
expect(
315+
computePermissionsFromChmodOctalRepresentation('4222'),
316+
).to.eql({
317+
owner: { read: false, write: true, execute: false },
318+
group: { read: false, write: true, execute: false },
319+
public: { read: false, write: true, execute: false },
320+
flags: { setuid: true, setgid: false, stickybit: false },
321+
});
322+
323+
expect(
324+
computePermissionsFromChmodOctalRepresentation('2222'),
325+
).to.eql({
326+
owner: { read: false, write: true, execute: false },
327+
group: { read: false, write: true, execute: false },
328+
public: { read: false, write: true, execute: false },
329+
flags: { setuid: false, setgid: true, stickybit: false },
330+
});
331+
332+
expect(
333+
computePermissionsFromChmodOctalRepresentation('1222'),
334+
).to.eql({
335+
owner: { read: false, write: true, execute: false },
336+
group: { read: false, write: true, execute: false },
337+
public: { read: false, write: true, execute: false },
338+
flags: { setuid: false, setgid: false, stickybit: true },
339+
});
128340
});
129341
});
130342
});
Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
import _ from 'lodash';
2-
import type { GroupPermissions, Permissions } from './chmod-calculator.types';
2+
import type { GroupPermissions, Permissions, SpecialPermissions } from './chmod-calculator.types';
33

4-
export { computeChmodOctalRepresentation, computeChmodSymbolicRepresentation };
4+
export { computeChmodOctalRepresentation, computeChmodSymbolicRepresentation, computePermissionsFromChmodOctalRepresentation };
55

66
function computeChmodOctalRepresentation({ permissions }: { permissions: Permissions }): string {
77
const permissionValue = { read: 4, write: 2, execute: 1 };
8+
const specialPermissionValue = { setuid: 4, setgid: 2, stickybit: 1 };
89

910
const getGroupPermissionValue = (permission: GroupPermissions) =>
1011
_.reduce(permission, (acc, isPermSet, key) => acc + (isPermSet ? _.get(permissionValue, key, 0) : 0), 0);
12+
const getSpecialPermissionValue = (permission: SpecialPermissions) => {
13+
const octalValue = _.reduce(permission, (acc, isPermSet, key) => acc + (isPermSet ? _.get(specialPermissionValue, key, 0) : 0), 0);
14+
return octalValue > 0 ? octalValue.toString() : '';
15+
};
1116

1217
return [
18+
getSpecialPermissionValue(permissions.flags || { setuid: false, setgid: false, stickybit: false }),
1319
getGroupPermissionValue(permissions.owner),
1420
getGroupPermissionValue(permissions.group),
1521
getGroupPermissionValue(permissions.public),
@@ -18,13 +24,40 @@ function computeChmodOctalRepresentation({ permissions }: { permissions: Permiss
1824

1925
function computeChmodSymbolicRepresentation({ permissions }: { permissions: Permissions }): string {
2026
const permissionValue = { read: 'r', write: 'w', execute: 'x' };
27+
const specialFlagPermission = 'execute';
2128

22-
const getGroupPermissionValue = (permission: GroupPermissions) =>
23-
_.reduce(permission, (acc, isPermSet, key) => acc + (isPermSet ? _.get(permissionValue, key, '') : '-'), '');
29+
const getGroupPermissionValue = (permission: GroupPermissions, specialFlag: null | 's' | 't') =>
30+
_.reduce(permission, (acc, isPermSet, key) => acc + ((key === specialFlagPermission ? specialFlag : undefined)
31+
|| (isPermSet ? _.get(permissionValue, key, '') : '-')), '');
2432

2533
return [
26-
getGroupPermissionValue(permissions.owner),
27-
getGroupPermissionValue(permissions.group),
28-
getGroupPermissionValue(permissions.public),
34+
getGroupPermissionValue(permissions.owner, permissions.flags?.setuid ? 's' : null),
35+
getGroupPermissionValue(permissions.group, permissions.flags?.setgid ? 's' : null),
36+
getGroupPermissionValue(permissions.public, permissions.flags?.stickybit ? 't' : null),
2937
].join('');
3038
}
39+
40+
function computePermissionsFromChmodOctalRepresentation(octalPermissions: string): Permissions {
41+
const permissionValue = { read: 4, write: 2, execute: 1 };
42+
const specialPermissionValue = { setuid: 4, setgid: 2, stickybit: 1 };
43+
44+
if (!octalPermissions || !octalPermissions.match(/^[0-7]{3,4}$/)) {
45+
throw new Error(`Invalid octal permissions (must be 3 or 4 octal digits): ${octalPermissions}`);
46+
}
47+
const fullOctalPermissions = octalPermissions.length === 3 ? `0${octalPermissions}` : octalPermissions;
48+
49+
const hasSet = (position: number, flagValue: number) => (Number(fullOctalPermissions[position]) & flagValue) === flagValue;
50+
function computePermissionObject<T>(permissionSet: object, position: number): T {
51+
return _.reduce(permissionSet, (acc, flag, key) => ({ ...acc, [key]: hasSet(position, flag) }), {}) as T;
52+
}
53+
const flagsPosition = 0;
54+
const ownerPosition = 1;
55+
const groupPosition = 2;
56+
const publicPosition = 3;
57+
return {
58+
owner: computePermissionObject(permissionValue, ownerPosition),
59+
group: computePermissionObject(permissionValue, groupPosition),
60+
public: computePermissionObject(permissionValue, publicPosition),
61+
flags: computePermissionObject(specialPermissionValue, flagsPosition),
62+
};
63+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
export type Scope = 'read' | 'write' | 'execute';
22
export type Group = 'owner' | 'group' | 'public';
3+
export type SpecialFlags = 'setuid' | 'setgid' | 'stickybit';
34

45
export type GroupPermissions = {
56
[k in Scope]: boolean;
67
};
78

9+
export type SpecialPermissions = {
10+
[k in SpecialFlags]: boolean;
11+
};
12+
813
export type Permissions = {
914
[k in Group]: GroupPermissions;
15+
} & {
16+
flags: SpecialPermissions
1017
};

0 commit comments

Comments
 (0)