/* * Copyright 1990-2009 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 only, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License version 2 for more details (a copy is * included at /legal/license.txt). * * You should have received a copy of the GNU General Public License * version 2 along with this work; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa * Clara, CA 95054 or visit www.sun.com if you need additional * information or have any questions. */ package com.sun.cdc.io.j2me.file; import com.sun.j2me.app.AppPackage; import com.sun.j2me.security.FileConnectionPermission; import com.sun.j2me.main.Configuration; import com.sun.j2me.io.ConnectionBaseAdapter; import com.sun.j2me.security.Token; import javax.microedition.io.file.*; import java.io.IOException; import java.io.InterruptedIOException; import java.io.UnsupportedEncodingException; import java.io.OutputStream; import java.io.InputStream; import java.util.Enumeration; import javax.microedition.io.*; import java.util.Vector; /** * This class implements the necessary functionality * for a File connection. */ public class Protocol extends ConnectionBaseAdapter implements FileConnection { /** Security token for using FileConnection API from PIM */ private Token classSecurityToken; /** Stores file connection mode */ private int mode; /** File name string */ private String fileName; /** File path string including root filesystem */ private String filePath; /** Root filesystem for the file */ private String fileRoot; /** File original URL */ private String fileURL; /** Native path/name of the file */ private String nativePathName; /** A peer to the native file */ private BaseFileHandler fileHandler; /** Indicates if there is a need to try to load alternative file handler */ private static boolean hasOtherFileHandler = true; /** Input stream associated with this connection */ InputStream fis; /** Output stream associated with this connection */ OutputStream fos; /** Separator for file path components */ private static String sep; /** Static initialization of file separator */ static { char[] t = new char[1]; t[0]= DefaultFileHandler.getFileSeparator(); sep = new String(t); if (sep == null) { throw new NullPointerException("Undefined \"file.separator\" property"); } } /** * Constructor for file connection implementation. */ public Protocol() { connectionOpen = false; fileHandler = null; } /** * Opens the file connection. * @param name URL path fragment * @param mode access mode * @param timeouts flag to indicate that timeouts allowed * @return an opened Connection * @throws IOException if some other kind of I/O error occurs. */ public Connection openPrim(String name, int mode, boolean timeouts) throws IOException { return openPrimImpl(name, mode, timeouts, true); } /** * Opens the file connection and receive security token. * @param token security token from PIM * @param name URL path fragment * @return an opened Connection * @throws IOException if some other kind of I/O error occurs. */ public Connection openPrim(Token token, String name) throws IOException { return openPrim(token, name, Connector.READ_WRITE); } /** * Opens the file connection and receive security token. * @param token security token from PIM * @param name URL path fragment * @param mode access mode * @return an opened Connection * @throws IOException if some other kind of I/O error occurs. */ public Connection openPrim(Token token, String name, int mode) throws IOException { classSecurityToken = token; return openPrim(name, mode, false); } // JAVADOC COMMENT ELIDED public boolean isOpen() { return connectionOpen; } // JAVADOC COMMENT ELIDED public InputStream openInputStream() throws IOException { inputStreamPermissionCheck(); checkReadMode(); try { ensureOpenAndConnected(); } catch (ConnectionClosedException e) { throw new IOException(e.getMessage()); } // IOException when target file doesn't exist if (!fileHandler.exists()) { throw new IOException("Target file doesn't exist"); } if (!fileHandler.canRead()) { // no read access throw new SecurityException("No read access"); } fileHandler.openForRead(); fis = super.openInputStream(); return fis; } // JAVADOC COMMENT ELIDED public OutputStream openOutputStream() throws IOException { return openOutputStream(0); } // JAVADOC COMMENT ELIDED public OutputStream openOutputStream(long byteOffset) throws IOException { if (byteOffset < 0) { throw new IllegalArgumentException("Offset has a negative value"); } outputStreamPermissionCheck(); checkWriteMode(); try { ensureOpenAndConnected(); } catch (ConnectionClosedException e) { throw new IOException(e.getMessage()); } // IOException when target file doesn't exist if (!fileHandler.exists()) { throw new IOException("Target file doesn't exist"); } if (!fileHandler.canWrite()) { // no write access throw new SecurityException("No write access"); } fileHandler.openForWrite(); fileHandler.positionForWrite(byteOffset); fos = super.openOutputStream(); return fos; } // JAVADOC COMMENT ELIDED public long totalSize() { long size = -1; try { checkReadMode(); ensureOpenAndConnected(); size = fileHandler.totalSize(); } catch (IOException e) { size = -1; } return size; } // JAVADOC COMMENT ELIDED public long availableSize() { long size = -1; try { checkReadMode(); ensureOpenAndConnected(); size = fileHandler.availableSize(); } catch (IOException e) { size = -1; } return size; } // JAVADOC COMMENT ELIDED public long usedSize() { long size = -1; try { checkReadMode(); ensureOpenAndConnected(); size = fileHandler.usedSize(); } catch (IOException e) { size = -1; } return size; } // JAVADOC COMMENT ELIDED public long directorySize(boolean includeSubDirs) throws IOException { long size = 0; // Permissions and ensureOpenAndConnected called by exists() if (exists()) { if (!isDirectory()) { throw new IOException("directorySize is not invoked on directory"); } } else { return -1L; } try { size = fileHandler.directorySize(includeSubDirs); } catch (IOException e) { size = -1; } return size; } // JAVADOC COMMENT ELIDED public long fileSize() throws IOException { long size = -1; checkReadMode(); if (isDirectory()) { throw new IOException("fileSize invoked on a directory"); } try { ensureOpenAndConnected(); size = fileHandler.fileSize(); } catch (IOException e) { size = -1; } return size; } // JAVADOC COMMENT ELIDED public boolean canRead() { boolean res = false; try { checkReadMode(); ensureOpenAndConnected(); res = fileHandler.canRead(); } catch (IOException e) { res = false; } return res; } // JAVADOC COMMENT ELIDED public boolean canWrite() { boolean res = false; try { checkReadMode(); ensureOpenAndConnected(); res = fileHandler.canWrite(); } catch (IOException e) { res = false; } return res; } // JAVADOC COMMENT ELIDED public boolean isHidden() { boolean res = false; try { checkReadMode(); ensureOpenAndConnected(); res = fileHandler.isHidden(); } catch (IOException e) { res = false; } return res; } // JAVADOC COMMENT ELIDED public void setReadable(boolean readable) throws IOException { checkWriteMode(); ensureOpenAndConnected(); fileHandler.setReadable(readable); } // JAVADOC COMMENT ELIDED public void setWritable(boolean writable) throws IOException { checkWriteMode(); ensureOpenAndConnected(); fileHandler.setWritable(writable); } // JAVADOC COMMENT ELIDED public void setHidden(boolean hidden) throws IOException { checkWriteMode(); ensureOpenAndConnected(); fileHandler.setHidden(hidden); } // JAVADOC COMMENT ELIDED public Enumeration list() throws IOException { return listInternal(null, false); } // JAVADOC COMMENT ELIDED public Enumeration list(String filter, boolean includeHidden) throws IOException { if (filter == null) { throw new NullPointerException("List filter is null"); } return listInternal(EscapedUtil.getUnescapedString(filter), includeHidden); } // JAVADOC COMMENT ELIDED public void create() throws IOException { checkWriteMode(); ensureOpenAndConnected(); if (fileName.charAt(fileName.length() - 1) == '/') { throw new IOException("Can not create directory"); } fileHandler.create(); } // JAVADOC COMMENT ELIDED public void mkdir() throws IOException { checkWriteMode(); ensureOpenAndConnected(); fileHandler.mkdir(); } // JAVADOC COMMENT ELIDED public boolean exists() { boolean res = false; try { checkReadMode(); ensureOpenAndConnected(); res = fileHandler.exists(); } catch (IOException e) { res = false; } return res; } // JAVADOC COMMENT ELIDED public boolean isDirectory() { boolean res = false; try { checkReadMode(); ensureOpenAndConnected(); res = fileHandler.isDirectory(); } catch (IOException e) { res = false; } return res; } // JAVADOC COMMENT ELIDED public void delete() throws java.io.IOException { checkWriteMode(); ensureOpenAndConnected(); try { if (fis != null) { fis.close(); fis = null; } } catch (IOException e) { // Ignore silently } try { if (fos != null) { fos.close(); fos = null; } } catch (IOException e) { // Ignore silently } try { fileHandler.closeForReadWrite(); } catch (IOException e) { // Ignore silently } fileHandler.delete(); } // JAVADOC COMMENT ELIDED public void rename(String newName) throws IOException { checkWriteMode(); newName = EscapedUtil.getUnescapedString(newName); // Following line will throw NullPointerException if newName is null int dirindex = newName.indexOf('/'); if (dirindex != -1 && dirindex != (newName.length() - 1)) { throw new IllegalArgumentException("New name contains path specification"); } if (!"/".equals(sep) && newName.indexOf(sep) != -1) { throw new IllegalArgumentException("New name contains path specification"); } ensureOpenAndConnected(); checkIllegalChars(newName); try { if (fis != null) { fis.close(); fis = null; } } catch (IOException e) { // Ignore silently } try { if (fos != null) { fos.close(); fos = null; } } catch (IOException e) { // Ignore silently } try { fileHandler.closeForReadWrite(); } catch (IOException e) { // Ignore silently } fileHandler.rename(filePath + newName); fileName = newName; fileURL = "file://" + filePath + fileName; } // JAVADOC COMMENT ELIDED public void truncate(long byteOffset) throws IOException { checkWriteMode(); ensureOpenAndConnected(); if (byteOffset < 0) { throw new IllegalArgumentException("offset is negative"); } try { if (fos != null) { fos.flush(); } } catch (IOException e) { // Ignore silently } fileHandler.truncate(byteOffset); } // JAVADOC COMMENT ELIDED public void setFileConnection(String fileName) throws IOException { ensureOpenAndConnected(); // Note: permissions are checked by openPrim method // Following line will throw NullPointerException if fileName is null int dirindex = fileName.indexOf('/'); if (dirindex != -1 && dirindex != (fileName.length() - 1)) { throw new IllegalArgumentException( "Contains any path specification"); } if (fileName.equals("..") && this.fileName.length() == 0) { throw new IOException( "Cannot set FileConnection to '..' from a file system root"); } if (!"/".equals(sep) && fileName.indexOf(sep) != -1) { throw new IllegalArgumentException("Contains any path specification"); } checkIllegalChars(fileName); // According to the spec, the current FileConnection object must refer // to a directory. // Check this right here in order to avoid IllegalModeException instead // of IOException. if (!fileHandler.isDirectory()) { throw new IOException("Not a directory"); } String origPath = filePath, origName = this.fileName; String tmp_sep; // Note: security checks are performed before any object state changes if (fileName.equals("..")) { // go one directory up openPrim("//" + filePath, mode, false); } else { int fileNameLen = this.fileName.length(); if (fileNameLen == 0 || this.fileName.charAt(fileNameLen - 1) == '/') { tmp_sep = ""; } else { tmp_sep = "/"; } // go deeper in directory structure openPrimImpl("//" + filePath + this.fileName + tmp_sep + fileName, mode, false, false); } // Old file connection must be a directory. It can not have open // streams so no need to close it. Just reset it to null fileHandler = null; // Reconnect to the new target ensureOpenAndConnected(); // At this point we are already refer to the new file if (!fileHandler.exists()) { // Revert to an old file openPrim("//" + origPath + origName, mode, false); fileHandler = null; throw new IllegalArgumentException("New target does not exists"); } } /** * Spec is not consistent: sometimes it requires IOException * and sometimes IllegalArgumentException in case of illegal chars * in the filename * @param name URL path fragment * @throws IOException if name contains unsupported characters */ private void checkIllegalChars(String name) throws IOException { String illegalChars = fileHandler.illegalFileNameChars(); for (int i = 0; i < illegalChars.length(); i++) { if (name.indexOf(illegalChars.charAt(i)) != -1) { throw new IOException("Contains characters invalid for a filename"); } } } // JAVADOC COMMENT ELIDED public String getName() { String name = fileName; try { if (exists()) { int lastPos = name.length() - 1; if (isDirectory()) { if (!name.equals("") && name.charAt(lastPos) != '/') name += '/'; } else { if (name.charAt(lastPos) == '/') name = name.substring(0, lastPos); } } } catch (SecurityException e) { // According to spec should silently ignore any exceptions } catch (IllegalModeException e) { // According to spec should silently ignore any exceptions } catch (ConnectionClosedException e) { // According to spec should silently ignore any exceptions } return name; } // JAVADOC COMMENT ELIDED public String getPath() { return filePath; } // JAVADOC COMMENT ELIDED public String getURL() { String url = EscapedUtil.getEscapedString(fileURL); try { if (exists()) { int lastPos = url.length() - 1; if (isDirectory()) { if (url.charAt(lastPos) != '/') { url += '/'; } } else { if (url.charAt(lastPos) == '/') { url = url.substring(0, lastPos); } } } } catch (SecurityException e) { // According to spec should silently ignore any exceptions } catch (IllegalModeException e) { // According to spec should silently ignore any exceptions } catch (ConnectionClosedException e) { // According to spec should silently ignore any exceptions } return url; } // JAVADOC COMMENT ELIDED public long lastModified() { long res = 0; try { checkReadMode(); ensureOpenAndConnected(); res = fileHandler.lastModified(); } catch (IOException e) { res = 0; } return res; } /** * Throws <code>SecurityException</code> if permission check fails. * * @param name permission name. * @param fileURL URL of the file that is going to be accessed. */ protected void checkPermission(String name, String fileURL) throws InterruptedIOException { try { AppPackage.getInstance(). checkForPermission(new FileConnectionPermission(name, fileURL)); } catch (InterruptedException ie) { throw new InterruptedIOException( "Interrupted while trying to ask the user permission"); } } /** * Throws <code>SecurityException</code> if <code>InputStream</code> * opening permission check fails. */ protected void inputStreamPermissionCheck() throws InterruptedIOException { checkReadPermission(); } /** * Throws <code>SecurityException</code> if <code>OutputStream</code> * opening permission check fails. */ protected void outputStreamPermissionCheck() throws InterruptedIOException { checkWritePermission(); } // JAVADOC COMMENT ELIDED // See javax.microedition.media.protocol.SourceStream.read() in JSR-135 protected int readBytes(byte b[], int off, int len) throws IOException { checkReadMode(); ensureConnected(); int readBytes = fileHandler.read(b, off, len); // return '-1' instead of '0' as stream specification requires // in case the end of the stream has been reached return (readBytes > 0) ? readBytes : -1; } /** * Writes <code>len</code> bytes from the specified byte array * starting at offset <code>off</code> to this output stream. * <p> * Polling the native code is done here to allow for simple * asynchronous native code to be written. Not all implementations * work this way (they block in the native code) but the same * Java code works for both. * * @param b the data. * @param off the start offset in the data. * @param len the number of bytes to write. * @return number of bytes written * @exception IOException if an I/O error occurs. In particular, * an <code>IOException</code> is thrown if the output * stream is closed. */ protected int writeBytes(byte b[], int off, int len) throws IOException { checkWriteMode(); ensureConnected(); return fileHandler.write(b, off, len); } // JAVADOC COMMENT ELIDED // See java.io.OutputStream.flush() in CLDC 1.1 protected void flush() throws IOException { checkWriteMode(); ensureConnected(); fileHandler.flush(); } /** * Called once by each child input stream. * If the input stream is marked open, it will be marked closed and * the if the connection and output stream are closed the disconnect * method will be called. * * @exception IOException if the subclass throws one */ protected void closeInputStream() throws IOException { maxIStreams++; fileHandler.closeForRead(); super.closeInputStream(); } /** * Called once by each child output stream. * If the output stream is marked open, it will be marked closed and * the if the connection and input stream are closed the disconnect * method will be called. * * @exception IOException if the subclass throws one */ protected void closeOutputStream() throws IOException { maxOStreams++; flush(); fileHandler.closeForWrite(); super.closeOutputStream(); } /** * Free up the connection resources. * * @exception IOException if an I/O error occurs. */ protected void disconnect() throws IOException { try { if (fileHandler != null) { fileHandler.close(); } } finally { fileHandler = null; } } // In order to compile against MIDP's ConnectionBaseAdapter /** * Establishes the connection. * @param name URL path fragment * @param mode access mode * @param timeouts flag to indicate that timeouts allowed * @throws IOException if an error occurs */ protected void connect(String name, int mode, boolean timeouts) throws IOException {} /** * Checks that the connection is already open. * @throws IOException if the connection is closed */ protected void ensureConnected() throws IOException { if (!isRoot(fileRoot)) { throw new IOException("Root is not accessible"); } connect(fileRoot, filePath + fileName); } /** * Connects to the native file handler and sets <code>nativePathName</code> * to the real name in the file system. * * @param root virtual root of the file. * @param pathName path and name of the file at the given root. */ private void connect(String root, String pathName) throws IOException { if (fileHandler == null) { fileHandler = getFileHandler(); nativePathName = fileHandler.connect(root, pathName); fileHandler.ensurePrivateDirExists(root); } } /** * Opens the file connection. * @param name URL path fragment * @param mode access mode * @param timeouts flag to indicate that timeouts allowed * @param unescape flag to indicate whether URL must be unescaped * @return an opened Connection * @throws IOException if some other kind of I/O error occurs. */ private Connection openPrimImpl(String name, int mode, boolean timeouts, boolean unescape) throws IOException { // Accepting URLs without double slash after scheme (violates RFC 1738). // See CR 6588553. int rootStart = name.startsWith("//") ? name.indexOf('/', 2) : 0; if (rootStart == -1 || name.charAt(0) != '/' ) { throw new IllegalArgumentException("Malformed File URL"); } /* The string must be a valid URL path separated by "/" */ if (name.indexOf("/../", rootStart) != -1 || name.indexOf("/./", rootStart) != -1 || name.endsWith("/..") || name.endsWith("/.") || !"/".equals(sep) && name.indexOf(sep, rootStart) != -1 || name.indexOf('\\') != -1) { throw new IllegalArgumentException("/. or /.. is not supported " + "or other illegal characters found"); } if (unescape) { name = EscapedUtil.getUnescapedString(name); } String fileURL = "file:" + name; String fileName; String fileRoot; String filePath; int nameLength = name.length(); int pathStart = name.indexOf('/', rootStart + 1); if (pathStart == -1) { throw new IllegalArgumentException("Root is not specified"); } if (pathStart == (nameLength - 1)) { fileName = ""; fileRoot = name.substring(rootStart + 1); filePath = name.substring(rootStart); } else { fileRoot = name.substring(rootStart + 1, pathStart + 1); int fileStart = name.lastIndexOf('/', nameLength - 2); if (fileStart <= pathStart) { fileName = name.substring(pathStart + 1); filePath = name.substring(rootStart, pathStart + 1); } else { filePath = name.substring(rootStart, fileStart + 1); fileName = name.substring(fileStart + 1); } } connect(fileRoot, filePath + fileName); // Perform security checks before any object state changes since // this method is used not only by Connector.open() but // by FileConnection.setFileConnection() too. switch (mode) { case Connector.READ: checkReadPermission(nativePathName, mode); checkReadMode(); maxOStreams = 0; break; case Connector.WRITE: checkWritePermission(nativePathName, mode); checkWriteMode(); maxIStreams = 0; break; case Connector.READ_WRITE: checkReadPermission(nativePathName, mode); checkReadMode(); checkWritePermission(nativePathName, mode); checkWriteMode(); break; default: throw new IllegalArgumentException("Invalid mode"); } this.fileURL = fileURL; this.mode = mode; this.fileRoot = fileRoot; this.filePath = filePath; this.fileName = fileName; connectionOpen = true; return this; } /** * Checks if path is a root path. * @param root path to be checked * @return <code>true</code> if path is a root, * <code>false</code> otherwise. */ private boolean isRoot(String root) { Vector r = listRoots(); // retrieve up-to-date list of mounted roots for (int i = 0; i < r.size(); i++) { String name = (String)r.elementAt(i); if (name.equals(root)) { return true; } } return false; } /** * Checks that the connection is already open and connected. * @throws ConnectionClosedException if the connection is closed * @throws IOException if any error occurs while connecting */ protected void ensureOpenAndConnected() throws IOException { if (!isOpen()) { throw new ConnectionClosedException("Connection is closed"); } ensureConnected(); } /** * Checks that the connection mode allows reading. * * @throws IllegalModeException if connection is write only */ protected final void checkReadMode() { if (mode == Connector.WRITE) { throw new IllegalModeException("Connection is write only"); } } /** * Checks that the connection mode allows writing. * * @throws IllegalModeException if connection is read only */ protected final void checkWriteMode() { if (mode == Connector.READ) { throw new IllegalModeException("Connection is read only"); } } /** * Checks that the application has permission to read. * @param fileURL complete file URL * @param mode access mode * @throws InterruptedIOException if the permission dialog is * terminated before completed * @throws SecurityException if read is not allowed * @throws IllegalModeException if connection is write only */ private final void checkReadPermission(String filePath, int mode) throws InterruptedIOException { if (classSecurityToken == null) { // FC permission checkPermission(FileConnectionPermission.READ.getName(), filePath); } else { // call from PIM classSecurityToken.checkIfPermissionAllowed( FileConnectionPermission.READ); } } /** * Checks that the application has permission to read. * @throws InterruptedIOException if the permission dialog is * terminated before completed * @throws SecurityException if read is not allowed * @throws IllegalModeException if connection is write only */ protected final void checkReadPermission() throws InterruptedIOException { checkReadPermission(nativePathName, mode); } /** * Checks that the application has permission to write. * @param fileURL complete file URL * @param mode access mode * @throws InterruptedIOException if the permission dialog is * terminated before completed * @throws SecurityException if write is not allowed * @throws IllegalModeException if connection is read only */ private final void checkWritePermission(String filePath, int mode) throws InterruptedIOException { if (classSecurityToken == null) { // FC permission checkPermission(FileConnectionPermission.WRITE.getName(), filePath); } else { // call from PIM classSecurityToken.checkIfPermissionAllowed( FileConnectionPermission.WRITE); } } /** * Checks that the application has permission to write. * @throws InterruptedIOException if the permission dialog is * terminated before completed * @throws SecurityException if write is not allowed * @throws IllegalModeException if connection is read only */ protected final void checkWritePermission() throws InterruptedIOException { checkWritePermission(nativePathName, mode); } /** * Gets an array of file system roots. * @return up-to-date array of file system roots; * empty array is returned if there are no roots available. */ public static Vector listRoots() { BaseFileHandler fh = getFileHandler(); return fh.listRoots(); } // JAVADOC COMMENT ELIDED - see FileConnection.list() description private Enumeration listInternal(String filter, boolean includeHidden) throws IOException { checkReadMode(); ensureOpenAndConnected(); if (filter != null) { if (filter.indexOf('/') != -1) { throw new IllegalArgumentException( "Filter contains any path specification"); } String illegalChars = fileHandler.illegalFileNameChars(); for (int i = 0; i < illegalChars.length(); i++) { if (filter.indexOf(illegalChars.charAt(i)) != -1) { throw new IllegalArgumentException("Filter contains characters " + "invalid for a filename"); } } } return fileHandler.list(filter, includeHidden).elements(); } /** * Gets the file handler. * @return handle to current file connection */ private static BaseFileHandler getFileHandler() { String def = "com.sun.cdc.io.j2me.file.DefaultFileHandler"; String n = null; if (hasOtherFileHandler) { n = Configuration.getProperty( "com.sun.io.j2me.fileHandlerImpl"); if (n == null) { hasOtherFileHandler = false; } } if (hasOtherFileHandler) { try { return (BaseFileHandler) (Class.forName(n)).newInstance(); } catch (ClassNotFoundException e) { hasOtherFileHandler = false; } catch (Error e) { hasOtherFileHandler = false; } catch (IllegalAccessException e) { hasOtherFileHandler = false; } catch (InstantiationException e) { hasOtherFileHandler = false; } } try { return (BaseFileHandler) (Class.forName(def)).newInstance(); } catch (ClassNotFoundException e) { throw new Error("Unable to create FileConnection Handler: " + e); } catch (Error e) { throw new Error("Unable to create FileConnection Handler: " + e); } catch (IllegalAccessException e) { throw new Error("Unable to create FileConnection Handler: " + e); } catch (InstantiationException e) { throw new Error("Unable to create FileConnection Handler: " + e); } } } /** * Utility for escaped character handling. */ class EscapedUtil { /** * Gets the escaped string. * @param name string to be processed * @return escaped string * @throws IllegalArgumentException if encoding not supported */ public static String getEscapedString(String name) { try { if (name == null) { return null; } byte newName[] = new byte[name.length()*12]; int nextPlace = 0; for (int i = 0; i < name.length(); i++) { char c = name.charAt(i); if (containsReserved(c)) { char data[] = {c}; byte[] reservedBytes = new String(data).getBytes("utf-8"); for (int j = 0; j < reservedBytes.length; j++) { newName[nextPlace++] = '%'; byte upper = (byte) ((reservedBytes[j] >> 4) & 0xF); if (upper <= 9) { newName[nextPlace++] = (byte) ('0' + upper); } else { newName[nextPlace++] = (byte) ('A' + (upper - 10)); } byte lower = (byte) (reservedBytes[j] & 0xF); if (lower <= 9) { newName[nextPlace++] = (byte) ('0' + lower); } else { newName[nextPlace++] = (byte) ('A' + (lower - 10)); } } } else { newName[nextPlace++] = (byte)c; } } return new String(newName, 0, nextPlace); } catch (UnsupportedEncodingException uee) { throw new IllegalArgumentException(uee.getMessage()); } } /** * Gets the unescaped string. * <pre> * escaped = "%" hex hex * hex = digit | "A" | "B" | "C" | "D" | "E" | "F" | * "a" | "b" | "c" | "d" | "e" | "f" * </pre> * @param name string to be processed * @return escaped string * @throws IllegalArgumentException if encoding not supported * */ public static String getUnescapedString(String name) { try { if (name == null) { return null; } if (name.indexOf("%") == -1) { return name; } else { byte newName[] = new byte[name.length()]; int nextPlace = 0; for (int i = 0; i < name.length(); i++) { char c = name.charAt(i); if (c == '%') { String hexNum = name.substring(i+1, i+3).toUpperCase(); if (isHexCharsLegal(hexNum)) { c = hexToChar(hexNum); i = i + 2; } else { throw new IllegalArgumentException("Bad format"); } } else if (containsReserved(c)) { throw new IllegalArgumentException("Bad escaped format"); } newName[nextPlace++] = (byte)c; } return new String(newName, 0, nextPlace, "UTF-8"); } } catch (UnsupportedEncodingException uee) { throw new IllegalArgumentException(uee.getMessage()); } } /** * Checks if the hexadecimal character is valid. * @param hexValue string to be checked * @return <code>true</code> if all characters are valid */ private static boolean isHexCharsLegal(String hexValue) { if ((isDigit(hexValue.charAt(0)) || isABCDEF(hexValue.charAt(0))) && (isDigit(hexValue.charAt(1)) || isABCDEF(hexValue.charAt(1)))) { return true; } else { return false; } } /** * Converts one hexadecimal char. * @param hexValue string to be processed * @return normalized hex value */ private static char hexToChar(String hexValue) { char c = 0; if (isDigit(hexValue.charAt(0))) { c += (hexValue.charAt(0) - '0')*16; } else { c += (hexValue.charAt(0) - 'A' + 10)*16; } if (isDigit(hexValue.charAt(1))) { c += (hexValue.charAt(01) - '0'); } else { c += (hexValue.charAt(1) - 'A' + 10); } return c; } /** * Checks if character is decimal digit. * @param c character to check * @return <code>true</code> if in the range 0..9 */ private static boolean isDigit(char c) { return (c >= '0' && c <= '9'); } /** * Checks if character is hexadecimal digit. * @param c character to check * @return <code>true</code> if in the range A..F */ private static boolean isABCDEF(char c) { return (c >= 'A' && c <= 'F'); } /** * Checks if character is from the reserved character set. * @param c character to check * @return <code>true</code> if not in the range A..Z, * a..z,..9, or punctuation (forward slash, colon, hyphen, * under score, period, exclamation, tilde, asterisk, single quote, * left paren or right paren). */ private static boolean containsReserved(char c) { return !((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || ("/:-_.!~*'()".indexOf(c) != -1)); } } // End of EscapeUtil