1. /*
  2. * @(#)FilePermission.java 1.67 00/02/02
  3. *
  4. * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
  5. *
  6. * This software is the proprietary information of Sun Microsystems, Inc.
  7. * Use is subject to license terms.
  8. *
  9. */
  10. package java.io;
  11. import java.security.*;
  12. import java.util.Enumeration;
  13. import java.util.Vector;
  14. import java.util.StringTokenizer;
  15. /**
  16. * This class represents access to a file or directory. A FilePermission consists
  17. * of a pathname and a set of actions valid for that pathname.
  18. * <P>
  19. * Pathname is the pathname of the file or directory granted the specified
  20. * actions. A pathname that ends in "/*" (where "/" is
  21. * the file separator character, <code>File.separatorChar</code>) indicates
  22. * all the files and directories contained in that directory. A pathname
  23. * that ends with "/-" indicates (recursively) all files
  24. * and subdirectories contained in that directory. A pathname consisting of
  25. * the special token "<<ALL FILES>>" matches <bold>any</bold> file.
  26. * <P>
  27. * Note: A pathname consisting of a single "*" indicates all the files
  28. * in the current directory, while a pathname consisting of a single "-"
  29. * indicates all the files in the current directory and
  30. * (recursively) all files and subdirectories contained in the current
  31. * directory.
  32. * <P>
  33. * The actions to be granted are passed to the constructor in a string containing
  34. * a list of one or more comma-separated keywords. The possible keywords are
  35. * "read", "write", "execute", and "delete". Their meaning is defined as follows:
  36. * <P>
  37. * <DL>
  38. * <DT> read <DD> read permission
  39. * <DT> write <DD> write permission
  40. * <DT> execute
  41. * <DD> execute permission. Allows <code>Runtime.exec</code> to
  42. * be called. Corresponds to <code>SecurityManager.checkExec</code>.
  43. * <DT> delete
  44. * <DD> delete permission. Allows <code>File.delete</code> to
  45. * be called. Corresponds to <code>SecurityManager.checkDelete</code>.
  46. * </DL>
  47. * <P>
  48. * The actions string is converted to lowercase before processing.
  49. * <P>
  50. * Be careful when granting FilePermissions. Think about the implications
  51. * of granting read and especially write access to various files and
  52. * directories. The "<<ALL FILES>>" permission with write action is
  53. * especially dangerous. This grants permission to write to the entire
  54. * file system. One thing this effectively allows is replacement of the
  55. * system binary, including the JVM runtime environment.
  56. *
  57. * <p>Please note: Code can always read a file from the same
  58. * directory it's in (or a subdirectory of that directory); it does not
  59. * need explicit permission to do so.
  60. *
  61. * @see java.security.Permission
  62. * @see java.security.Permissions
  63. * @see java.security.PermissionCollection
  64. *
  65. * @version 1.67 00/02/02
  66. *
  67. * @author Marianne Mueller
  68. * @author Roland Schemers
  69. * @since 1.2
  70. *
  71. * @serial exclude
  72. */
  73. public final class FilePermission extends Permission implements Serializable {
  74. /**
  75. * Execute action.
  76. */
  77. private final static int EXECUTE = 0x1;
  78. /**
  79. * Write action.
  80. */
  81. private final static int WRITE = 0x2;
  82. /**
  83. * Read action.
  84. */
  85. private final static int READ = 0x4;
  86. /**
  87. * Delete action.
  88. */
  89. private final static int DELETE = 0x8;
  90. /**
  91. * All actions (read,write,execute,delete)
  92. */
  93. private final static int ALL = READ|WRITE|EXECUTE|DELETE;
  94. /**
  95. * No actions.
  96. */
  97. private final static int NONE = 0x0;
  98. // the actions mask
  99. private transient int mask;
  100. // does path indicate a directory? (wildcard or recursive)
  101. private transient boolean directory;
  102. // is it a recursive directory specification?
  103. private transient boolean recursive;
  104. /**
  105. * the actions string.
  106. *
  107. * @serial
  108. */
  109. private String actions; // Left null as long as possible, then
  110. // created and re-used in the getAction function.
  111. // canonicalized dir path. In the case of
  112. // directories, it is the name "/blah/*" or "/blah/-" without
  113. // the last character (the "*" or "-").
  114. private transient String cpath;
  115. // static Strings used by init(int mask)
  116. private static final String RECURSIVE = "-";
  117. private static final String WILD = "*";
  118. private static final String SEP_RECURSIVE = File.separator+RECURSIVE;
  119. private static final String SEP_WILD = File.separator+WILD;
  120. /*
  121. public String toString()
  122. {
  123. StringBuffer sb = new StringBuffer();
  124. sb.append("***\n");
  125. sb.append("cpath = "+cpath+"\n");
  126. sb.append("mask = "+mask+"\n");
  127. sb.append("actions = "+getActions()+"\n");
  128. sb.append("directory = "+directory+"\n");
  129. sb.append("recursive = "+recursive+"\n");
  130. sb.append("***\n");
  131. return sb.toString();
  132. }
  133. */
  134. private static final long serialVersionUID = 7930732926638008763L;
  135. /**
  136. * initialize a FilePermission object. Common to all constructors.
  137. * Also called during de-serialization.
  138. *
  139. * @param mask the actions mask to use.
  140. *
  141. */
  142. private void init(int mask)
  143. {
  144. if ((mask & ALL) != mask)
  145. throw new IllegalArgumentException("invalid actions mask");
  146. if (mask == NONE)
  147. throw new IllegalArgumentException("invalid actions mask");
  148. if (getName() == null)
  149. throw new NullPointerException("name can't be null");
  150. this.mask = mask;
  151. cpath = getName();
  152. if (cpath.equals("<<ALL FILES>>")) {
  153. directory = true;
  154. recursive = true;
  155. cpath = "";
  156. return;
  157. }
  158. if (cpath.endsWith(SEP_RECURSIVE) || cpath.equals(RECURSIVE)) {
  159. directory = true;
  160. recursive = true;
  161. cpath = cpath.substring(0, cpath.length()-1);
  162. } else if (cpath.endsWith(SEP_WILD) || cpath.equals(WILD)) {
  163. directory = true;
  164. //recursive = false;
  165. cpath = cpath.substring(0, cpath.length()-1);
  166. } else {
  167. // overkill since they are initialized to false, but
  168. // commented out here to remind us...
  169. //directory = false;
  170. //recursive = false;
  171. }
  172. if (cpath.equals("")) {
  173. cpath = (String) java.security.AccessController.doPrivileged(
  174. new sun.security.action.GetPropertyAction("user.dir"));
  175. }
  176. // store only the canonical cpath if possible
  177. // need a doPrivileged block as getCanonicalPath
  178. // might attempt to access user.dir to turn a relative
  179. // path into an absolute path.
  180. cpath = (String)
  181. AccessController.doPrivileged(
  182. new java.security.PrivilegedAction() {
  183. public Object run() {
  184. try {
  185. File file = new File(cpath);
  186. String canonical_path = file.getCanonicalPath();
  187. if (directory &&
  188. (!canonical_path.endsWith(File.separator))) {
  189. return canonical_path + File.separator;
  190. } else {
  191. return canonical_path;
  192. }
  193. } catch (IOException ioe) {
  194. // ignore if we can't canonicalize path?
  195. }
  196. return cpath;
  197. }
  198. });
  199. // XXX: at this point the path should be absolute. die if it isn't?
  200. }
  201. /**
  202. * Creates a new FilePermission object with the specified actions.
  203. * <i>path</i> is the pathname of a
  204. * file or directory, and <i>actions</i> contains a comma-separated list of the
  205. * desired actions granted on the file or directory. Possible actions are
  206. * "read", "write", "execute", and "delete".
  207. *
  208. * <p>A pathname that ends in "/*" (where "/" is
  209. * the file separator character, <code>File.separatorChar</code>) indicates
  210. * a directory and all the files contained in that directory. A pathname
  211. * that ends with "/-" indicates a directory and (recursively) all files
  212. * and subdirectories contained in that directory. The special pathname
  213. * "<<ALL FILES>>" matches all files.
  214. *
  215. * <p>A pathname consisting of a single "*" indicates all the files
  216. * in the current directory, while a pathname consisting of a single "-"
  217. * indicates all the files in the current directory and
  218. * (recursively) all files and subdirectories contained in the current
  219. * directory.
  220. *
  221. * @param path the pathname of the file/directory.
  222. * @param actions the action string.
  223. */
  224. public FilePermission(String path, String actions)
  225. {
  226. super(path);
  227. init(getMask(actions));
  228. }
  229. /**
  230. * Creates a new FilePermission object using an action mask.
  231. * More efficient than the FilePermission(String, String) constructor.
  232. * Can be used from within
  233. * code that needs to create a FilePermission object to pass into the
  234. * <code>implies</code> method.
  235. *
  236. * @param path the pathname of the file/directory.
  237. * @param mask the action mask to use.
  238. */
  239. // package private for use by the FilePermissionCollection add method
  240. FilePermission(String path, int mask)
  241. {
  242. super(path);
  243. init(mask);
  244. }
  245. /**
  246. * Checks if this FilePermission object "implies" the specified permission.
  247. * <P>
  248. * More specifically, this method returns true if:<p>
  249. * <ul>
  250. * <li> <i>p</i> is an instanceof FilePermission,<p>
  251. * <li> <i>p</i>'s actions are a proper subset of this
  252. * object's actions, and <p>
  253. * <li> <i>p</i>'s pathname is implied by this object's
  254. * pathname. For example, "/tmp/*" implies "/tmp/foo", since
  255. * "/tmp/*" encompasses the "/tmp" directory and all files in that
  256. * directory, including the one named "foo".
  257. * </ul>
  258. * @param p the permission to check against.
  259. *
  260. * @return true if the specified permission is implied by this object,
  261. * false if not.
  262. */
  263. public boolean implies(Permission p) {
  264. if (!(p instanceof FilePermission))
  265. return false;
  266. FilePermission that = (FilePermission) p;
  267. // we get the effective mask. i.e., the "and" of this and that.
  268. // They must be equal to that.mask for implies to return true.
  269. return ((this.mask & that.mask) == that.mask) && impliesIgnoreMask(that);
  270. }
  271. /**
  272. * Checks if the Permission's actions are a proper subset of the
  273. * this object's actions. Returns the effective mask iff the
  274. * this FilePermission's path also implies that FilePermission's path.
  275. *
  276. * @param that the FilePermission to check against.
  277. * @param exact return immediatly if the masks are not equal
  278. * @return the effective mask
  279. */
  280. boolean impliesIgnoreMask(FilePermission that) {
  281. if (this.directory) {
  282. if (this.recursive) {
  283. // make sure that.path is longer then path so
  284. // something like /foo/- does not imply /foo
  285. if (that.directory) {
  286. return (that.cpath.length() >= this.cpath.length()) &&
  287. that.cpath.startsWith(this.cpath);
  288. } else {
  289. return ((that.cpath.length() > this.cpath.length()) &&
  290. that.cpath.startsWith(this.cpath));
  291. }
  292. } else {
  293. if (that.directory) {
  294. // if the permission passed in is a directory
  295. // specification, make sure that a non-recursive
  296. // permission (i.e., this object) can't imply a recursive
  297. // permission.
  298. if (that.recursive)
  299. return false;
  300. else
  301. return (this.cpath.equals(that.cpath));
  302. } else {
  303. int last = that.cpath.lastIndexOf(File.separatorChar);
  304. if (last == -1)
  305. return false;
  306. else {
  307. String base = that.cpath.substring(0, last+1);
  308. return (this.cpath.equals(base));
  309. }
  310. }
  311. }
  312. } else {
  313. return (this.cpath.equals(that.cpath));
  314. }
  315. }
  316. /**
  317. * Checks two FilePermission objects for equality. Checks that <i>obj</i> is
  318. * a FilePermission, and has the same pathname and actions as this object.
  319. * <P>
  320. * @param obj the object we are testing for equality with this object.
  321. * @return true if obj is a FilePermission, and has the same pathname and
  322. * actions as this FilePermission object.
  323. */
  324. public boolean equals(Object obj) {
  325. if (obj == this)
  326. return true;
  327. if (! (obj instanceof FilePermission))
  328. return false;
  329. FilePermission that = (FilePermission) obj;
  330. return (this.mask == that.mask) &&
  331. this.cpath.equals(that.cpath) &&
  332. (this.directory == that.directory) &&
  333. (this.recursive == that.recursive);
  334. }
  335. /**
  336. * Returns the hash code value for this object.
  337. *
  338. * @return a hash code value for this object.
  339. */
  340. public int hashCode() {
  341. return this.cpath.hashCode();
  342. }
  343. /**
  344. * Converts an actions String to an actions mask.
  345. *
  346. * @param action the action string.
  347. * @return the actions mask.
  348. */
  349. private static int getMask(String actions) {
  350. int mask = NONE;
  351. if (actions == null) {
  352. return mask;
  353. }
  354. char[] a = actions.toCharArray();
  355. int i = a.length - 1;
  356. if (i < 0)
  357. return mask;
  358. while (i != -1) {
  359. char c;
  360. // skip whitespace
  361. while ((i!=-1) && ((c = a[i]) == ' ' ||
  362. c == '\r' ||
  363. c == '\n' ||
  364. c == '\f' ||
  365. c == '\t'))
  366. i--;
  367. // check for the known strings
  368. int matchlen;
  369. if (i >= 3 && (a[i-3] == 'r' || a[i-3] == 'R') &&
  370. (a[i-2] == 'e' || a[i-2] == 'E') &&
  371. (a[i-1] == 'a' || a[i-1] == 'A') &&
  372. (a[i] == 'd' || a[i] == 'D'))
  373. {
  374. matchlen = 4;
  375. mask |= READ;
  376. } else if (i >= 4 && (a[i-4] == 'w' || a[i-4] == 'W') &&
  377. (a[i-3] == 'r' || a[i-3] == 'R') &&
  378. (a[i-2] == 'i' || a[i-2] == 'I') &&
  379. (a[i-1] == 't' || a[i-1] == 'T') &&
  380. (a[i] == 'e' || a[i] == 'E'))
  381. {
  382. matchlen = 5;
  383. mask |= WRITE;
  384. } else if (i >= 6 && (a[i-6] == 'e' || a[i-6] == 'E') &&
  385. (a[i-5] == 'x' || a[i-5] == 'X') &&
  386. (a[i-4] == 'e' || a[i-4] == 'E') &&
  387. (a[i-3] == 'c' || a[i-3] == 'C') &&
  388. (a[i-2] == 'u' || a[i-2] == 'U') &&
  389. (a[i-1] == 't' || a[i-1] == 'T') &&
  390. (a[i] == 'e' || a[i] == 'E'))
  391. {
  392. matchlen = 7;
  393. mask |= EXECUTE;
  394. } else if (i >= 5 && (a[i-5] == 'd' || a[i-5] == 'D') &&
  395. (a[i-4] == 'e' || a[i-4] == 'E') &&
  396. (a[i-3] == 'l' || a[i-3] == 'L') &&
  397. (a[i-2] == 'e' || a[i-2] == 'E') &&
  398. (a[i-1] == 't' || a[i-1] == 'T') &&
  399. (a[i] == 'e' || a[i] == 'E'))
  400. {
  401. matchlen = 6;
  402. mask |= DELETE;
  403. } else {
  404. // parse error
  405. throw new IllegalArgumentException(
  406. "invalid permission: " + actions);
  407. }
  408. // make sure we didn't just match the tail of a word
  409. // like "ackbarfaccept". Also, skip to the comma.
  410. boolean seencomma = false;
  411. while (i >= matchlen && !seencomma) {
  412. switch(a[i-matchlen]) {
  413. case ',':
  414. seencomma = true;
  415. /*FALLTHROUGH*/
  416. case ' ': case '\r': case '\n':
  417. case '\f': case '\t':
  418. break;
  419. default:
  420. throw new IllegalArgumentException(
  421. "invalid permission: " + actions);
  422. }
  423. i--;
  424. }
  425. // point i at the location of the comma minus one (or -1).
  426. i -= matchlen;
  427. }
  428. return mask;
  429. }
  430. /**
  431. * Return the current action mask. Used by the FilePermissionCollection.
  432. *
  433. * @return the actions mask.
  434. */
  435. int getMask() {
  436. return mask;
  437. }
  438. /**
  439. * Return the canonical string representation of the actions.
  440. * Always returns present actions in the following order:
  441. * read, write, execute, delete.
  442. *
  443. * @return the canonical string representation of the actions.
  444. */
  445. private static String getActions(int mask)
  446. {
  447. StringBuffer sb = new StringBuffer();
  448. boolean comma = false;
  449. if ((mask & READ) == READ) {
  450. comma = true;
  451. sb.append("read");
  452. }
  453. if ((mask & WRITE) == WRITE) {
  454. if (comma) sb.append(',');
  455. else comma = true;
  456. sb.append("write");
  457. }
  458. if ((mask & EXECUTE) == EXECUTE) {
  459. if (comma) sb.append(',');
  460. else comma = true;
  461. sb.append("execute");
  462. }
  463. if ((mask & DELETE) == DELETE) {
  464. if (comma) sb.append(',');
  465. else comma = true;
  466. sb.append("delete");
  467. }
  468. return sb.toString();
  469. }
  470. /**
  471. * Returns the "canonical string representation" of the actions.
  472. * That is, this method always returns present actions in the following order:
  473. * read, write, execute, delete. For example, if this FilePermission object
  474. * allows both write and read actions, a call to <code>getActions</code>
  475. * will return the string "read,write".
  476. *
  477. * @return the canonical string representation of the actions.
  478. */
  479. public String getActions()
  480. {
  481. if (actions == null)
  482. actions = getActions(this.mask);
  483. return actions;
  484. }
  485. /**
  486. * Returns a new PermissionCollection object for storing FilePermission
  487. * objects.
  488. * <p>
  489. * FilePermission objects must be stored in a manner that allows them
  490. * to be inserted into the collection in any order, but that also enables the
  491. * PermissionCollection <code>implies</code>
  492. * method to be implemented in an efficient (and consistent) manner.
  493. *
  494. * <p>For example, if you have two FilePermissions:
  495. * <OL>
  496. * <LI> <code>"/tmp/-", "read"</code>
  497. * <LI> <code>"/tmp/scratch/foo", "write"</code>
  498. * </OL>
  499. *
  500. * <p>and you are calling the <code>implies</code> method with the FilePermission:
  501. *
  502. * <pre>
  503. * "/tmp/scratch/foo", "read,write",
  504. * </pre>
  505. *
  506. * then the <code>implies</code> function must
  507. * take into account both the "/tmp/-" and "/tmp/scratch/foo"
  508. * permissions, so the effective permission is "read,write",
  509. * and <code>implies</code> returns true. The "implies" semantics for
  510. * FilePermissions are handled properly by the PermissionCollection object
  511. * returned by this <code>newPermissionCollection</code> method.
  512. *
  513. * @return a new PermissionCollection object suitable for storing
  514. * FilePermissions.
  515. */
  516. public PermissionCollection newPermissionCollection() {
  517. return new FilePermissionCollection();
  518. }
  519. /**
  520. * WriteObject is called to save the state of the FilePermission
  521. * to a stream. The actions are serialized, and the superclass
  522. * takes care of the name.
  523. */
  524. private synchronized void writeObject(java.io.ObjectOutputStream s)
  525. throws IOException
  526. {
  527. // Write out the actions. The superclass takes care of the name
  528. // call getActions to make sure actions field is initialized
  529. if (actions == null)
  530. getActions();
  531. s.defaultWriteObject();
  532. }
  533. /**
  534. * readObject is called to restore the state of the FilePermission from
  535. * a stream.
  536. */
  537. private synchronized void readObject(java.io.ObjectInputStream s)
  538. throws IOException, ClassNotFoundException
  539. {
  540. // Read in the actions, then restore everything else by calling init.
  541. s.defaultReadObject();
  542. init(getMask(actions));
  543. }
  544. }
  545. /**
  546. * A FilePermissionCollection stores a set of FilePermission permissions.
  547. * FilePermission objects
  548. * must be stored in a manner that allows them to be inserted in any
  549. * order, but enable the implies function to evaluate the implies
  550. * method.
  551. * For example, if you have two FilePermissions:
  552. * <OL>
  553. * <LI> "/tmp/-", "read"
  554. * <LI> "/tmp/scratch/foo", "write"
  555. * </OL>
  556. * And you are calling the implies function with the FilePermission:
  557. * "/tmp/scratch/foo", "read,write", then the implies function must
  558. * take into account both the /tmp/- and /tmp/scratch/foo
  559. * permissions, so the effective permission is "read,write".
  560. *
  561. * @see java.security.Permission
  562. * @see java.security.Permissions
  563. * @see java.security.PermissionCollection
  564. *
  565. * @version 1.67 02/02/00
  566. *
  567. * @author Marianne Mueller
  568. * @author Roland Schemers
  569. *
  570. * @serial include
  571. *
  572. */
  573. final class FilePermissionCollection extends PermissionCollection
  574. implements Serializable {
  575. private Vector permissions;
  576. /**
  577. * Create an empty FilePermissions object.
  578. *
  579. */
  580. public FilePermissionCollection() {
  581. permissions = new Vector();
  582. }
  583. /**
  584. * Adds a permission to the FilePermissions. The key for the hash is
  585. * permission.path.
  586. *
  587. * @param permission the Permission object to add.
  588. *
  589. * @exception IllegalArgumentException - if the permission is not a
  590. * FilePermission
  591. *
  592. * @exception SecurityException - if this FilePermissionCollection object
  593. * has been marked readonly
  594. */
  595. public void add(Permission permission)
  596. {
  597. if (! (permission instanceof FilePermission))
  598. throw new IllegalArgumentException("invalid permission: "+
  599. permission);
  600. if (isReadOnly())
  601. throw new SecurityException("attempt to add a Permission to a readonly PermissionCollection");
  602. permissions.addElement(permission);
  603. }
  604. /**
  605. * Check and see if this set of permissions implies the permissions
  606. * expressed in "permission".
  607. *
  608. * @param p the Permission object to compare
  609. *
  610. * @return true if "permission" is a proper subset of a permission in
  611. * the set, false if not.
  612. */
  613. public boolean implies(Permission permission)
  614. {
  615. if (! (permission instanceof FilePermission))
  616. return false;
  617. FilePermission fp = (FilePermission) permission;
  618. int desired = fp.getMask();
  619. int effective = 0;
  620. int needed = desired;
  621. Enumeration e = permissions.elements();
  622. while (e.hasMoreElements()) {
  623. FilePermission x = (FilePermission) e.nextElement();
  624. if (((needed & x.getMask()) != 0) && x.impliesIgnoreMask(fp)) {
  625. effective |= x.getMask();
  626. if ((effective & desired) == desired)
  627. return true;
  628. needed = (desired ^ effective);
  629. }
  630. }
  631. return false;
  632. }
  633. /**
  634. * Returns an enumeration of all the FilePermission objects in the
  635. * container.
  636. *
  637. * @return an enumeration of all the FilePermission objects.
  638. */
  639. public Enumeration elements()
  640. {
  641. return permissions.elements();
  642. }
  643. }