0001: /*
0002: *
0003: *
0004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
0005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
0006: *
0007: * This program is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU General Public License version
0009: * 2 only, as published by the Free Software Foundation.
0010: *
0011: * This program is distributed in the hope that it will be useful, but
0012: * WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0014: * General Public License version 2 for more details (a copy is
0015: * included at /legal/license.txt).
0016: *
0017: * You should have received a copy of the GNU General Public License
0018: * version 2 along with this work; if not, write to the Free Software
0019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
0020: * 02110-1301 USA
0021: *
0022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
0023: * Clara, CA 95054 or visit www.sun.com if you need additional
0024: * information or have any questions.
0025: */
0026:
0027: package com.sun.midp.io.j2me.file;
0028:
0029: import com.sun.midp.security.Permissions;
0030: import com.sun.midp.main.Configuration;
0031: import com.sun.midp.io.*;
0032:
0033: import javax.microedition.io.file.*;
0034:
0035: import java.io.IOException;
0036: import java.io.InterruptedIOException;
0037: import java.io.UnsupportedEncodingException;
0038: import java.io.OutputStream;
0039: import java.io.InputStream;
0040: import java.io.DataOutputStream;
0041: import java.io.DataInputStream;
0042: import java.util.Enumeration;
0043: import javax.microedition.io.*;
0044: import com.sun.midp.security.*;
0045:
0046: import java.util.Vector;
0047:
0048: import com.sun.midp.midlet.MIDletSuite;
0049: import com.sun.midp.midlet.Scheduler;
0050:
0051: /**
0052: * This class implements the necessary functionality
0053: * for a File connection.
0054: */
0055: public class Protocol extends ConnectionBaseAdapter implements
0056: FileConnection {
0057:
0058: /** Security token for using FileConnection API from PIM */
0059: private SecurityToken classSecurityToken;
0060:
0061: /** Stores file connection mode */
0062: private int mode;
0063:
0064: /** File name string */
0065: private String fileName;
0066:
0067: /** File path string including root filesystem */
0068: private String filePath;
0069:
0070: /** Root filesystem for the file */
0071: private String fileRoot;
0072:
0073: /** File original URL */
0074: private String fileURL;
0075:
0076: /** A peer to the native file */
0077: private BaseFileHandler fileHandler;
0078:
0079: /** Indicates if there is a need to try to load alternative file handler */
0080: private static boolean hasOtherFileHandler = true;
0081:
0082: /** Input stream associated with this connection */
0083: InputStream fis;
0084:
0085: /** Output stream associated with this connection */
0086: OutputStream fos;
0087:
0088: /** Separator for file path components */
0089: private static String sep;
0090:
0091: /** Static initialization of file separator */
0092: static {
0093: char[] t = new char[1];
0094: t[0] = DefaultFileHandler.getFileSeparator();
0095: sep = new String(t);
0096: if (sep == null) {
0097: throw new NullPointerException(
0098: "Undefined \"file.separator\" property");
0099: }
0100: }
0101:
0102: /**
0103: * Constructor for file connection implementation.
0104: */
0105: public Protocol() {
0106: connectionOpen = false;
0107: fileHandler = null;
0108: }
0109:
0110: /**
0111: * Opens the file connection.
0112: * @param name URL path fragment
0113: * @param mode access mode
0114: * @param timeouts flag to indicate that timeouts allowed
0115: * @return an opened Connection
0116: * @throws IOException if some other kind of I/O error occurs.
0117: */
0118: public Connection openPrim(String name, int mode, boolean timeouts)
0119: throws IOException {
0120: return openPrimImpl(name, mode, timeouts, true);
0121: }
0122:
0123: /**
0124: * Opens the file connection and receive security token.
0125: * @param token security token from PIM
0126: * @param name URL path fragment
0127: * @return an opened Connection
0128: * @throws IOException if some other kind of I/O error occurs.
0129: */
0130: public Connection openPrim(SecurityToken token, String name)
0131: throws IOException {
0132: return openPrim(token, name, Connector.READ_WRITE);
0133: }
0134:
0135: /**
0136: * Opens the file connection and receive security token.
0137: * @param token security token from PIM
0138: * @param name URL path fragment
0139: * @param mode access mode
0140: * @return an opened Connection
0141: * @throws IOException if some other kind of I/O error occurs.
0142: */
0143: public Connection openPrim(SecurityToken token, String name,
0144: int mode) throws IOException {
0145: classSecurityToken = token;
0146: return openPrim(name, mode, false);
0147: }
0148:
0149: // JAVADOC COMMENT ELIDED
0150: public boolean isOpen() {
0151: return connectionOpen;
0152: }
0153:
0154: /**
0155: * Open and return an input stream for a connection. The connection's
0156: * target must already exist and be accessible for the input stream to be
0157: * created.
0158: *
0159: * @return An open input stream
0160: * @exception IOException if an I/O error occurs, if the method is invoked
0161: * on a directory, if the connection's target does not
0162: * yet exist, or the connection's target is not accessible.
0163: * @exception IllegalModeException if the application does have read
0164: * access
0165: * to the connection's target but has opened the connection in
0166: * <code>Connector.WRITE</code> mode.
0167: * @exception SecurityException If the application is not granted read
0168: * access to the connection's target.
0169: *
0170: */
0171: public InputStream openInputStream() throws IOException {
0172:
0173: checkReadPermission();
0174:
0175: try {
0176: ensureOpenAndConnected();
0177: } catch (ConnectionClosedException e) {
0178: throw new IOException(e.getMessage());
0179: }
0180:
0181: // IOException when target file doesn't exist
0182: if (!fileHandler.exists()) {
0183: throw new IOException("Target file doesn't exist");
0184: }
0185:
0186: if (!fileHandler.canRead()) { // no read access
0187: throw new SecurityException("No read access");
0188: }
0189:
0190: fileHandler.openForRead();
0191:
0192: fis = super .openInputStream();
0193:
0194: return fis;
0195: }
0196:
0197: /**
0198: * Open and return an output stream for a connection. The output stream
0199: * is positioned at the start of the file. Writing data to the output
0200: * stream overwrites the contents of the files (i.e. does not insert data).
0201: * Writing data to output streams beyond the current end of file
0202: * automatically extends the file size. The connection's target must
0203: * already exist and be accessible for the output stream to be created.
0204: * {@link #openOutputStream(long)} should be used to position an output
0205: * stream to a different position in the file.
0206: * <P>
0207: * Changes made to a file through an output stream may not be immediately
0208: * made to the actual file residing on the file system because
0209: * platform and implementation specific use of caching and buffering of the
0210: * data. Stream contents and file length extensions are not necessarily
0211: * visible outside of the application immediately unless
0212: * <code>flush()</code> is called on the stream.? The returned output
0213: * stream is automatically and synchronously flushed when it is closed.
0214: * </P>
0215: *
0216: * @return An open output stream
0217: * @exception IOException If an I/O error occurs, if the method is
0218: * invoked on
0219: * a directory, the file does not yet exist, or the connection's
0220: * target is not accessible.
0221: * @exception IllegalModeException if the application does have write
0222: * access
0223: * to the connection's target but has opened the connection in
0224: * <code>Connector.READ</code> mode.
0225: * @exception SecurityException If the application is not granted write
0226: * access to the connection's target.
0227: * @see #openOutputStream(long)
0228: *
0229: */
0230: public OutputStream openOutputStream() throws IOException {
0231: return openOutputStream(0);
0232: }
0233:
0234: // JAVADOC COMMENT ELIDED
0235: public OutputStream openOutputStream(long byteOffset)
0236: throws IOException {
0237: if (byteOffset < 0) {
0238: throw new IllegalArgumentException(
0239: "Offset has a negative value");
0240: }
0241:
0242: checkWritePermission();
0243:
0244: try {
0245: ensureOpenAndConnected();
0246: } catch (ConnectionClosedException e) {
0247: throw new IOException(e.getMessage());
0248: }
0249:
0250: // IOException when target file doesn't exist
0251: if (!fileHandler.exists()) {
0252: throw new IOException("Target file doesn't exist");
0253: }
0254:
0255: if (!fileHandler.canWrite()) {
0256: // no write access
0257: throw new SecurityException("No write access");
0258: }
0259:
0260: fileHandler.openForWrite();
0261: fileHandler.positionForWrite(byteOffset);
0262:
0263: fos = super .openOutputStream();
0264:
0265: return fos;
0266: }
0267:
0268: // JAVADOC COMMENT ELIDED
0269: public long totalSize() {
0270: long size = -1;
0271:
0272: try {
0273: checkRootReadPermission();
0274:
0275: ensureOpenAndConnected();
0276:
0277: size = fileHandler.totalSize();
0278: } catch (IOException e) {
0279: size = -1;
0280: }
0281:
0282: return size;
0283: }
0284:
0285: // JAVADOC COMMENT ELIDED
0286: public long availableSize() {
0287: long size = -1;
0288:
0289: try {
0290: checkRootReadPermission();
0291:
0292: ensureOpenAndConnected();
0293:
0294: size = fileHandler.availableSize();
0295: } catch (IOException e) {
0296: size = -1;
0297: }
0298:
0299: return size;
0300: }
0301:
0302: // JAVADOC COMMENT ELIDED
0303: public long usedSize() {
0304: long size = -1;
0305:
0306: try {
0307: checkRootReadPermission();
0308:
0309: ensureOpenAndConnected();
0310:
0311: size = fileHandler.usedSize();
0312: } catch (IOException e) {
0313: size = -1;
0314: }
0315:
0316: return size;
0317: }
0318:
0319: // JAVADOC COMMENT ELIDED
0320: public long directorySize(boolean includeSubDirs)
0321: throws IOException {
0322: long size = 0;
0323:
0324: // Permissions and ensureOpenAndConnected called by exists()
0325: if (exists()) {
0326: if (!isDirectory()) {
0327: throw new IOException(
0328: "directorySize is not invoked on directory");
0329: }
0330: } else {
0331: return -1L;
0332: }
0333:
0334: try {
0335: size = fileHandler.directorySize(includeSubDirs);
0336: } catch (IOException e) {
0337: size = -1;
0338: }
0339:
0340: return size;
0341: }
0342:
0343: // JAVADOC COMMENT ELIDED
0344: public long fileSize() throws IOException {
0345: long size = -1;
0346:
0347: checkReadPermission();
0348:
0349: if (isDirectory()) {
0350: throw new IOException("fileSize invoked on a directory");
0351: }
0352:
0353: try {
0354: ensureOpenAndConnected();
0355:
0356: size = fileHandler.fileSize();
0357: } catch (IOException e) {
0358: size = -1;
0359: }
0360:
0361: return size;
0362: }
0363:
0364: // JAVADOC COMMENT ELIDED
0365: public boolean canRead() {
0366: boolean res = false;
0367:
0368: try {
0369: checkReadPermission();
0370:
0371: ensureOpenAndConnected();
0372:
0373: res = fileHandler.canRead();
0374: } catch (IOException e) {
0375: res = false;
0376: }
0377:
0378: return res;
0379: }
0380:
0381: // JAVADOC COMMENT ELIDED
0382: public boolean canWrite() {
0383: boolean res = false;
0384:
0385: try {
0386: checkReadPermission();
0387:
0388: ensureOpenAndConnected();
0389:
0390: res = fileHandler.canWrite();
0391: } catch (IOException e) {
0392: res = false;
0393: }
0394:
0395: return res;
0396: }
0397:
0398: // JAVADOC COMMENT ELIDED
0399: public boolean isHidden() {
0400: boolean res = false;
0401:
0402: try {
0403: checkReadPermission();
0404:
0405: ensureOpenAndConnected();
0406:
0407: res = fileHandler.isHidden();
0408: } catch (IOException e) {
0409: res = false;
0410: }
0411:
0412: return res;
0413: }
0414:
0415: // JAVADOC COMMENT ELIDED
0416: public void setReadable(boolean readable) throws IOException {
0417: checkWritePermission();
0418:
0419: ensureOpenAndConnected();
0420:
0421: fileHandler.setReadable(readable);
0422: }
0423:
0424: // JAVADOC COMMENT ELIDED
0425: public void setWritable(boolean writable) throws IOException {
0426: checkWritePermission();
0427:
0428: ensureOpenAndConnected();
0429:
0430: fileHandler.setWritable(writable);
0431: }
0432:
0433: // JAVADOC COMMENT ELIDED
0434: public void setHidden(boolean hidden) throws IOException {
0435: checkWritePermission();
0436:
0437: ensureOpenAndConnected();
0438:
0439: fileHandler.setHidden(hidden);
0440: }
0441:
0442: // JAVADOC COMMENT ELIDED
0443: public Enumeration list() throws IOException {
0444: return listInternal(null, false);
0445: }
0446:
0447: // JAVADOC COMMENT ELIDED
0448: public Enumeration list(String filter, boolean includeHidden)
0449: throws IOException {
0450:
0451: if (filter == null) {
0452: throw new NullPointerException("List filter is null");
0453: }
0454:
0455: return listInternal(EscapedUtil.getUnescapedString(filter),
0456: includeHidden);
0457: }
0458:
0459: // JAVADOC COMMENT ELIDED
0460: public void create() throws IOException {
0461: checkWritePermission();
0462:
0463: ensureOpenAndConnected();
0464:
0465: if (fileName.charAt(fileName.length() - 1) == '/') {
0466: throw new IOException("Can not create directory");
0467: }
0468:
0469: fileHandler.create();
0470: }
0471:
0472: // JAVADOC COMMENT ELIDED
0473: public void mkdir() throws IOException {
0474: checkWritePermission();
0475:
0476: ensureOpenAndConnected();
0477:
0478: fileHandler.mkdir();
0479: }
0480:
0481: // JAVADOC COMMENT ELIDED
0482: public boolean exists() {
0483: boolean res = false;
0484:
0485: try {
0486: checkReadPermission();
0487:
0488: ensureOpenAndConnected();
0489:
0490: res = fileHandler.exists();
0491: } catch (IOException e) {
0492: res = false;
0493: }
0494:
0495: return res;
0496: }
0497:
0498: // JAVADOC COMMENT ELIDED
0499: public boolean isDirectory() {
0500: boolean res = false;
0501:
0502: try {
0503: checkReadPermission();
0504:
0505: ensureOpenAndConnected();
0506:
0507: res = fileHandler.isDirectory();
0508: } catch (IOException e) {
0509: res = false;
0510: }
0511:
0512: return res;
0513: }
0514:
0515: // JAVADOC COMMENT ELIDED
0516: public void delete() throws java.io.IOException {
0517: checkWritePermission();
0518:
0519: ensureOpenAndConnected();
0520:
0521: try {
0522: if (fis != null) {
0523: fis.close();
0524: fis = null;
0525: }
0526: } catch (IOException e) {
0527: // Ignore silently
0528: }
0529:
0530: try {
0531: if (fos != null) {
0532: fos.close();
0533: fos = null;
0534: }
0535: } catch (IOException e) {
0536: // Ignore silently
0537: }
0538:
0539: try {
0540: fileHandler.closeForReadWrite();
0541: } catch (IOException e) {
0542: // Ignore silently
0543: }
0544:
0545: fileHandler.delete();
0546: }
0547:
0548: // JAVADOC COMMENT ELIDED
0549: public void rename(String newName) throws IOException {
0550: checkWritePermission();
0551:
0552: newName = EscapedUtil.getUnescapedString(newName);
0553: // Following line will throw NullPointerException if newName is null
0554: int dirindex = newName.indexOf('/');
0555: if (dirindex != -1 && dirindex != (newName.length() - 1)) {
0556: throw new IllegalArgumentException(
0557: "New name contains path specification");
0558: }
0559:
0560: if (!"/".equals(sep) && newName.indexOf(sep) != -1) {
0561: throw new IllegalArgumentException(
0562: "New name contains path specification");
0563: }
0564:
0565: ensureOpenAndConnected();
0566: checkIllegalChars(newName);
0567:
0568: try {
0569: if (fis != null) {
0570: fis.close();
0571: fis = null;
0572: }
0573: } catch (IOException e) {
0574: // Ignore silently
0575: }
0576:
0577: try {
0578: if (fos != null) {
0579: fos.close();
0580: fos = null;
0581: }
0582: } catch (IOException e) {
0583: // Ignore silently
0584: }
0585:
0586: try {
0587: fileHandler.closeForReadWrite();
0588: } catch (IOException e) {
0589: // Ignore silently
0590: }
0591:
0592: fileHandler.rename(filePath + newName);
0593:
0594: fileName = newName;
0595: fileURL = "file://" + filePath + fileName;
0596: }
0597:
0598: // JAVADOC COMMENT ELIDED
0599: public void truncate(long byteOffset) throws IOException {
0600: checkWritePermission();
0601:
0602: ensureOpenAndConnected();
0603:
0604: if (byteOffset < 0) {
0605: throw new IllegalArgumentException("offset is negative");
0606: }
0607:
0608: try {
0609: if (fos != null) {
0610: fos.flush();
0611: }
0612: } catch (IOException e) {
0613: // Ignore silently
0614: }
0615:
0616: fileHandler.truncate(byteOffset);
0617: }
0618:
0619: // JAVADOC COMMENT ELIDED
0620: public void setFileConnection(String fileName) throws IOException {
0621: ensureOpenAndConnected();
0622:
0623: // Note: permissions are checked by openPrim method
0624:
0625: // Following line will throw NullPointerException if fileName is null
0626: int dirindex = fileName.indexOf('/');
0627: if (dirindex != -1 && dirindex != (fileName.length() - 1)) {
0628: throw new IllegalArgumentException(
0629: "Contains any path specification");
0630: }
0631:
0632: if (fileName.equals("..") && this .fileName.length() == 0) {
0633: throw new IOException(
0634: "Cannot set FileConnection to '..' from a file system root");
0635: }
0636:
0637: if (!"/".equals(sep) && fileName.indexOf(sep) != -1) {
0638: throw new IllegalArgumentException(
0639: "Contains any path specification");
0640: }
0641:
0642: checkIllegalChars(fileName);
0643:
0644: // According to the spec, the current FileConnection object must refer
0645: // to a directory.
0646: // Check this right here in order to avoid IllegalModeException instead
0647: // of IOException.
0648: if (!fileHandler.isDirectory()) {
0649: throw new IOException("Not a directory");
0650: }
0651:
0652: String origPath = filePath, origName = this .fileName;
0653:
0654: String tmp_sep;
0655: // Note: security checks are performed before any object state changes
0656: if (fileName.equals("..")) {
0657: // go one directory up
0658: openPrim("//" + filePath, mode, false);
0659: } else {
0660: int fileNameLen = this .fileName.length();
0661: if (fileNameLen == 0
0662: || this .fileName.charAt(fileNameLen - 1) == '/') {
0663: tmp_sep = "";
0664: } else {
0665: tmp_sep = "/";
0666: }
0667: // go deeper in directory structure
0668: openPrimImpl("//" + filePath + this .fileName + tmp_sep
0669: + fileName, mode, false, false);
0670: }
0671:
0672: // Old file connection must be a directory. It can not have open
0673: // streams so no need to close it. Just reset it to null
0674: fileHandler = null;
0675:
0676: // Reconnect to the new target
0677: ensureOpenAndConnected();
0678:
0679: // At this point we are already refer to the new file
0680: if (!fileHandler.exists()) {
0681: // Revert to an old file
0682: openPrim("//" + origPath + origName, mode, false);
0683: fileHandler = null;
0684:
0685: throw new IllegalArgumentException(
0686: "New target does not exists");
0687: }
0688: }
0689:
0690: /**
0691: * Spec is not consistent: sometimes it requires IOException
0692: * and sometimes IllegalArgumentException in case of illegal chars
0693: * in the filename
0694: * @param name URL path fragment
0695: * @throws IOException if name contains unsupported characters
0696: */
0697: private void checkIllegalChars(String name) throws IOException {
0698:
0699: String illegalChars = fileHandler.illegalFileNameChars();
0700: for (int i = 0; i < illegalChars.length(); i++) {
0701: if (name.indexOf(illegalChars.charAt(i)) != -1) {
0702: throw new IOException(
0703: "Contains characters invalid for a filename");
0704: }
0705: }
0706: }
0707:
0708: // JAVADOC COMMENT ELIDED
0709: public String getName() {
0710: String name = fileName;
0711:
0712: try {
0713: if (exists()) {
0714: int lastPos = name.length() - 1;
0715: if (isDirectory()) {
0716: if (!name.equals("") && name.charAt(lastPos) != '/')
0717: name += '/';
0718: } else {
0719: if (name.charAt(lastPos) == '/')
0720: name = name.substring(0, lastPos);
0721: }
0722: }
0723: } catch (SecurityException e) {
0724: // According to spec should silently ignore any exceptions
0725: } catch (IllegalModeException e) {
0726: // According to spec should silently ignore any exceptions
0727: } catch (ConnectionClosedException e) {
0728: // According to spec should silently ignore any exceptions
0729: }
0730:
0731: return name;
0732: }
0733:
0734: // JAVADOC COMMENT ELIDED
0735: public String getPath() {
0736: return filePath;
0737: }
0738:
0739: // JAVADOC COMMENT ELIDED
0740: public String getURL() {
0741: String url = EscapedUtil.getEscapedString(fileURL);
0742:
0743: try {
0744: if (exists()) {
0745: int lastPos = url.length() - 1;
0746: if (isDirectory()) {
0747: if (url.charAt(lastPos) != '/') {
0748: url += '/';
0749: }
0750: } else {
0751: if (url.charAt(lastPos) == '/') {
0752: url = url.substring(0, lastPos);
0753: }
0754: }
0755: }
0756: } catch (SecurityException e) {
0757: // According to spec should silently ignore any exceptions
0758: } catch (IllegalModeException e) {
0759: // According to spec should silently ignore any exceptions
0760: } catch (ConnectionClosedException e) {
0761: // According to spec should silently ignore any exceptions
0762: }
0763:
0764: return url;
0765: }
0766:
0767: // JAVADOC COMMENT ELIDED
0768: public long lastModified() {
0769: long res = 0;
0770:
0771: try {
0772: checkReadPermission();
0773:
0774: ensureOpenAndConnected();
0775:
0776: res = fileHandler.lastModified();
0777: } catch (IOException e) {
0778: res = 0;
0779: }
0780:
0781: return res;
0782: }
0783:
0784: /**
0785: * Reads up to <code>len</code> bytes of data from the input stream into
0786: * an array of bytes, blocks until at least one byte is available.
0787: *
0788: * @param b the buffer into which the data is read.
0789: * @param off the start offset in array <code>b</code>
0790: * at which the data is written.
0791: * @param len the maximum number of bytes to read.
0792: * @return the total number of bytes read into the buffer, or
0793: * <code>-1</code> if there is no more data because the end of
0794: * the stream has been reached.
0795: * @exception IOException if an I/O error occurs.
0796: */
0797: protected int readBytes(byte b[], int off, int len)
0798: throws IOException {
0799:
0800: checkReadPermission();
0801:
0802: ensureConnected();
0803:
0804: int readBytes = fileHandler.read(b, off, len);
0805: // return '-1' instead of '0' as stream specification requires
0806: // in case the end of the stream has been reached
0807: return (readBytes > 0) ? readBytes : -1;
0808: }
0809:
0810: /**
0811: * Returns the number of bytes that can be read (or skipped over) from
0812: * this input stream without blocking by the next caller of a method for
0813: * this input stream. The next caller might be the same thread or
0814: * another thread. This classes implementation always returns
0815: * <code>0</code>. It is up to subclasses to override this method.
0816: *
0817: * @return the number of bytes that can be read from this input stream
0818: * without blocking.
0819: * @exception IOException if an I/O error occurs.
0820: *
0821: * public int available() throws IOException {
0822: * return 0;
0823: *}
0824: */
0825: /**
0826: * Writes <code>len</code> bytes from the specified byte array
0827: * starting at offset <code>off</code> to this output stream.
0828: * <p>
0829: * Polling the native code is done here to allow for simple
0830: * asynchronous native code to be written. Not all implementations
0831: * work this way (they block in the native code) but the same
0832: * Java code works for both.
0833: *
0834: * @param b the data.
0835: * @param off the start offset in the data.
0836: * @param len the number of bytes to write.
0837: * @return number of bytes written
0838: * @exception IOException if an I/O error occurs. In particular,
0839: * an <code>IOException</code> is thrown if the output
0840: * stream is closed.
0841: */
0842: protected int writeBytes(byte b[], int off, int len)
0843: throws IOException {
0844: checkWritePermission();
0845:
0846: ensureConnected();
0847:
0848: return fileHandler.write(b, off, len);
0849: }
0850:
0851: /**
0852: * Forces any buffered output bytes to be written out.
0853: * The general contract of <code>flush</code> is
0854: * that calling it is an indication that, if any bytes previously
0855: * written that have been buffered by the connection,
0856: * should immediately be written to their intended destination.
0857: * <p>
0858: * The <code>flush</code> method of <code>ConnectionBaseAdapter</code>
0859: * does nothing.
0860: *
0861: * @exception IOException if an I/O error occurs.
0862: */
0863: protected void flush() throws IOException {
0864: checkWritePermission();
0865:
0866: ensureConnected();
0867:
0868: fileHandler.flush();
0869: }
0870:
0871: /**
0872: * Called once by each child input stream.
0873: * If the input stream is marked open, it will be marked closed and
0874: * the if the connection and output stream are closed the disconnect
0875: * method will be called.
0876: *
0877: * @exception IOException if the subclass throws one
0878: */
0879: protected void closeInputStream() throws IOException {
0880: maxIStreams++;
0881: fileHandler.closeForRead();
0882: super .closeInputStream();
0883: }
0884:
0885: /**
0886: * Called once by each child output stream.
0887: * If the output stream is marked open, it will be marked closed and
0888: * the if the connection and input stream are closed the disconnect
0889: * method will be called.
0890: *
0891: * @exception IOException if the subclass throws one
0892: */
0893: protected void closeOutputStream() throws IOException {
0894: maxOStreams++;
0895: flush();
0896: fileHandler.closeForWrite();
0897: super .closeOutputStream();
0898: }
0899:
0900: /**
0901: * Free up the connection resources.
0902: *
0903: * @exception IOException if an I/O error occurs.
0904: */
0905: protected void disconnect() throws IOException {
0906: try {
0907: if (fileHandler != null) {
0908: fileHandler.close();
0909: }
0910: } finally {
0911: fileHandler = null;
0912: }
0913: }
0914:
0915: // In order to compile against MIDP's ConnectionBaseAdapter
0916: /**
0917: * Establishes the connection.
0918: * @param name URL path fragment
0919: * @param mode access mode
0920: * @param timeouts flag to indicate that timeouts allowed
0921: * @throws IOException if an error occurs
0922: */
0923: protected void connect(String name, int mode, boolean timeouts)
0924: throws IOException {
0925: }
0926:
0927: /**
0928: * Checks that the connection is already open.
0929: * @throws IOException if the connection is closed
0930: */
0931: protected void ensureConnected() throws IOException {
0932: if (!isRoot(fileRoot)) {
0933: throw new IOException("Root is not accessible");
0934: }
0935:
0936: if (fileHandler == null) {
0937: fileHandler = getFileHandler();
0938:
0939: fileHandler.connect(fileRoot, filePath + fileName);
0940:
0941: fileHandler.createPrivateDir(fileRoot);
0942: }
0943: }
0944:
0945: /**
0946: * Opens the file connection.
0947: * @param name URL path fragment
0948: * @param mode access mode
0949: * @param timeouts flag to indicate that timeouts allowed
0950: * @param unescape flag to indicate whether URL must be unescaped
0951: * @return an opened Connection
0952: * @throws IOException if some other kind of I/O error occurs.
0953: */
0954: private Connection openPrimImpl(String name, int mode,
0955: boolean timeouts, boolean unescape) throws IOException {
0956:
0957: if (!name.startsWith("//")) {
0958: throw new IllegalArgumentException(
0959: "Missing protocol separator");
0960: }
0961:
0962: int rootStart = name.indexOf('/', 2);
0963:
0964: if (rootStart == -1) {
0965: throw new IllegalArgumentException("Malformed File URL");
0966: }
0967:
0968: /* The string must be a valid URL path separated by "/" */
0969:
0970: if (name.indexOf("/../", rootStart) != -1
0971: || name.indexOf("/./", rootStart) != -1
0972: || name.endsWith("/..") || name.endsWith("/.")
0973: || !"/".equals(sep)
0974: && name.indexOf(sep, rootStart) != -1
0975: || name.indexOf('\\') != -1) {
0976: throw new IllegalArgumentException(
0977: "/. or /.. is not supported "
0978: + "or other illegal characters found");
0979: }
0980:
0981: if (unescape) {
0982: name = EscapedUtil.getUnescapedString(name);
0983: }
0984: String fileURL = "file:" + name;
0985:
0986: // Perform security checks before any object state changes since
0987: // this method is used not only by Connector.open() but
0988: // by FileConnection.setFileConnection() too.
0989: switch (mode) {
0990: case Connector.READ:
0991: checkReadPermission(fileURL, mode);
0992: maxOStreams = 0;
0993: break;
0994: case Connector.WRITE:
0995: checkWritePermission(fileURL, mode);
0996: maxIStreams = 0;
0997: break;
0998: case Connector.READ_WRITE:
0999: checkReadPermission(fileURL, mode);
1000: checkWritePermission(fileURL, mode);
1001: break;
1002: default:
1003: throw new IllegalArgumentException("Invalid mode");
1004: }
1005:
1006: this .fileURL = fileURL;
1007: this .mode = mode;
1008:
1009: int nameLength = name.length();
1010: int pathStart = name.indexOf('/', rootStart + 1);
1011:
1012: if (pathStart == -1) {
1013: throw new IllegalArgumentException("Root is not specified");
1014: }
1015:
1016: if (pathStart == (nameLength - 1)) {
1017: fileName = "";
1018: fileRoot = name.substring(rootStart + 1);
1019: filePath = name.substring(rootStart);
1020: } else {
1021: fileRoot = name.substring(rootStart + 1, pathStart + 1);
1022:
1023: int fileStart = name.lastIndexOf('/', nameLength - 2);
1024:
1025: if (fileStart <= pathStart) {
1026: fileName = name.substring(pathStart + 1);
1027: filePath = name.substring(rootStart, pathStart + 1);
1028: } else {
1029: filePath = name.substring(rootStart, fileStart + 1);
1030: fileName = name.substring(fileStart + 1);
1031: }
1032: }
1033:
1034: connectionOpen = true;
1035: return this ;
1036: }
1037:
1038: /**
1039: * Checks if path is a root path.
1040: * @param root path to be checked
1041: * @return <code>true</code> if path is a root,
1042: * <code>false</code> otherwise.
1043: */
1044: private boolean isRoot(String root) {
1045: Vector r = listRoots(); // retrieve up-to-date list of mounted roots
1046: for (int i = 0; i < r.size(); i++) {
1047: String name = (String) r.elementAt(i);
1048: if (name.equals(root)) {
1049: return true;
1050: }
1051: }
1052: return false;
1053: }
1054:
1055: /**
1056: * Checks that the connection is already open and connected.
1057: * @throws ConnectionClosedException if the connection is closed
1058: * @throws IOException if any error occurs while connecting
1059: */
1060: protected void ensureOpenAndConnected() throws IOException {
1061: if (!isOpen()) {
1062: throw new ConnectionClosedException("Connection is closed");
1063: }
1064:
1065: ensureConnected();
1066: }
1067:
1068: /**
1069: * Checks that the application has permission to read.
1070: * @param fileURL complete file URL
1071: * @param mode access mode
1072: * @throws InterruptedIOException if the permission dialog is
1073: * terminated before completed
1074: * @throws SecurityException if read is not allowed
1075: * @throws IllegalModeException if connection is write only
1076: */
1077: private final void checkReadPermission(String fileURL, int mode)
1078: throws InterruptedIOException {
1079:
1080: if (classSecurityToken == null) { // FC permission
1081: MIDletSuite suite = Scheduler.getScheduler()
1082: .getMIDletSuite();
1083:
1084: try {
1085: suite.checkForPermission(
1086: Permissions.FILE_CONNECTION_READ, fileURL);
1087: } catch (InterruptedException ie) {
1088: throw new InterruptedIOException(
1089: "Interrupted while trying to ask the user permission");
1090: }
1091: } else { // call from PIM
1092: classSecurityToken
1093: .checkIfPermissionAllowed(Permissions.FILE_CONNECTION_READ);
1094: }
1095:
1096: if (mode == Connector.WRITE) {
1097: throw new IllegalModeException("Connection is write only");
1098: }
1099: }
1100:
1101: /**
1102: * Checks that the application has permission to read.
1103: * @throws InterruptedIOException if the permission dialog is
1104: * terminated before completed
1105: * @throws SecurityException if read is not allowed
1106: * @throws IllegalModeException if connection is write only
1107: */
1108: protected final void checkReadPermission()
1109: throws InterruptedIOException {
1110: checkReadPermission(fileURL, mode);
1111: }
1112:
1113: /**
1114: * Checks that the application has permission to read the root path.
1115: * @throws InterruptedIOException if the permission dialog is
1116: * terminated before completed
1117: * @throws SecurityException if read is not allowed
1118: * @throws IllegalModeException if connection is write only
1119: */
1120: protected final void checkRootReadPermission()
1121: throws InterruptedIOException {
1122:
1123: if (classSecurityToken == null) { // FC permission
1124: MIDletSuite suite = Scheduler.getScheduler()
1125: .getMIDletSuite();
1126:
1127: try {
1128: suite.checkForPermission(
1129: Permissions.FILE_CONNECTION_READ, "file://"
1130: + fileRoot);
1131: } catch (InterruptedException ie) {
1132: throw new InterruptedIOException(
1133: "Interrupted while trying to ask the user permission");
1134: }
1135: } else { // call from PIM
1136: classSecurityToken
1137: .checkIfPermissionAllowed(Permissions.FILE_CONNECTION_READ);
1138: }
1139:
1140: if (mode == Connector.WRITE) {
1141: throw new IllegalModeException("Connection is write only");
1142: }
1143: }
1144:
1145: /**
1146: * Checks that the application has permission to write.
1147: * @param fileURL complete file URL
1148: * @param mode access mode
1149: * @throws InterruptedIOException if the permission dialog is
1150: * terminated before completed
1151: * @throws SecurityException if write is not allowed
1152: * @throws IllegalModeException if connection is read only
1153: */
1154: private final void checkWritePermission(String fileURL, int mode)
1155: throws InterruptedIOException {
1156:
1157: if (classSecurityToken == null) { // FC permission
1158: MIDletSuite suite = Scheduler.getScheduler()
1159: .getMIDletSuite();
1160:
1161: try {
1162: suite.checkForPermission(
1163: Permissions.FILE_CONNECTION_WRITE, fileURL);
1164: } catch (InterruptedException ie) {
1165: throw new InterruptedIOException(
1166: "Interrupted while trying to ask the user permission");
1167: }
1168: } else { // call from PIM
1169: classSecurityToken
1170: .checkIfPermissionAllowed(Permissions.FILE_CONNECTION_WRITE);
1171: }
1172:
1173: if (mode == Connector.READ) {
1174: throw new IllegalModeException("Connection is read only");
1175: }
1176: }
1177:
1178: /**
1179: * Checks that the application has permission to write.
1180: * @throws InterruptedIOException if the permission dialog is
1181: * terminated before completed
1182: * @throws SecurityException if write is not allowed
1183: * @throws IllegalModeException if connection is read only
1184: */
1185: protected final void checkWritePermission()
1186: throws InterruptedIOException {
1187: checkWritePermission(fileURL, mode);
1188: }
1189:
1190: /**
1191: * Gets an array of file system roots.
1192: * @return up-to-date array of file system roots;
1193: * empty array is returned if there are no roots available.
1194: */
1195: public static Vector listRoots() {
1196: BaseFileHandler fh = getFileHandler();
1197: return fh.listRoots();
1198: }
1199:
1200: /**
1201: * Gets a filtered list of files and directories contained in a directory.
1202: * The directory is the connection's target as specified in
1203: * <code>Connector.open()</code>.
1204: *
1205: * @param filter String against which all files and directories are
1206: * matched for retrieval. An asterisk ("*") can be used as a
1207: * wildcard to represent 0 or more occurrences of any character.
1208: * @param includeHidden boolean indicating whether files marked as hidden
1209: * should be included or not in the list of files and directories
1210: * returned.
1211: * @return An Enumeration of strings, denoting the files and directories
1212: * in the directory matching the filter. Directories are denoted
1213: * with a trailing slash "/" in their returned name. The
1214: * Enumeration has zero length if the directory is empty or no
1215: * files and/or directories are found matching the given filter.
1216: * Any current directory indication (".") and any parent directory
1217: * indication ("..") is not included in the list of files and
1218: * directories returned.
1219: * @exception SecurityException if the security of the application does
1220: * not have read access for the directory.
1221: * @exception IllegalModeException if the application does have read
1222: * access
1223: * to the connection's target but has opened the connection in
1224: * <code>Connector.WRITE</code> mode.
1225: * @exception IOException if invoked on a file, the directory does not
1226: * exists, the directory is not accessible, or an I/O error occurs.
1227: * @exception ConnectionClosedException if the connection is closed.
1228: * @exception IllegalArgumentException if filter contains any path
1229: * specification or is an invalid filename for the platform
1230: * (e.g. contains characters invalid for a filename on the
1231: * platform).
1232: */
1233: private Enumeration listInternal(String filter,
1234: boolean includeHidden) throws IOException {
1235: checkReadPermission();
1236:
1237: ensureOpenAndConnected();
1238:
1239: if (filter != null) {
1240: if (filter.indexOf('/') != -1) {
1241: throw new IllegalArgumentException(
1242: "Filter contains any path specification");
1243: }
1244:
1245: String illegalChars = fileHandler.illegalFileNameChars();
1246: for (int i = 0; i < illegalChars.length(); i++) {
1247: if (filter.indexOf(illegalChars.charAt(i)) != -1) {
1248: throw new IllegalArgumentException(
1249: "Filter contains characters "
1250: + "invalid for a filename");
1251: }
1252: }
1253: }
1254:
1255: return fileHandler.list(filter, includeHidden).elements();
1256: }
1257:
1258: /**
1259: * Gets the file handler.
1260: * @return handle to current file connection
1261: */
1262: private static BaseFileHandler getFileHandler() {
1263: String def = "com.sun.midp.io.j2me.file.DefaultFileHandler";
1264: String n = null;
1265: if (hasOtherFileHandler) {
1266: n = Configuration
1267: .getProperty("com.sun.midp.io.j2me.fileHandlerImpl");
1268: if (n == null) {
1269: hasOtherFileHandler = false;
1270: }
1271: }
1272: if (hasOtherFileHandler) {
1273: try {
1274: return (BaseFileHandler) (Class.forName(n))
1275: .newInstance();
1276: } catch (ClassNotFoundException e) {
1277: hasOtherFileHandler = false;
1278: } catch (Error e) {
1279: hasOtherFileHandler = false;
1280: } catch (IllegalAccessException e) {
1281: hasOtherFileHandler = false;
1282: } catch (InstantiationException e) {
1283: hasOtherFileHandler = false;
1284: }
1285: }
1286: try {
1287: return (BaseFileHandler) (Class.forName(def)).newInstance();
1288: } catch (ClassNotFoundException e) {
1289: } catch (Error e) {
1290: } catch (IllegalAccessException e) {
1291: } catch (InstantiationException e) {
1292: }
1293: throw new Error("Unable to create FileConnection Handler");
1294: }
1295: }
1296:
1297: /**
1298: * Utility for escaped character handling.
1299: */
1300: class EscapedUtil {
1301: /**
1302: * Gets the escaped string.
1303: * @param name string to be processed
1304: * @return escaped string
1305: * @throws IllegalArgumentException if encoding not supported
1306: */
1307: public static String getEscapedString(String name) {
1308: try {
1309: if (name == null) {
1310: return null;
1311: }
1312: byte newName[] = new byte[name.length() * 12];
1313: int nextPlace = 0;
1314: for (int i = 0; i < name.length(); i++) {
1315: char c = name.charAt(i);
1316: if (containsReserved(c)) {
1317: char data[] = { c };
1318: byte[] reservedBytes = new String(data)
1319: .getBytes("utf-8");
1320: for (int j = 0; j < reservedBytes.length; j++) {
1321: newName[nextPlace++] = '%';
1322: byte upper = (byte) ((reservedBytes[j] >> 4) & 0xF);
1323: if (upper <= 9) {
1324: newName[nextPlace++] = (byte) ('0' + upper);
1325: } else {
1326: newName[nextPlace++] = (byte) ('A' + (upper - 10));
1327: }
1328: byte lower = (byte) (reservedBytes[j] & 0xF);
1329: if (lower <= 9) {
1330: newName[nextPlace++] = (byte) ('0' + lower);
1331: } else {
1332: newName[nextPlace++] = (byte) ('A' + (lower - 10));
1333: }
1334: }
1335: } else {
1336: newName[nextPlace++] = (byte) c;
1337: }
1338: }
1339: return new String(newName, 0, nextPlace);
1340: } catch (UnsupportedEncodingException uee) {
1341: throw new IllegalArgumentException(uee.getMessage());
1342: }
1343: }
1344:
1345: /**
1346: * Gets the unescaped string.
1347: * <pre>
1348: * escaped = "%" hex hex
1349: * hex = digit | "A" | "B" | "C" | "D" | "E" | "F" |
1350: * "a" | "b" | "c" | "d" | "e" | "f"
1351: * </pre>
1352: * @param name string to be processed
1353: * @return escaped string
1354: * @throws IllegalArgumentException if encoding not supported
1355: *
1356: */
1357: public static String getUnescapedString(String name) {
1358: try {
1359: if (name == null) {
1360: return null;
1361: }
1362: if (name.indexOf("%") == -1) {
1363: return name;
1364: } else {
1365: byte newName[] = new byte[name.length()];
1366: int nextPlace = 0;
1367: for (int i = 0; i < name.length(); i++) {
1368: char c = name.charAt(i);
1369: if (c == '%') {
1370: String hexNum = name.substring(i + 1, i + 3)
1371: .toUpperCase();
1372: if (isHexCharsLegal(hexNum)) {
1373: c = hexToChar(hexNum);
1374: i = i + 2;
1375: } else {
1376: throw new IllegalArgumentException(
1377: "Bad format");
1378: }
1379: } else if (containsReserved(c)) {
1380: throw new IllegalArgumentException(
1381: "Bad escaped format");
1382: }
1383: newName[nextPlace++] = (byte) c;
1384: }
1385: return new String(newName, 0, nextPlace, "UTF-8");
1386: }
1387: } catch (UnsupportedEncodingException uee) {
1388: throw new IllegalArgumentException(uee.getMessage());
1389: }
1390: }
1391:
1392: /**
1393: * Checks if the hexadecimal character is valid.
1394: * @param hexValue string to be checked
1395: * @return <code>true</code> if all characters are valid
1396: */
1397: private static boolean isHexCharsLegal(String hexValue) {
1398: if ((isDigit(hexValue.charAt(0)) || isABCDEF(hexValue.charAt(0)))
1399: && (isDigit(hexValue.charAt(1)) || isABCDEF(hexValue
1400: .charAt(1)))) {
1401: return true;
1402: } else {
1403: return false;
1404: }
1405:
1406: }
1407:
1408: /**
1409: * Converts one hexadecimal char.
1410: * @param hexValue string to be processed
1411: * @return normalized hex value
1412: */
1413: private static char hexToChar(String hexValue) {
1414: char c = 0;
1415: if (isDigit(hexValue.charAt(0))) {
1416: c += (hexValue.charAt(0) - '0') * 16;
1417: } else {
1418: c += (hexValue.charAt(0) - 'A' + 10) * 16;
1419: }
1420:
1421: if (isDigit(hexValue.charAt(1))) {
1422: c += (hexValue.charAt(01) - '0');
1423: } else {
1424: c += (hexValue.charAt(1) - 'A' + 10);
1425: }
1426: return c;
1427: }
1428:
1429: /**
1430: * Checks if character is decimal digit.
1431: * @param c character to check
1432: * @return <code>true</code> if in the range 0..9
1433: */
1434: private static boolean isDigit(char c) {
1435: return (c >= '0' && c <= '9');
1436: }
1437:
1438: /**
1439: * Checks if character is hexadecimal digit.
1440: * @param c character to check
1441: * @return <code>true</code> if in the range A..F
1442: */
1443: private static boolean isABCDEF(char c) {
1444: return (c >= 'A' && c <= 'F');
1445: }
1446:
1447: /**
1448: * Checks if character is from the reserved character set.
1449: * @param c character to check
1450: * @return <code>true</code> if not in the range A..Z,
1451: * a..z,..9, or punctuation (forward slash, colon, hyphen,
1452: * under score, period, exclamation, tilde, asterisk, single quote,
1453: * left paren or right paren).
1454: */
1455: private static boolean containsReserved(char c) {
1456: return !((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
1457: || (c >= '0' && c <= '9') || ("/:-_.!~*'()".indexOf(c) != -1));
1458: }
1459:
1460: } // End of EscapeUtil
|