001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: /**
019: * @author Alexey V. Varlamov
020: * @version $Revision$
021: */package org.apache.harmony.security;
022:
023: import java.io.IOException;
024: import java.io.Reader;
025: import java.io.StreamTokenizer;
026: import java.util.Collection;
027: import java.util.HashSet;
028: import java.util.List;
029:
030: import org.apache.harmony.security.internal.nls.Messages;
031:
032: /**
033: * This is a basic high-level tokenizer of policy files. It takes in a stream,
034: * analyzes data read from it and returns a set of structured tokens. <br>
035: * This implementation recognizes text files, consisting of clauses with the
036: * following syntax:
037: *
038: * <pre>
039: *
040: * keystore "some_keystore_url", "keystore_type";
041: *
042: * </pre>
043: * <pre>
044: *
045: * grant [SignedBy "signer_names"] [, CodeBase "URL"]
046: * [, Principal [principal_class_name] "principal_name"]
047: * [, Principal [principal_class_name] "principal_name"] ... {
048: * permission permission_class_name [ "target_name" ] [, "action"]
049: * [, SignedBy "signer_names"];
050: * permission ...
051: * };
052: *
053: * </pre>
054: *
055: * For semantical details of this format, see the
056: * {@link org.apache.harmony.security.DefaultPolicy default policy description}.
057: * <br>
058: * Keywords are case-insensitive in contrast to quoted string literals.
059: * Comma-separation rule is quite forgiving, most commas may be just omitted.
060: * Whitespaces, line- and block comments are ignored. Symbol-level tokenization
061: * is delegated to java.io.StreamTokenizer. <br>
062: * <br>
063: * This implementation is effectively thread-safe, as it has no field references
064: * to data being processed (that is, passes all the data as method parameters).
065: *
066: * @see org.apache.harmony.security.fortress.DefaultPolicyParser
067: */
068: public class DefaultPolicyScanner {
069:
070: /**
071: * Specific exception class to signal policy file syntax error.
072: *
073: */
074: public static class InvalidFormatException extends Exception {
075:
076: /**
077: * @serial
078: */
079: private static final long serialVersionUID = 5789786270390222184L;
080:
081: /**
082: * Constructor with detailed message parameter.
083: */
084: public InvalidFormatException(String arg0) {
085: super (arg0);
086: }
087: }
088:
089: /**
090: * Configures passed tokenizer accordingly to supported syntax.
091: */
092: protected StreamTokenizer configure(StreamTokenizer st) {
093: st.slashSlashComments(true);
094: st.slashStarComments(true);
095: st.wordChars('_', '_');
096: st.wordChars('$', '$');
097: return st;
098: }
099:
100: /**
101: * Performs the main parsing loop. Starts with creating and configuring a
102: * StreamTokenizer instance; then tries to recognize <i>keystore </i> or
103: * <i>grant </i> keyword. When found, invokes read method corresponding to
104: * the clause and collects result to the passed collection.
105: *
106: * @param r
107: * policy stream reader
108: * @param grantEntries
109: * a collection to accumulate parsed GrantEntries
110: * @param keystoreEntries
111: * a collection to accumulate parsed KeystoreEntries
112: * @throws IOException
113: * if stream reading failed
114: * @throws InvalidFormatException
115: * if unexpected or unknown token encountered
116: */
117: public void scanStream(Reader r,
118: Collection<GrantEntry> grantEntries,
119: List<KeystoreEntry> keystoreEntries) throws IOException,
120: InvalidFormatException {
121: StreamTokenizer st = configure(new StreamTokenizer(r));
122: //main parsing loop
123: parsing: while (true) {
124: switch (st.nextToken()) {
125: case StreamTokenizer.TT_EOF: //we've done the job
126: break parsing;
127:
128: case StreamTokenizer.TT_WORD:
129: if (Util.equalsIgnoreCase("keystore", st.sval)) { //$NON-NLS-1$
130: keystoreEntries.add(readKeystoreEntry(st));
131: } else if (Util.equalsIgnoreCase("grant", st.sval)) { //$NON-NLS-1$
132: grantEntries.add(readGrantEntry(st));
133: } else {
134: handleUnexpectedToken(st, Messages
135: .getString("security.89")); //$NON-NLS-1$
136: }
137: break;
138:
139: case ';': //just delimiter of entries
140: break;
141:
142: default:
143: handleUnexpectedToken(st);
144: break;
145: }
146: }
147: }
148:
149: /**
150: * Tries to read <i>keystore </i> clause fields. The expected syntax is
151: *
152: * <pre>
153: *
154: * "some_keystore_url"[, "keystore_type"];
155: *
156: * </pre>
157: *
158: * @return successfully parsed KeystoreEntry
159: * @throws IOException
160: * if stream reading failed
161: * @throws InvalidFormatException
162: * if unexpected or unknown token encountered
163: */
164: protected KeystoreEntry readKeystoreEntry(StreamTokenizer st)
165: throws IOException, InvalidFormatException {
166: KeystoreEntry ke = new KeystoreEntry();
167: if (st.nextToken() == '"') {
168: ke.url = st.sval;
169: if ((st.nextToken() == '"')
170: || ((st.ttype == ',') && (st.nextToken() == '"'))) {
171: ke.type = st.sval;
172: } else { // handle token in the main loop
173: st.pushBack();
174: }
175: } else {
176: handleUnexpectedToken(st, Messages.getString("security.8A")); //$NON-NLS-1$
177: }
178: return ke;
179: }
180:
181: /**
182: * Tries to read <i>grant </i> clause. <br>
183: * First, it reads <i>codebase </i>, <i>signedby </i>, <i>principal </i>
184: * entries till the '{' (opening curly brace) symbol. Then it calls
185: * readPermissionEntries() method to read the permissions of this clause.
186: * <br>
187: * Principal entries (if any) are read by invoking readPrincipalEntry()
188: * method, obtained PrincipalEntries are accumulated. <br>
189: * The expected syntax is
190: *
191: * <pre>
192: *
193: * [ [codebase "url"] | [signedby "name1,...,nameN"] |
194: * principal ...] ]* { ... }
195: *
196: * </pre>
197: *
198: * @return successfully parsed GrantEntry
199: * @throws IOException
200: * if stream reading failed
201: * @throws InvalidFormatException
202: * if unexpected or unknown token encountered
203: */
204: protected GrantEntry readGrantEntry(StreamTokenizer st)
205: throws IOException, InvalidFormatException {
206: GrantEntry ge = new GrantEntry();
207: parsing: while (true) {
208: switch (st.nextToken()) {
209:
210: case StreamTokenizer.TT_WORD:
211: if (Util.equalsIgnoreCase("signedby", st.sval)) { //$NON-NLS-1$
212: if (st.nextToken() == '"') {
213: ge.signers = st.sval;
214: } else {
215: handleUnexpectedToken(st, Messages
216: .getString("security.8B")); //$NON-NLS-1$
217: }
218: } else if (Util.equalsIgnoreCase("codebase", st.sval)) { //$NON-NLS-1$
219: if (st.nextToken() == '"') {
220: ge.codebase = st.sval;
221: } else {
222: handleUnexpectedToken(st, Messages
223: .getString("security.8C")); //$NON-NLS-1$
224: }
225: } else if (Util.equalsIgnoreCase("principal", st.sval)) { //$NON-NLS-1$
226: ge.addPrincipal(readPrincipalEntry(st));
227: } else {
228: handleUnexpectedToken(st);
229: }
230: break;
231:
232: case ',': //just delimiter of entries
233: break;
234:
235: case '{':
236: ge.permissions = readPermissionEntries(st);
237: break parsing;
238:
239: default: // handle token in the main loop
240: st.pushBack();
241: break parsing;
242: }
243: }
244:
245: return ge;
246: }
247:
248: /**
249: * Tries to read <i>Principal </i> entry fields. The expected syntax is
250: *
251: * <pre>
252: *
253: * [ principal_class_name ] "principal_name"
254: *
255: * </pre>
256: *
257: * Both class and name may be wildcards, wildcard names should not
258: * surrounded by quotes.
259: *
260: * @return successfully parsed PrincipalEntry
261: * @throws IOException
262: * if stream reading failed
263: * @throws InvalidFormatException
264: * if unexpected or unknown token encountered
265: */
266: protected PrincipalEntry readPrincipalEntry(StreamTokenizer st)
267: throws IOException, InvalidFormatException {
268: PrincipalEntry pe = new PrincipalEntry();
269: if (st.nextToken() == StreamTokenizer.TT_WORD) {
270: pe.klass = st.sval;
271: st.nextToken();
272: } else if (st.ttype == '*') {
273: pe.klass = PrincipalEntry.WILDCARD;
274: st.nextToken();
275: }
276: if (st.ttype == '"') {
277: pe.name = st.sval;
278: } else if (st.ttype == '*') {
279: pe.name = PrincipalEntry.WILDCARD;
280: } else {
281: handleUnexpectedToken(st, Messages.getString("security.8D")); //$NON-NLS-1$
282: }
283: return pe;
284: }
285:
286: /**
287: * Tries to read a list of <i>permission </i> entries. The expected syntax
288: * is
289: *
290: * <pre>
291: *
292: * permission permission_class_name
293: * [ "target_name" ] [, "action_list"]
294: * [, signedby "name1,name2,..."];
295: *
296: * </pre>
297: *
298: * List is terminated by '}' (closing curly brace) symbol.
299: *
300: * @return collection of successfully parsed PermissionEntries
301: * @throws IOException
302: * if stream reading failed
303: * @throws InvalidFormatException
304: * if unexpected or unknown token encountered
305: */
306: protected Collection<PermissionEntry> readPermissionEntries(
307: StreamTokenizer st) throws IOException,
308: InvalidFormatException {
309: Collection<PermissionEntry> permissions = new HashSet<PermissionEntry>();
310: parsing: while (true) {
311: switch (st.nextToken()) {
312:
313: case StreamTokenizer.TT_WORD:
314: if (Util.equalsIgnoreCase("permission", st.sval)) { //$NON-NLS-1$
315: PermissionEntry pe = new PermissionEntry();
316: if (st.nextToken() == StreamTokenizer.TT_WORD) {
317: pe.klass = st.sval;
318: if (st.nextToken() == '"') {
319: pe.name = st.sval;
320: st.nextToken();
321: }
322: if (st.ttype == ',') {
323: st.nextToken();
324: }
325: if (st.ttype == '"') {
326: pe.actions = st.sval;
327: if (st.nextToken() == ',') {
328: st.nextToken();
329: }
330: }
331: if (st.ttype == StreamTokenizer.TT_WORD
332: && Util.equalsIgnoreCase(
333: "signedby", st.sval)) { //$NON-NLS-1$
334: if (st.nextToken() == '"') {
335: pe.signers = st.sval;
336: } else {
337: handleUnexpectedToken(st);
338: }
339: } else { // handle token in the next iteration
340: st.pushBack();
341: }
342: permissions.add(pe);
343: continue parsing;
344: }
345: }
346: handleUnexpectedToken(st, Messages
347: .getString("security.8E")); //$NON-NLS-1$
348: break;
349:
350: case ';': //just delimiter of entries
351: break;
352:
353: case '}': //end of list
354: break parsing;
355:
356: default: // invalid token
357: handleUnexpectedToken(st);
358: break;
359: }
360: }
361:
362: return permissions;
363: }
364:
365: /**
366: * Formats a detailed description of tokenizer status: current token,
367: * current line number, etc.
368: */
369: protected String composeStatus(StreamTokenizer st) {
370: return st.toString();
371: }
372:
373: /**
374: * Throws InvalidFormatException with detailed diagnostics.
375: *
376: * @param st
377: * a tokenizer holding the erroneous token
378: * @param message
379: * a user-friendly comment, probably explaining expected syntax.
380: * Should not be <code>null</code>- use the overloaded
381: * single-parameter method instead.
382: */
383: protected final void handleUnexpectedToken(StreamTokenizer st,
384: String message) throws InvalidFormatException {
385: throw new InvalidFormatException(Messages.getString(
386: "security.8F", //$NON-NLS-1$
387: composeStatus(st), message));
388: }
389:
390: /**
391: * Throws InvalidFormatException with error status: which token is
392: * unexpected on which line.
393: *
394: * @param st
395: * a tokenizer holding the erroneous token
396: */
397: protected final void handleUnexpectedToken(StreamTokenizer st)
398: throws InvalidFormatException {
399: throw new InvalidFormatException(Messages.getString(
400: "security.90", //$NON-NLS-1$
401: composeStatus(st)));
402: }
403:
404: /**
405: * Compound token representing <i>keystore </i> clause. See policy format
406: * {@link org.apache.harmony.security.DefaultPolicy description}for details.
407: *
408: * @see org.apache.harmony.security.fortress.DefaultPolicyParser
409: * @see org.apache.harmony.security.DefaultPolicyScanner
410: */
411: public static class KeystoreEntry {
412:
413: /**
414: * The URL part of keystore clause.
415: */
416: public String url;
417:
418: /**
419: * The typename part of keystore clause.
420: */
421: public String type;
422: }
423:
424: /**
425: * Compound token representing <i>grant </i> clause. See policy format
426: * {@link org.apache.harmony.security.DefaultPolicy description}for details.
427: *
428: * @see org.apache.harmony.security.fortress.DefaultPolicyParser
429: * @see org.apache.harmony.security.DefaultPolicyScanner
430: */
431: public static class GrantEntry {
432:
433: /**
434: * The signers part of grant clause. This is a comma-separated list of
435: * certificate aliases.
436: */
437: public String signers;
438:
439: /**
440: * The codebase part of grant clause. This is an URL from which code
441: * originates.
442: */
443: public String codebase;
444:
445: /**
446: * Collection of PrincipalEntries of grant clause.
447: */
448: public Collection<PrincipalEntry> principals;
449:
450: /**
451: * Collection of PermissionEntries of grant clause.
452: */
453: public Collection<PermissionEntry> permissions;
454:
455: /**
456: * Adds specified element to the <code>principals</code> collection.
457: * If collection does not exist yet, creates a new one.
458: */
459: public void addPrincipal(PrincipalEntry pe) {
460: if (principals == null) {
461: principals = new HashSet<PrincipalEntry>();
462: }
463: principals.add(pe);
464: }
465:
466: }
467:
468: /**
469: * Compound token representing <i>principal </i> entry of a <i>grant </i>
470: * clause. See policy format
471: * {@link org.apache.harmony.security.DefaultPolicy description}for details.
472: *
473: * @see org.apache.harmony.security.fortress.DefaultPolicyParser
474: * @see org.apache.harmony.security.DefaultPolicyScanner
475: */
476: public static class PrincipalEntry {
477:
478: /**
479: * Wildcard value denotes any class and/or any name.
480: * Must be asterisk, for proper general expansion and
481: * PrivateCredentialsPermission wildcarding
482: */
483: public static final String WILDCARD = "*"; //$NON-NLS-1$
484:
485: /**
486: * The classname part of principal clause.
487: */
488: public String klass;
489:
490: /**
491: * The name part of principal clause.
492: */
493: public String name;
494: }
495:
496: /**
497: * Compound token representing <i>permission </i> entry of a <i>grant </i>
498: * clause. See policy format
499: * {@link org.apache.harmony.security.DefaultPolicy description}for details.
500: *
501: * @see org.apache.harmony.security.fortress.DefaultPolicyParser
502: * @see org.apache.harmony.security.DefaultPolicyScanner
503: */
504: public static class PermissionEntry {
505:
506: /**
507: * The classname part of permission clause.
508: */
509: public String klass;
510:
511: /**
512: * The name part of permission clause.
513: */
514: public String name;
515:
516: /**
517: * The actions part of permission clause.
518: */
519: public String actions;
520:
521: /**
522: * The signers part of permission clause. This is a comma-separated list
523: * of certificate aliases.
524: */
525: public String signers;
526: }
527: }
|