1/* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18/** 19* @author Alexey V. Varlamov 20* @version $Revision$ 21*/ 22 23package org.apache.harmony.security; 24 25import java.io.IOException; 26import java.io.Reader; 27import java.io.StreamTokenizer; 28import java.util.Collection; 29import java.util.HashSet; 30import java.util.List; 31 32import org.apache.harmony.security.internal.nls.Messages; 33 34/** 35 * This is a basic high-level tokenizer of policy files. It takes in a stream, 36 * analyzes data read from it and returns a set of structured tokens. <br> 37 * This implementation recognizes text files, consisting of clauses with the 38 * following syntax: 39 * 40 * <pre> 41 * 42 * keystore "some_keystore_url", "keystore_type"; 43 * 44 * </pre> 45 * <pre> 46 * 47 * grant [SignedBy "signer_names"] [, CodeBase "URL"] 48 * [, Principal [principal_class_name] "principal_name"] 49 * [, Principal [principal_class_name] "principal_name"] ... { 50 * permission permission_class_name [ "target_name" ] [, "action"] 51 * [, SignedBy "signer_names"]; 52 * permission ... 53 * }; 54 * 55 * </pre> 56 * 57 * For semantical details of this format, see the 58 * {@link org.apache.harmony.security.fortress.DefaultPolicy default policy description}. 59 * <br> 60 * Keywords are case-insensitive in contrast to quoted string literals. 61 * Comma-separation rule is quite forgiving, most commas may be just omitted. 62 * Whitespaces, line- and block comments are ignored. Symbol-level tokenization 63 * is delegated to java.io.StreamTokenizer. <br> 64 * <br> 65 * This implementation is effectively thread-safe, as it has no field references 66 * to data being processed (that is, passes all the data as method parameters). 67 * 68 * @see org.apache.harmony.security.fortress.DefaultPolicyParser 69 */ 70public class DefaultPolicyScanner { 71 72 /** 73 * Specific exception class to signal policy file syntax error. 74 * 75 */ 76 public static class InvalidFormatException extends Exception { 77 78 /** 79 * @serial 80 */ 81 private static final long serialVersionUID = 5789786270390222184L; 82 83 /** 84 * Constructor with detailed message parameter. 85 */ 86 public InvalidFormatException(String arg0) { 87 super(arg0); 88 } 89 } 90 91 /** 92 * Configures passed tokenizer accordingly to supported syntax. 93 */ 94 protected StreamTokenizer configure(StreamTokenizer st) { 95 st.slashSlashComments(true); 96 st.slashStarComments(true); 97 st.wordChars('_', '_'); 98 st.wordChars('$', '$'); 99 return st; 100 } 101 102 /** 103 * Performs the main parsing loop. Starts with creating and configuring a 104 * StreamTokenizer instance; then tries to recognize <i>keystore </i> or 105 * <i>grant </i> keyword. When found, invokes read method corresponding to 106 * the clause and collects result to the passed collection. 107 * 108 * @param r 109 * policy stream reader 110 * @param grantEntries 111 * a collection to accumulate parsed GrantEntries 112 * @param keystoreEntries 113 * a collection to accumulate parsed KeystoreEntries 114 * @throws IOException 115 * if stream reading failed 116 * @throws InvalidFormatException 117 * if unexpected or unknown token encountered 118 */ 119 public void scanStream(Reader r, Collection<GrantEntry> grantEntries, 120 List<KeystoreEntry> keystoreEntries) throws IOException, 121 InvalidFormatException { 122 StreamTokenizer st = configure(new StreamTokenizer(r)); 123 //main parsing loop 124 parsing: while (true) { 125 switch (st.nextToken()) { 126 case StreamTokenizer.TT_EOF: //we've done the job 127 break parsing; 128 129 case StreamTokenizer.TT_WORD: 130 if (Util.equalsIgnoreCase("keystore", st.sval)) { //$NON-NLS-1$ 131 keystoreEntries.add(readKeystoreEntry(st)); 132 } else if (Util.equalsIgnoreCase("grant", st.sval)) { //$NON-NLS-1$ 133 grantEntries.add(readGrantEntry(st)); 134 } else { 135 handleUnexpectedToken(st, Messages.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) throws IOException, 205 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.getString("security.8B")); //$NON-NLS-1$ 216 } 217 } else if (Util.equalsIgnoreCase("codebase", st.sval)) { //$NON-NLS-1$ 218 if (st.nextToken() == '"') { 219 ge.codebase = st.sval; 220 } else { 221 handleUnexpectedToken(st, Messages.getString("security.8C")); //$NON-NLS-1$ 222 } 223 } else if (Util.equalsIgnoreCase("principal", st.sval)) { //$NON-NLS-1$ 224 ge.addPrincipal(readPrincipalEntry(st)); 225 } else { 226 handleUnexpectedToken(st); 227 } 228 break; 229 230 case ',': //just delimiter of entries 231 break; 232 233 case '{': 234 ge.permissions = readPermissionEntries(st); 235 break parsing; 236 237 default: // handle token in the main loop 238 st.pushBack(); 239 break parsing; 240 } 241 } 242 243 return ge; 244 } 245 246 /** 247 * Tries to read <i>Principal </i> entry fields. The expected syntax is 248 * 249 * <pre> 250 * 251 * [ principal_class_name ] "principal_name" 252 * 253 * </pre> 254 * 255 * Both class and name may be wildcards, wildcard names should not 256 * surrounded by quotes. 257 * 258 * @return successfully parsed PrincipalEntry 259 * @throws IOException 260 * if stream reading failed 261 * @throws InvalidFormatException 262 * if unexpected or unknown token encountered 263 */ 264 protected PrincipalEntry readPrincipalEntry(StreamTokenizer st) 265 throws IOException, InvalidFormatException { 266 PrincipalEntry pe = new PrincipalEntry(); 267 if (st.nextToken() == StreamTokenizer.TT_WORD) { 268 pe.klass = st.sval; 269 st.nextToken(); 270 } else if (st.ttype == '*') { 271 pe.klass = PrincipalEntry.WILDCARD; 272 st.nextToken(); 273 } 274 if (st.ttype == '"') { 275 pe.name = st.sval; 276 } else if (st.ttype == '*') { 277 pe.name = PrincipalEntry.WILDCARD; 278 } else { 279 handleUnexpectedToken(st, Messages.getString("security.8D")); //$NON-NLS-1$ 280 } 281 return pe; 282 } 283 284 /** 285 * Tries to read a list of <i>permission </i> entries. The expected syntax 286 * is 287 * 288 * <pre> 289 * 290 * permission permission_class_name 291 * [ "target_name" ] [, "action_list"] 292 * [, signedby "name1,name2,..."]; 293 * 294 * </pre> 295 * 296 * List is terminated by '}' (closing curly brace) symbol. 297 * 298 * @return collection of successfully parsed PermissionEntries 299 * @throws IOException 300 * if stream reading failed 301 * @throws InvalidFormatException 302 * if unexpected or unknown token encountered 303 */ 304 protected Collection<PermissionEntry> readPermissionEntries( 305 StreamTokenizer st) throws IOException, InvalidFormatException { 306 Collection<PermissionEntry> permissions = new HashSet<PermissionEntry>(); 307 parsing: while (true) { 308 switch (st.nextToken()) { 309 310 case StreamTokenizer.TT_WORD: 311 if (Util.equalsIgnoreCase("permission", st.sval)) { //$NON-NLS-1$ 312 PermissionEntry pe = new PermissionEntry(); 313 if (st.nextToken() == StreamTokenizer.TT_WORD) { 314 pe.klass = st.sval; 315 if (st.nextToken() == '"') { 316 pe.name = st.sval; 317 st.nextToken(); 318 } 319 if (st.ttype == ',') { 320 st.nextToken(); 321 } 322 if (st.ttype == '"') { 323 pe.actions = st.sval; 324 if (st.nextToken() == ',') { 325 st.nextToken(); 326 } 327 } 328 if (st.ttype == StreamTokenizer.TT_WORD 329 && Util.equalsIgnoreCase("signedby", st.sval)) { //$NON-NLS-1$ 330 if (st.nextToken() == '"') { 331 pe.signers = st.sval; 332 } else { 333 handleUnexpectedToken(st); 334 } 335 } else { // handle token in the next iteration 336 st.pushBack(); 337 } 338 permissions.add(pe); 339 continue parsing; 340 } 341 } 342 handleUnexpectedToken(st, Messages.getString("security.8E")); //$NON-NLS-1$ 343 break; 344 345 case ';': //just delimiter of entries 346 break; 347 348 case '}': //end of list 349 break parsing; 350 351 default: // invalid token 352 handleUnexpectedToken(st); 353 break; 354 } 355 } 356 357 return permissions; 358 } 359 360 /** 361 * Formats a detailed description of tokenizer status: current token, 362 * current line number, etc. 363 */ 364 protected String composeStatus(StreamTokenizer st) { 365 return st.toString(); 366 } 367 368 /** 369 * Throws InvalidFormatException with detailed diagnostics. 370 * 371 * @param st 372 * a tokenizer holding the erroneous token 373 * @param message 374 * a user-friendly comment, probably explaining expected syntax. 375 * Should not be <code>null</code>- use the overloaded 376 * single-parameter method instead. 377 */ 378 protected final void handleUnexpectedToken(StreamTokenizer st, 379 String message) throws InvalidFormatException { 380 throw new InvalidFormatException(Messages.getString("security.8F", //$NON-NLS-1$ 381 composeStatus(st), message)); 382 } 383 384 /** 385 * Throws InvalidFormatException with error status: which token is 386 * unexpected on which line. 387 * 388 * @param st 389 * a tokenizer holding the erroneous token 390 */ 391 protected final void handleUnexpectedToken(StreamTokenizer st) 392 throws InvalidFormatException { 393 throw new InvalidFormatException(Messages.getString("security.90", //$NON-NLS-1$ 394 composeStatus(st))); 395 } 396 397 /** 398 * Compound token representing <i>keystore </i> clause. See policy format 399 * {@link org.apache.harmony.security.fortress.DefaultPolicy description}for details. 400 * 401 * @see org.apache.harmony.security.fortress.DefaultPolicyParser 402 * @see org.apache.harmony.security.DefaultPolicyScanner 403 */ 404 public static class KeystoreEntry { 405 406 /** 407 * The URL part of keystore clause. 408 */ 409 public String url; 410 411 /** 412 * The typename part of keystore clause. 413 */ 414 public String type; 415 } 416 417 /** 418 * Compound token representing <i>grant </i> clause. See policy format 419 * {@link org.apache.harmony.security.fortress.DefaultPolicy description}for details. 420 * 421 * @see org.apache.harmony.security.fortress.DefaultPolicyParser 422 * @see org.apache.harmony.security.DefaultPolicyScanner 423 */ 424 public static class GrantEntry { 425 426 /** 427 * The signers part of grant clause. This is a comma-separated list of 428 * certificate aliases. 429 */ 430 public String signers; 431 432 /** 433 * The codebase part of grant clause. This is an URL from which code 434 * originates. 435 */ 436 public String codebase; 437 438 /** 439 * Collection of PrincipalEntries of grant clause. 440 */ 441 public Collection<PrincipalEntry> principals; 442 443 /** 444 * Collection of PermissionEntries of grant clause. 445 */ 446 public Collection<PermissionEntry> permissions; 447 448 /** 449 * Adds specified element to the <code>principals</code> collection. 450 * If collection does not exist yet, creates a new one. 451 */ 452 public void addPrincipal(PrincipalEntry pe) { 453 if (principals == null) { 454 principals = new HashSet<PrincipalEntry>(); 455 } 456 principals.add(pe); 457 } 458 459 } 460 461 /** 462 * Compound token representing <i>principal </i> entry of a <i>grant </i> 463 * clause. See policy format 464 * {@link org.apache.harmony.security.fortress.DefaultPolicy description}for details. 465 * 466 * @see org.apache.harmony.security.fortress.DefaultPolicyParser 467 * @see org.apache.harmony.security.DefaultPolicyScanner 468 */ 469 public static class PrincipalEntry { 470 471 /** 472 * Wildcard value denotes any class and/or any name. 473 * Must be asterisk, for proper general expansion and 474 * PrivateCredentialsPermission wildcarding 475 */ 476 public static final String WILDCARD = "*"; //$NON-NLS-1$ 477 478 /** 479 * The classname part of principal clause. 480 */ 481 public String klass; 482 483 /** 484 * The name part of principal clause. 485 */ 486 public String name; 487 } 488 489 /** 490 * Compound token representing <i>permission </i> entry of a <i>grant </i> 491 * clause. See policy format 492 * {@link org.apache.harmony.security.fortress.DefaultPolicy description}for details. 493 * 494 * @see org.apache.harmony.security.fortress.DefaultPolicyParser 495 * @see org.apache.harmony.security.DefaultPolicyScanner 496 */ 497 public static class PermissionEntry { 498 499 /** 500 * The classname part of permission clause. 501 */ 502 public String klass; 503 504 /** 505 * The name part of permission clause. 506 */ 507 public String name; 508 509 /** 510 * The actions part of permission clause. 511 */ 512 public String actions; 513 514 /** 515 * The signers part of permission clause. This is a comma-separated list 516 * of certificate aliases. 517 */ 518 public String signers; 519 } 520} 521