Source for gnu.java.security.PolicyFile

   1: /* PolicyFile.java -- policy file reader
   2:    Copyright (C) 2004, 2005, 2006  Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: package gnu.java.security;
  39: 
  40: import gnu.classpath.debug.Component;
  41: import gnu.classpath.debug.SystemLogger;
  42: 
  43: import gnu.java.lang.CPStringBuilder;
  44: import gnu.java.security.action.GetPropertyAction;
  45: 
  46: import java.io.File;
  47: import java.io.IOException;
  48: import java.io.InputStreamReader;
  49: import java.io.StreamTokenizer;
  50: import java.lang.reflect.Constructor;
  51: import java.net.MalformedURLException;
  52: import java.net.URL;
  53: import java.security.AccessController;
  54: import java.security.CodeSource;
  55: import java.security.KeyStore;
  56: import java.security.KeyStoreException;
  57: import java.security.Permission;
  58: import java.security.PermissionCollection;
  59: import java.security.Permissions;
  60: import java.security.Policy;
  61: import java.security.Principal;
  62: import java.security.PrivilegedActionException;
  63: import java.security.PrivilegedExceptionAction;
  64: import java.security.Security;
  65: import java.security.UnresolvedPermission;
  66: import java.security.cert.Certificate;
  67: import java.security.cert.X509Certificate;
  68: import java.util.Enumeration;
  69: import java.util.HashMap;
  70: import java.util.Iterator;
  71: import java.util.LinkedList;
  72: import java.util.List;
  73: import java.util.Map;
  74: import java.util.StringTokenizer;
  75: import java.util.logging.Logger;
  76: 
  77: /**
  78:  * An implementation of a {@link java.security.Policy} object whose
  79:  * permissions are specified by a <em>policy file</em>.
  80:  *
  81:  * <p>The approximate syntax of policy files is:</p>
  82:  *
  83:  * <pre>
  84:  * policyFile ::= keystoreOrGrantEntries ;
  85:  *
  86:  * keystoreOrGrantEntries ::= keystoreOrGrantEntry |
  87:  *                            keystoreOrGrantEntries keystoreOrGrantEntry |
  88:  *                            EMPTY ;
  89:  *
  90:  * keystoreOrGrantEntry ::= keystoreEntry | grantEntry ;
  91:  *
  92:  * keystoreEntry ::= "keystore" keystoreUrl ';' |
  93:  *                   "keystore" keystoreUrl ',' keystoreAlgorithm ';' ;
  94:  *
  95:  * keystoreUrl ::= URL ;
  96:  * keystoreAlgorithm ::= STRING ;
  97:  *
  98:  * grantEntry ::= "grant" domainParameters '{' permissions '}' ';'
  99:  *
 100:  * domainParameters ::= domainParameter |
 101:  *                      domainParameter ',' domainParameters ;
 102:  *
 103:  * domainParameter ::= "signedBy" signerNames |
 104:  *                     "codeBase" codeBaseUrl |
 105:  *                     "principal" principalClassName principalName |
 106:  *                     "principal" principalName ;
 107:  *
 108:  * signerNames ::= quotedString ;
 109:  * codeBaseUrl ::= URL ;
 110:  * principalClassName ::= STRING ;
 111:  * principalName ::= quotedString ;
 112:  *
 113:  * quotedString ::= quoteChar STRING quoteChar ;
 114:  * quoteChar ::= '"' | '\'';
 115:  *
 116:  * permissions ::= permission | permissions permission ;
 117:  *
 118:  * permission ::= "permission" permissionClassName permissionTarget permissionAction |
 119:  *                "permission" permissionClassName permissionTarget |
 120:  *                "permission" permissionClassName;
 121:  * </pre>
 122:  *
 123:  * <p>Comments are either form of Java comments. Keystore entries only
 124:  * affect subsequent grant entries, so if a grant entry preceeds a
 125:  * keystore entry, that grant entry is not affected by that keystore
 126:  * entry. Certian instances of <code>${property-name}</code> will be
 127:  * replaced with <code>System.getProperty("property-name")</code> in
 128:  * quoted strings.</p>
 129:  *
 130:  * <p>This class will load the following files when created or
 131:  * refreshed, in order:</p>
 132:  *
 133:  * <ol>
 134:  * <li>The file <code>${java.home}/lib/security/java.policy</code>.</li>
 135:  * <li>All URLs specified by security properties
 136:  * <code>"policy.file.<i>n</i>"</code>, for increasing <i>n</i>
 137:  * starting from 1. The sequence stops at the first undefined
 138:  * property, so you must set <code>"policy.file.1"</code> if you also
 139:  * set <code>"policy.file.2"</code>, and so on.</li>
 140:  * <li>The URL specified by the property
 141:  * <code>"java.security.policy"</code>.</li>
 142:  * </ol>
 143:  *
 144:  * @author Casey Marshall (csm@gnu.org)
 145:  * @see java.security.Policy
 146:  */
 147: public final class PolicyFile extends Policy
 148: {
 149: 
 150:   // Constants and fields.
 151:   // -------------------------------------------------------------------------
 152: 
 153:   protected static final Logger logger = SystemLogger.SYSTEM;
 154:   // Added to cut redundant AccessController.doPrivileged calls
 155:   private static GetPropertyAction prop = new GetPropertyAction("file.separator");
 156:   private static final String fs = (String) AccessController.doPrivileged(prop);
 157: 
 158:   private static final String DEFAULT_POLICY =
 159:     (String) AccessController.doPrivileged(prop.setParameters("java.home"))
 160:     + fs + "lib" + fs + "security" + fs + "java.policy";
 161:   private static final String DEFAULT_USER_POLICY =
 162:     (String) AccessController.doPrivileged(prop.setParameters("user.home")) +
 163:     fs + ".java.policy";
 164: 
 165:   private final Map cs2pc;
 166: 
 167:   // Constructors.
 168:   // -------------------------------------------------------------------------
 169: 
 170:   public PolicyFile()
 171:   {
 172:     cs2pc = new HashMap();
 173:     refresh();
 174:   }
 175: 
 176:   // Instance methods.
 177:   // -------------------------------------------------------------------------
 178: 
 179:   public PermissionCollection getPermissions(CodeSource codeSource)
 180:   {
 181:     Permissions perms = new Permissions();
 182:     for (Iterator it = cs2pc.entrySet().iterator(); it.hasNext(); )
 183:       {
 184:         Map.Entry e = (Map.Entry) it.next();
 185:         CodeSource cs = (CodeSource) e.getKey();
 186:         if (cs.implies(codeSource))
 187:           {
 188:             logger.log (Component.POLICY, "{0} -> {1}", new Object[]
 189:               { cs, codeSource });
 190:             PermissionCollection pc = (PermissionCollection) e.getValue();
 191:             for (Enumeration ee = pc.elements(); ee.hasMoreElements(); )
 192:               {
 193:                 perms.add((Permission) ee.nextElement());
 194:               }
 195:           }
 196:         else
 197:           logger.log (Component.POLICY, "{0} !-> {1}", new Object[]
 198:             { cs, codeSource });
 199:       }
 200:     logger.log (Component.POLICY, "returning permissions {0} for {1}",
 201:                 new Object[] { perms, codeSource });
 202:     return perms;
 203:   }
 204: 
 205:   public void refresh()
 206:   {
 207:     cs2pc.clear();
 208:     final List policyFiles = new LinkedList();
 209:     try
 210:       {
 211:         policyFiles.add (new File (DEFAULT_POLICY).toURL());
 212:         policyFiles.add (new File (DEFAULT_USER_POLICY).toURL ());
 213: 
 214:         AccessController.doPrivileged(
 215:           new PrivilegedExceptionAction()
 216:           {
 217:             public Object run() throws Exception
 218:             {
 219:               String allow = Security.getProperty ("policy.allowSystemProperty");
 220:               if (allow == null || Boolean.getBoolean (allow))
 221:                 {
 222:                   String s = System.getProperty ("java.security.policy");
 223:                   logger.log (Component.POLICY, "java.security.policy={0}", s);
 224:                   if (s != null)
 225:                     {
 226:                       boolean only = s.startsWith ("=");
 227:                       if (only)
 228:                         s = s.substring (1);
 229:                       policyFiles.clear ();
 230:                       policyFiles.add (new URL (s));
 231:                       if (only)
 232:                         return null;
 233:                     }
 234:                 }
 235:               for (int i = 1; ; i++)
 236:                 {
 237:                   String pname = "policy.url." + i;
 238:                   String s = Security.getProperty (pname);
 239:                   logger.log (Component.POLICY, "{0}={1}", new Object []
 240:                     { pname, s });
 241:                   if (s == null)
 242:                     break;
 243:                   policyFiles.add (new URL (s));
 244:                 }
 245:               return null;
 246:             }
 247:           });
 248:       }
 249:     catch (PrivilegedActionException pae)
 250:       {
 251:         logger.log (Component.POLICY, "reading policy properties", pae);
 252:       }
 253:     catch (MalformedURLException mue)
 254:       {
 255:         logger.log (Component.POLICY, "setting default policies", mue);
 256:       }
 257: 
 258:     logger.log (Component.POLICY, "building policy from URLs {0}",
 259:                 policyFiles);
 260:     for (Iterator it = policyFiles.iterator(); it.hasNext(); )
 261:       {
 262:         try
 263:           {
 264:             URL url = (URL) it.next();
 265:             parse(url);
 266:           }
 267:         catch (IOException ioe)
 268:           {
 269:             logger.log (Component.POLICY, "reading policy", ioe);
 270:           }
 271:       }
 272:   }
 273: 
 274:   public String toString()
 275:   {
 276:     return super.toString() + " [ " + cs2pc.toString() + " ]";
 277:   }
 278: 
 279:   // Own methods.
 280:   // -------------------------------------------------------------------------
 281: 
 282:   private static final int STATE_BEGIN = 0;
 283:   private static final int STATE_GRANT = 1;
 284:   private static final int STATE_PERMS = 2;
 285: 
 286:   /**
 287:    * Parse a policy file, incorporating the permission definitions
 288:    * described therein.
 289:    *
 290:    * @param url The URL of the policy file to read.
 291:    * @throws IOException if an I/O error occurs, or if the policy file
 292:    * cannot be parsed.
 293:    */
 294:   private void parse(final URL url) throws IOException
 295:   {
 296:     logger.log (Component.POLICY, "reading policy file from {0}", url);
 297:     final StreamTokenizer in = new StreamTokenizer(new InputStreamReader(url.openStream()));
 298:     in.resetSyntax();
 299:     in.slashSlashComments(true);
 300:     in.slashStarComments(true);
 301:     in.wordChars('A', 'Z');
 302:     in.wordChars('a', 'z');
 303:     in.wordChars('0', '9');
 304:     in.wordChars('.', '.');
 305:     in.wordChars('_', '_');
 306:     in.wordChars('$', '$');
 307:     in.whitespaceChars(' ', ' ');
 308:     in.whitespaceChars('\t', '\t');
 309:     in.whitespaceChars('\f', '\f');
 310:     in.whitespaceChars('\n', '\n');
 311:     in.whitespaceChars('\r', '\r');
 312:     in.quoteChar('\'');
 313:     in.quoteChar('"');
 314: 
 315:     int tok;
 316:     int state = STATE_BEGIN;
 317:     List keystores = new LinkedList();
 318:     URL currentBase = null;
 319:     List currentCerts = new LinkedList();
 320:     Permissions currentPerms = new Permissions();
 321:     while ((tok = in.nextToken()) != StreamTokenizer.TT_EOF)
 322:       {
 323:         switch (tok)
 324:           {
 325:           case '{':
 326:             if (state != STATE_GRANT)
 327:               error(url, in, "spurious '{'");
 328:             state = STATE_PERMS;
 329:             tok = in.nextToken();
 330:             break;
 331:           case '}':
 332:             if (state != STATE_PERMS)
 333:               error(url, in, "spurious '}'");
 334:             state = STATE_BEGIN;
 335:             currentPerms.setReadOnly();
 336:             Certificate[] c = null;
 337:             if (!currentCerts.isEmpty())
 338:               c = (Certificate[]) currentCerts.toArray(new Certificate[currentCerts.size()]);
 339:             cs2pc.put(new CodeSource(currentBase, c), currentPerms);
 340:             currentCerts.clear();
 341:             currentPerms = new Permissions();
 342:             currentBase = null;
 343:             tok = in.nextToken();
 344:             if (tok != ';')
 345:               in.pushBack();
 346:             continue;
 347:           }
 348:         if (tok != StreamTokenizer.TT_WORD)
 349:           {
 350:             error(url, in, "expecting word token");
 351:           }
 352: 
 353:         // keystore "<keystore-path>" [',' "<keystore-type>"] ';'
 354:         if (in.sval.equalsIgnoreCase("keystore"))
 355:           {
 356:             String alg = KeyStore.getDefaultType();
 357:             tok = in.nextToken();
 358:             if (tok != '"' && tok != '\'')
 359:               error(url, in, "expecting key store URL");
 360:             String store = in.sval;
 361:             tok = in.nextToken();
 362:             if (tok == ',')
 363:               {
 364:                 tok = in.nextToken();
 365:                 if (tok != '"' && tok != '\'')
 366:                   error(url, in, "expecting key store type");
 367:                 alg = in.sval;
 368:                 tok = in.nextToken();
 369:               }
 370:             if (tok != ';')
 371:               error(url, in, "expecting semicolon");
 372:             try
 373:               {
 374:                 KeyStore keystore = KeyStore.getInstance(alg);
 375:                 keystore.load(new URL(url, store).openStream(), null);
 376:                 keystores.add(keystore);
 377:               }
 378:             catch (Exception x)
 379:               {
 380:                 error(url, in, x.toString());
 381:               }
 382:           }
 383:         else if (in.sval.equalsIgnoreCase("grant"))
 384:           {
 385:             if (state != STATE_BEGIN)
 386:               error(url, in, "extraneous grant keyword");
 387:             state = STATE_GRANT;
 388:           }
 389:         else if (in.sval.equalsIgnoreCase("signedBy"))
 390:           {
 391:             if (state != STATE_GRANT && state != STATE_PERMS)
 392:               error(url, in, "spurious 'signedBy'");
 393:             if (keystores.isEmpty())
 394:               error(url, in, "'signedBy' with no keystores");
 395:             tok = in.nextToken();
 396:             if (tok != '"' && tok != '\'')
 397:               error(url, in, "expecting signedBy name");
 398:             StringTokenizer st = new StringTokenizer(in.sval, ",");
 399:             while (st.hasMoreTokens())
 400:               {
 401:                 String alias = st.nextToken();
 402:                 for (Iterator it = keystores.iterator(); it.hasNext(); )
 403:                   {
 404:                     KeyStore keystore = (KeyStore) it.next();
 405:                     try
 406:                       {
 407:                         if (keystore.isCertificateEntry(alias))
 408:                           currentCerts.add(keystore.getCertificate(alias));
 409:                       }
 410:                     catch (KeyStoreException kse)
 411:                       {
 412:                         error(url, in, kse.toString());
 413:                       }
 414:                   }
 415:               }
 416:             tok = in.nextToken();
 417:             if (tok != ',')
 418:               {
 419:                 if (state != STATE_GRANT)
 420:                   error(url, in, "spurious ','");
 421:                 in.pushBack();
 422:               }
 423:           }
 424:         else if (in.sval.equalsIgnoreCase("codeBase"))
 425:           {
 426:             if (state != STATE_GRANT)
 427:               error(url, in, "spurious 'codeBase'");
 428:             tok = in.nextToken();
 429:             if (tok != '"' && tok != '\'')
 430:               error(url, in, "expecting code base URL");
 431:             String base = expand(in.sval);
 432:             if (File.separatorChar != '/')
 433:               base = base.replace(File.separatorChar, '/');
 434:             try
 435:               {
 436:                 currentBase = new URL(base);
 437:               }
 438:             catch (MalformedURLException mue)
 439:               {
 440:                 error(url, in, mue.toString());
 441:               }
 442:             tok = in.nextToken();
 443:             if (tok != ',')
 444:               in.pushBack();
 445:           }
 446:         else if (in.sval.equalsIgnoreCase("principal"))
 447:           {
 448:             if (state != STATE_GRANT)
 449:               error(url, in, "spurious 'principal'");
 450:             tok = in.nextToken();
 451:             if (tok == StreamTokenizer.TT_WORD)
 452:               {
 453:                 tok = in.nextToken();
 454:                 if (tok != '"' && tok != '\'')
 455:                   error(url, in, "expecting principal name");
 456:                 String name = in.sval;
 457:                 Principal p = null;
 458:                 try
 459:                   {
 460:                     Class pclass = Class.forName(in.sval);
 461:                     Constructor c =
 462:                       pclass.getConstructor(new Class[] { String.class });
 463:                     p = (Principal) c.newInstance(new Object[] { name });
 464:                   }
 465:                 catch (Exception x)
 466:                   {
 467:                     error(url, in, x.toString());
 468:                   }
 469:                 for (Iterator it = keystores.iterator(); it.hasNext(); )
 470:                   {
 471:                     KeyStore ks = (KeyStore) it.next();
 472:                     try
 473:                       {
 474:                         for (Enumeration e = ks.aliases(); e.hasMoreElements(); )
 475:                           {
 476:                             String alias = (String) e.nextElement();
 477:                             if (ks.isCertificateEntry(alias))
 478:                               {
 479:                                 Certificate cert = ks.getCertificate(alias);
 480:                                 if (!(cert instanceof X509Certificate))
 481:                                   continue;
 482:                                 if (p.equals(((X509Certificate) cert).getSubjectDN()) ||
 483:                                     p.equals(((X509Certificate) cert).getSubjectX500Principal()))
 484:                                   currentCerts.add(cert);
 485:                               }
 486:                           }
 487:                       }
 488:                     catch (KeyStoreException kse)
 489:                       {
 490:                         error(url, in, kse.toString());
 491:                       }
 492:                   }
 493:               }
 494:             else if (tok == '"' || tok == '\'')
 495:               {
 496:                 String alias = in.sval;
 497:                 for (Iterator it = keystores.iterator(); it.hasNext(); )
 498:                   {
 499:                     KeyStore ks = (KeyStore) it.next();
 500:                     try
 501:                       {
 502:                         if (ks.isCertificateEntry(alias))
 503:                           currentCerts.add(ks.getCertificate(alias));
 504:                       }
 505:                     catch (KeyStoreException kse)
 506:                       {
 507:                         error(url, in, kse.toString());
 508:                       }
 509:                   }
 510:               }
 511:             else
 512:               error(url, in, "expecting principal");
 513:             tok = in.nextToken();
 514:             if (tok != ',')
 515:               in.pushBack();
 516:           }
 517:         else if (in.sval.equalsIgnoreCase("permission"))
 518:           {
 519:             if (state != STATE_PERMS)
 520:               error(url, in, "spurious 'permission'");
 521:             tok = in.nextToken();
 522:             if (tok != StreamTokenizer.TT_WORD)
 523:               error(url, in, "expecting permission class name");
 524:             String className = in.sval;
 525:             Class clazz = null;
 526:             try
 527:               {
 528:                 clazz = Class.forName(className);
 529:               }
 530:             catch (ClassNotFoundException cnfe)
 531:               {
 532:               }
 533:             tok = in.nextToken();
 534:             if (tok == ';')
 535:               {
 536:                 if (clazz == null)
 537:                   {
 538:                     currentPerms.add(new UnresolvedPermission(className,
 539:                       null, null, (Certificate[]) currentCerts.toArray(new Certificate[currentCerts.size()])));
 540:                     continue;
 541:                   }
 542:                 try
 543:                   {
 544:                     currentPerms.add((Permission) clazz.newInstance());
 545:                   }
 546:                 catch (Exception x)
 547:                   {
 548:                     error(url, in, x.toString());
 549:                   }
 550:                 continue;
 551:               }
 552:             if (tok != '"' && tok != '\'')
 553:               error(url, in, "expecting permission target");
 554:             String target = expand(in.sval);
 555:             tok = in.nextToken();
 556:             if (tok == ';')
 557:               {
 558:                 if (clazz == null)
 559:                   {
 560:                     currentPerms.add(new UnresolvedPermission(className,
 561:                       target, null, (Certificate[]) currentCerts.toArray(new Certificate[currentCerts.size()])));
 562:                     continue;
 563:                   }
 564:                 try
 565:                   {
 566:                     Constructor c =
 567:                       clazz.getConstructor(new Class[] { String.class });
 568:                     currentPerms.add((Permission) c.newInstance(
 569:                       new Object[] { target }));
 570:                   }
 571:                 catch (Exception x)
 572:                   {
 573:                     error(url, in, x.toString());
 574:                   }
 575:                 continue;
 576:               }
 577:             if (tok != ',')
 578:               error(url, in, "expecting ','");
 579:             tok = in.nextToken();
 580:             if (tok == StreamTokenizer.TT_WORD)
 581:               {
 582:                 if (!in.sval.equalsIgnoreCase("signedBy"))
 583:                   error(url, in, "expecting 'signedBy'");
 584:                 try
 585:                   {
 586:                     Constructor c =
 587:                       clazz.getConstructor(new Class[] { String.class });
 588:                     currentPerms.add((Permission) c.newInstance(
 589:                       new Object[] { target }));
 590:                   }
 591:                 catch (Exception x)
 592:                   {
 593:                     error(url, in, x.toString());
 594:                   }
 595:                 in.pushBack();
 596:                 continue;
 597:               }
 598:             if (tok != '"' && tok != '\'')
 599:               error(url, in, "expecting permission action");
 600:             String action = in.sval;
 601:             if (clazz == null)
 602:               {
 603:                 currentPerms.add(new UnresolvedPermission(className,
 604:                   target, action, (Certificate[]) currentCerts.toArray(new Certificate[currentCerts.size()])));
 605:                 continue;
 606:               }
 607:             else
 608:               {
 609:                 try
 610:                   {
 611:                     Constructor c = clazz.getConstructor(
 612:                       new Class[] { String.class, String.class });
 613:                     currentPerms.add((Permission) c.newInstance(
 614:                       new Object[] { target, action }));
 615:                   }
 616:                 catch (Exception x)
 617:                   {
 618:                     error(url, in, x.toString());
 619:                   }
 620:               }
 621:             tok = in.nextToken();
 622:             if (tok != ';' && tok != ',')
 623:               error(url, in, "expecting ';' or ','");
 624:           }
 625:       }
 626:   }
 627: 
 628:   /**
 629:    * Expand all instances of <code>"${property-name}"</code> into
 630:    * <code>System.getProperty("property-name")</code>.
 631:    */
 632:   private static String expand(final String s)
 633:   {
 634:     final CPStringBuilder result = new CPStringBuilder();
 635:     final CPStringBuilder prop = new CPStringBuilder();
 636:     int state = 0;
 637:     for (int i = 0; i < s.length(); i++)
 638:       {
 639:         switch (state)
 640:           {
 641:           case 0:
 642:             if (s.charAt(i) == '$')
 643:               state = 1;
 644:             else
 645:               result.append(s.charAt(i));
 646:             break;
 647:           case 1:
 648:             if (s.charAt(i) == '{')
 649:               state = 2;
 650:             else
 651:               {
 652:                 state = 0;
 653:                 result.append('$').append(s.charAt(i));
 654:               }
 655:             break;
 656:           case 2:
 657:             if (s.charAt(i) == '}')
 658:               {
 659:                 String p = prop.toString();
 660:                 if (p.equals("/"))
 661:                   p = "file.separator";
 662:                 p = System.getProperty(p);
 663:                 if (p == null)
 664:                   p = "";
 665:                 result.append(p);
 666:                 prop.setLength(0);
 667:                 state = 0;
 668:               }
 669:             else
 670:               prop.append(s.charAt(i));
 671:             break;
 672:           }
 673:       }
 674:     if (state != 0)
 675:       result.append('$').append('{').append(prop);
 676:     return result.toString();
 677:   }
 678: 
 679:   /**
 680:    * I miss macros.
 681:    */
 682:   private static void error(URL base, StreamTokenizer in, String msg)
 683:     throws IOException
 684:   {
 685:     throw new IOException(base+":"+in.lineno()+": "+msg);
 686:   }
 687: }