Skip to content

Commit 3569f01

Browse files
committed
Adds permissions parser for string/octal
Signed-off-by: Patrick Reinhart <patrick@reini.net>
1 parent ac3f044 commit 3569f01

File tree

3 files changed

+511
-2
lines changed

3 files changed

+511
-2
lines changed

build.gradle

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ defaultTasks 'build'
6464

6565
java {
6666
sourceCompatibility = JavaVersion.VERSION_1_8
67-
targetCompatibility = JavaVersion.VERSION_1_8
6867
}
6968

7069
repositories {
@@ -95,7 +94,6 @@ signing {
9594

9695
jar {
9796
manifest.attributes 'Implementation-Title': 'XML:DB API specification',
98-
'Implementation-Version': project.version,
9997
'Automatic-Module-Name': "org.xmldb.api"
10098
}
10199

Lines changed: 357 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,357 @@
1+
/*
2+
* The XML:DB Initiative Software License, Version 1.0
3+
*
4+
* Copyright (c) 2000-2022 The XML:DB Initiative. All rights reserved.
5+
*
6+
* Redistribution and use in source and binary forms, with or without modification, are permitted
7+
* provided that the following conditions are met:
8+
*
9+
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions
10+
* and the following disclaimer.
11+
*
12+
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of
13+
* conditions and the following disclaimer in the documentation and/or other materials provided with
14+
* the distribution.
15+
*
16+
* 3. The end-user documentation included with the redistribution, if any, must include the
17+
* following acknowledgment: "This product includes software developed by the XML:DB Initiative
18+
* (http://www.xmldb.org/)." Alternately, this acknowledgment may appear in the software itself, if
19+
* and wherever such third-party acknowledgments normally appear.
20+
*
21+
* 4. The name "XML:DB Initiative" must not be used to endorse or promote products derived from this
22+
* software without prior written permission. For written permission, please contact info@xmldb.org.
23+
*
24+
* 5. Products derived from this software may not be called "XML:DB", nor may "XML:DB" appear in
25+
* their name, without prior written permission of the XML:DB Initiative.
26+
*
27+
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
29+
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR
30+
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
31+
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
32+
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33+
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
34+
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35+
* =================================================================================================
36+
* This software consists of voluntary contributions made by many individuals on behalf of the
37+
* XML:DB Initiative. For more information on the XML:DB Initiative, please see
38+
* <https://github.com/xmldb-org/>
39+
*/
40+
package org.xmldb.api.security;
41+
42+
import static java.lang.Boolean.TRUE;
43+
import static org.xmldb.api.security.Permission.GROUP_EXECUTE;
44+
import static org.xmldb.api.security.Permission.GROUP_READ;
45+
import static org.xmldb.api.security.Permission.GROUP_WRITE;
46+
import static org.xmldb.api.security.Permission.OTHERS_EXECUTE;
47+
import static org.xmldb.api.security.Permission.OTHERS_READ;
48+
import static org.xmldb.api.security.Permission.OTHERS_WRITE;
49+
import static org.xmldb.api.security.Permission.OWNER_EXECUTE;
50+
import static org.xmldb.api.security.Permission.OWNER_READ;
51+
import static org.xmldb.api.security.Permission.OWNER_WRITE;
52+
import static org.xmldb.api.security.Permission.SET_GID;
53+
import static org.xmldb.api.security.Permission.SET_UID;
54+
import static org.xmldb.api.security.Permission.STICKY_BIT;
55+
56+
import java.util.EnumMap;
57+
import java.util.EnumSet;
58+
import java.util.Set;
59+
import java.util.regex.Matcher;
60+
import java.util.regex.Pattern;
61+
62+
/**
63+
* Parser for {@link Permission} values.
64+
*
65+
* This class provides static methods to parse string and integer based permission modes into a set
66+
* of permissions.
67+
*/
68+
public final class Permissions {
69+
private static final byte READ = 04;
70+
private static final byte WRITE = 02;
71+
private static final byte EXECUTE = 01;
72+
73+
private static final char SETID_CHAR = 's';
74+
private static final char SETID_CHAR_NO_EXEC = 'S';
75+
private static final char STICKY_CHAR = 't';
76+
private static final char STICKY_CHAR_NO_EXEC = 'T';
77+
private static final char READ_CHAR = 'r';
78+
private static final char WRITE_CHAR = 'w';
79+
private static final char EXECUTE_CHAR = 'x';
80+
private static final char UNSET_CHAR = '-';
81+
private static final char ALL_CHAR = 'a';
82+
private static final char USER_CHAR = 'u';
83+
private static final char GROUP_CHAR = 'g';
84+
private static final char OTHER_CHAR = 'o';
85+
86+
public static final Pattern UNIX_SYMBOLIC_MODE_PATTERN =
87+
Pattern.compile("((?:[augo]*(?:[+\\-=](?:[" + READ_CHAR + SETID_CHAR + STICKY_CHAR
88+
+ WRITE_CHAR + EXECUTE_CHAR + "])+)+),?)+");
89+
public static final Pattern SIMPLE_SYMBOLIC_MODE_PATTERN = Pattern
90+
.compile("(?:(?:" + READ_CHAR + "|" + UNSET_CHAR + ")(?:" + WRITE_CHAR + "|" + UNSET_CHAR
91+
+ ")(?:[" + EXECUTE_CHAR + SETID_CHAR + SETID_CHAR_NO_EXEC + "]|" + UNSET_CHAR
92+
+ ")){2}(?:" + READ_CHAR + "|" + UNSET_CHAR + ")(?:" + WRITE_CHAR + "|" + UNSET_CHAR
93+
+ ")(?:[" + EXECUTE_CHAR + STICKY_CHAR + STICKY_CHAR_NO_EXEC + "]|" + UNSET_CHAR + ")");
94+
95+
private Permissions() {}
96+
97+
private enum PermType {
98+
READ, WRITE, EXECUTE, SETID, STICKY;
99+
}
100+
101+
/**
102+
* Parses the permissions from the given mode string.
103+
*
104+
* The string can either be in one of two formats:
105+
* <ol>
106+
* <li>Unix Symbolic format as given to 'chmod' on Unix/Linux</li>
107+
* <li>Simple Symbolic format e.g. "rwxr-xr-x"</li>
108+
* </ol>
109+
*
110+
* @param modeStr the mode to be parsed
111+
* @return the set of parsed permissions
112+
* @throws IllegalArgumentException if the given mode string is in the wrong format
113+
*/
114+
public static Set<Permission> fromModeString(String modeStr) {
115+
final Matcher simpleSymbolicModeMatcher = SIMPLE_SYMBOLIC_MODE_PATTERN.matcher(modeStr);
116+
final EnumSet<Permission> permissions = EnumSet.noneOf(Permission.class);
117+
if (simpleSymbolicModeMatcher.matches()) {
118+
setSimpleSymbolicMode(permissions, modeStr);
119+
} else {
120+
final Matcher unixSymbolicModeMatcher = UNIX_SYMBOLIC_MODE_PATTERN.matcher(modeStr);
121+
if (unixSymbolicModeMatcher.matches()) {
122+
setUnixSymbolicMode(permissions, modeStr);
123+
} else {
124+
throw new IllegalArgumentException("Unknown mode String: " + modeStr);
125+
}
126+
}
127+
return permissions;
128+
}
129+
130+
131+
/**
132+
* Parses the permissions from the given octal mode value.
133+
*
134+
* @param mode the octal permission mode
135+
* @return the set of parsed permissions
136+
*/
137+
public static Set<Permission> fromOctal(int mode) {
138+
final EnumSet<Permission> permissions = EnumSet.noneOf(Permission.class);
139+
// user operations
140+
if ((mode & (READ << 6)) != 0) {
141+
permissions.add(OWNER_READ);
142+
}
143+
if ((mode & (WRITE << 6)) != 0) {
144+
permissions.add(OWNER_WRITE);
145+
}
146+
if ((mode & (EXECUTE << 6)) != 0) {
147+
permissions.add(OWNER_EXECUTE);
148+
}
149+
if ((mode & (1 << 11)) != 0) {
150+
permissions.add(SET_UID);
151+
}
152+
// group operations
153+
if ((mode & (READ << 3)) != 0) {
154+
permissions.add(GROUP_READ);
155+
}
156+
if ((mode & (WRITE << 3)) != 0) {
157+
permissions.add(GROUP_WRITE);
158+
}
159+
if ((mode & (EXECUTE << 3)) != 0) {
160+
permissions.add(GROUP_EXECUTE);
161+
}
162+
if ((mode & (1 << 10)) != 0) {
163+
permissions.add(SET_GID);
164+
}
165+
166+
// others operations
167+
if ((mode & READ) != 0) {
168+
permissions.add(OTHERS_READ);
169+
}
170+
if ((mode & WRITE) != 0) {
171+
permissions.add(OTHERS_WRITE);
172+
}
173+
if ((mode & EXECUTE) != 0) {
174+
permissions.add(OTHERS_EXECUTE);
175+
}
176+
if ((mode & (1 << 9)) != 0) {
177+
permissions.add(STICKY_BIT);
178+
}
179+
return permissions;
180+
}
181+
182+
@SuppressWarnings("StringSplitter")
183+
private static void setUnixSymbolicMode(final EnumSet<Permission> permissions,
184+
final String symbolicMode) {
185+
for (final String clause : symbolicMode.split(",")) {
186+
final String[] whoPerm = clause.split("[+\\-=]");
187+
final EnumMap<PermType, Boolean> perms = new EnumMap<>(PermType.class);
188+
// process the operation first
189+
parseOperation(whoPerm, perms);
190+
191+
final char[] whoose;
192+
if (!whoPerm[0].isEmpty()) {
193+
whoose = whoPerm[0].toCharArray();
194+
} else {
195+
whoose = new char[] {ALL_CHAR};
196+
}
197+
198+
for (final char c : whoose) {
199+
switch (c) {
200+
case ALL_CHAR:
201+
if (clause.indexOf('+') > -1 || clause.indexOf('=') > -1) {
202+
if (perms.containsKey(PermType.READ)) {
203+
permissions.add(OWNER_READ);
204+
permissions.add(GROUP_READ);
205+
permissions.add(OTHERS_READ);
206+
}
207+
if (perms.containsKey(PermType.WRITE)) {
208+
permissions.add(OWNER_WRITE);
209+
permissions.add(GROUP_WRITE);
210+
permissions.add(OTHERS_WRITE);
211+
}
212+
if (perms.containsKey(PermType.EXECUTE)) {
213+
permissions.add(OWNER_EXECUTE);
214+
permissions.add(GROUP_EXECUTE);
215+
permissions.add(OTHERS_EXECUTE);
216+
}
217+
if (perms.containsKey(PermType.SETID)) {
218+
permissions.add(SET_UID);
219+
permissions.add(SET_GID);
220+
}
221+
if (perms.containsKey(PermType.STICKY)) {
222+
permissions.add(STICKY_BIT);
223+
}
224+
}
225+
break;
226+
case USER_CHAR:
227+
if (clause.indexOf('+') > -1 || clause.indexOf('=') > -1) {
228+
setPermissions(permissions, perms, OWNER_READ, OWNER_WRITE, OWNER_EXECUTE);
229+
if (perms.containsKey(PermType.SETID)) {
230+
permissions.add(SET_UID);
231+
}
232+
}
233+
break;
234+
case GROUP_CHAR:
235+
if (clause.indexOf('+') > -1 || clause.indexOf('=') > -1) {
236+
setPermissions(permissions, perms, GROUP_READ, GROUP_WRITE, GROUP_EXECUTE);
237+
if (perms.containsKey(PermType.SETID)) {
238+
permissions.add(SET_GID);
239+
}
240+
}
241+
break;
242+
case OTHER_CHAR:
243+
if (clause.indexOf('+') > -1 || clause.indexOf('=') > -1) {
244+
setPermissions(permissions, perms, OTHERS_READ, OTHERS_WRITE, OTHERS_EXECUTE);
245+
if (perms.containsKey(PermType.STICKY)) {
246+
permissions.add(STICKY_BIT);
247+
}
248+
}
249+
break;
250+
default:
251+
throw new IllegalArgumentException("Unrecognised mode char '" + c + "'");
252+
}
253+
}
254+
perms.clear();
255+
}
256+
}
257+
258+
private static void parseOperation(final String[] whoPerm,
259+
final EnumMap<PermType, Boolean> perms) {
260+
for (final char c : whoPerm[1].toCharArray()) {
261+
switch (c) {
262+
case READ_CHAR:
263+
perms.put(PermType.READ, TRUE);
264+
break;
265+
case WRITE_CHAR:
266+
perms.put(PermType.WRITE, TRUE);
267+
break;
268+
case EXECUTE_CHAR:
269+
perms.put(PermType.EXECUTE, TRUE);
270+
break;
271+
case SETID_CHAR:
272+
perms.put(PermType.SETID, TRUE);
273+
break;
274+
case STICKY_CHAR:
275+
perms.put(PermType.STICKY, TRUE);
276+
break;
277+
default:
278+
throw new IllegalArgumentException("Unrecognised mode char '" + c + "'");
279+
}
280+
}
281+
}
282+
283+
private static void setPermissions(EnumSet<Permission> permissions,
284+
EnumMap<PermType, Boolean> perms, Permission read, Permission write, Permission execute) {
285+
if (perms.containsKey(PermType.READ)) {
286+
permissions.add(read);
287+
}
288+
if (perms.containsKey(PermType.WRITE)) {
289+
permissions.add(write);
290+
}
291+
if (perms.containsKey(PermType.EXECUTE)) {
292+
permissions.add(execute);
293+
}
294+
}
295+
296+
private static void setSimpleSymbolicMode(final EnumSet<Permission> permissions,
297+
final String simpleModeStr) {
298+
final char[] modeArray = simpleModeStr.toCharArray();
299+
for (int i = 0; i < modeArray.length; i++) {
300+
final char c = modeArray[i];
301+
switch (c) {
302+
case READ_CHAR:
303+
selectPermission(permissions, i, OWNER_READ, GROUP_READ, OTHERS_READ);
304+
break;
305+
case WRITE_CHAR:
306+
selectPermission(permissions, i - 1, OWNER_WRITE, GROUP_WRITE, OTHERS_WRITE);
307+
break;
308+
case EXECUTE_CHAR:
309+
selectPermission(permissions, i - 2, OWNER_EXECUTE, GROUP_EXECUTE, OTHERS_EXECUTE);
310+
break;
311+
case SETID_CHAR_NO_EXEC:
312+
if (i < 3) {
313+
permissions.add(SET_UID);
314+
} else {
315+
permissions.add(SET_GID);
316+
}
317+
break;
318+
case SETID_CHAR:
319+
if (i < 3) {
320+
permissions.add(OWNER_EXECUTE);
321+
permissions.add(SET_UID);
322+
} else {
323+
permissions.add(GROUP_EXECUTE);
324+
permissions.add(SET_GID);
325+
}
326+
break;
327+
case STICKY_CHAR_NO_EXEC:
328+
permissions.add(STICKY_BIT);
329+
break;
330+
case STICKY_CHAR:
331+
permissions.add(OTHERS_EXECUTE);
332+
permissions.add(STICKY_BIT);
333+
break;
334+
case UNSET_CHAR:
335+
break;
336+
default:
337+
throw new IllegalArgumentException("Unrecognised mode char '" + c + "'");
338+
}
339+
}
340+
}
341+
342+
private static void selectPermission(EnumSet<Permission> permissions, int index,
343+
Permission ownerPermission, Permission groupPermission, Permission othersPermission) {
344+
switch (index) {
345+
case 0:
346+
permissions.add(ownerPermission);
347+
break;
348+
case 3:
349+
permissions.add(groupPermission);
350+
break;
351+
case 6:
352+
permissions.add(othersPermission);
353+
break;
354+
default:
355+
}
356+
}
357+
}

0 commit comments

Comments
 (0)