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