/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun * Microsystems, Inc. All Rights Reserved. */ package org.openide.filesystems; import java.io.*; import java.net.*; import java.security.*; /** Special URL connection directly accessing an internal file object. * * @author Ales Novak, Petr Hamernik, Jan Jancura, Jaroslav Tulach */ final class FileURL extends URLConnection { /** Protocol name for this type of URL. */ public static final String PROTOCOL = "nbfs"; // NOI18N /** url separator */ private static final char SEPARATOR = '/'; /** Default implemenatation of handler for this type of URL. */ static URLStreamHandler HANDLER = new URLStreamHandler () { /** * @param u - URL to open connection to. * @return new URLConnection. */ public URLConnection openConnection(URL u) throws IOException { return new FileURL (u); } }; /** FileObject that we want to connect to. */ private FileObject fo; /** * Create a new connection to a {@link FileObject}. * @param u URL of the connection. Please use {@link #encodeFileObject(FileObject)} to create the URL. */ private FileURL(URL u) { super (u); } /** Provides a URL to access a file object. * @param fo the file object * @return a URL using the correct syntax and {@link #PROTOCOL protocol} * @exception FileStateInvalidException if the file object is not valid (typically, if its filesystem is inconsistent or no longer present) */ public static URL encodeFileObject (FileObject fo) throws FileStateInvalidException { return encodeFileObject (fo.getFileSystem (), fo); } /** Encodes fileobject into URL. * @param fs file system the object is on * @param fo file object * @return URL */ private static URL encodeFileObject (FileSystem fs, FileObject fo) { String fsName = encodeFileSystemName (fs.getSystemName()); String fileName = fo.getPath(); String name = fsName + SEPARATOR + fileName; boolean needOfSlash = needOfSlash = (fo.isFolder() && fileName.length () != 0 && fileName.charAt(fileName.length()-1) != '/'); final String url = (needOfSlash)?(name+"/"):name; // #13038: the URL constructor accepting a handler is a security-sensitive // operation. Sometimes a user class loaded internally (customized bean...), // which has no privileges, needs to make and use an nbfs: URL, since this // may be the URL used by e.g. ClassLoader.getResource for resources. try { return (URL)AccessController.doPrivileged(new PrivilegedExceptionAction() { public Object run() throws Exception { // #30397: the host name cannot be null return new URL(PROTOCOL, "", -1, url, HANDLER); // NOI18N } }); } catch (PrivilegedActionException pae) { // MalformedURLException is declared but should not happen. IllegalStateException ise = new IllegalStateException(pae.toString()); ExternalUtil.annotate(ise, pae); throw ise; } } /** Retrieves the file object specified by an internal URL. * @param u the url to decode * @return the file object that is represented by the URL, or <code>null</code> if the URL is somehow invalid or the file does not exist */ public static FileObject decodeURL (URL u) { if (!u.getProtocol ().equals (PROTOCOL)) return null; // resource name String resourceName = u.getFile (); if (resourceName.startsWith ("/")) resourceName = resourceName.substring (1); // NOI18N // first part is FS name int first = resourceName.indexOf ('/'); if (first == -1) return null; String fileSystemName = decodeFileSystemName (resourceName.substring (0, first)); resourceName = resourceName.substring (first); FileSystem fsys = ExternalUtil.getRepository ().findFileSystem(fileSystemName); return (fsys == null) ? null : fsys.findResource (resourceName); } /* A method for connecting to a FileObject. */ public void connect() throws IOException { if (fo != null) return; fo = decodeURL (url); if (fo == null) { throw new FileNotFoundException("Cannot find: " + url); // NOI18N } } /* * @return InputStream or given FileObject. */ public InputStream getInputStream() throws IOException, UnknownServiceException { connect (); try { if (fo.isFolder()) return new FIS (fo); return fo.getInputStream(); } catch (FileNotFoundException e) { ExternalUtil.exception (e); throw e; } } /* * @return OutputStream for given FileObject. */ public OutputStream getOutputStream() throws IOException, UnknownServiceException { connect(); if (fo.isFolder()) throw new UnknownServiceException(); org.openide.filesystems.FileLock flock = fo.lock(); return new LockOS (fo.getOutputStream(flock), flock); } /* * @return length of FileObject. */ public int getContentLength() { try { connect(); return (int)fo.getSize(); } catch (IOException ex) { return 0; } } /** Get a header field (currently, content type only). * @param name the header name. Only <code>content-type</code> is guaranteed to be present. * @return the value (i.e., MIME type) */ public String getHeaderField(String name) { if (name.equalsIgnoreCase("content-type")) { // NOI18N try { connect(); if (fo.isFolder()) return "text/html"; // NOI18N else return fo.getMIMEType (); } catch (IOException e) { } } return super.getHeaderField(name); } // #13038: URLClassPath is going to check this. // Better not return AllPermission! // SocketPermission on localhost might also work. public Permission getPermission() throws IOException { // Note this is normally called by URLClassPath with an unconnected // URLConnection, so the fo will probably be null anyway. if (fo != null) { File f = FileUtil.toFile(fo); if (f != null) { return new FilePermission(f.getAbsolutePath(), "read"); // NOI18N } try { FileSystem fs = fo.getFileSystem(); if (fs instanceof JarFileSystem) { return new FilePermission(((JarFileSystem)fs).getJarFile().getAbsolutePath(), "read"); // NOI18N } // [PENDING] could do XMLFileSystem too... } catch (FileStateInvalidException fsie) { // ignore } } // fallback return new FilePermission("<<ALL FILES>>", "read"); // NOI18N } /** Encodes filesystem name. * @param fs original filesystem name * @return new encoded name */ static String encodeFileSystemName (String fs) { // [PENDING] this ought to use standard URL encoding, not this weird scheme StringBuffer sb = new StringBuffer (); for (int i = 0; i < fs.length (); i++) { switch (fs.charAt (i)) { case 'Q': sb.append ("QQ"); // NOI18N break; case '/': sb.append ("QB"); // NOI18N break; case ':': sb.append ("QC"); // NOI18N break; case '\\': sb.append ("QD"); // NOI18N break; case '#': sb.append ("QE"); // NOI18N break; default: sb.append (fs.charAt (i)); break; } } return sb.toString (); } /** Decodes name to FS one. * @param name encoded name * @return original name of the filesystem */ static String decodeFileSystemName (String name) { StringBuffer sb = new StringBuffer (); int i = 0; int len = name.length (); while (i < len) { char ch = name.charAt (i++); if (ch == 'Q' && i < len) { switch (name.charAt (i++)) { case 'B': sb.append ('/'); break; case 'C': sb.append (':'); break; case 'D': sb.append ('\\'); break; case 'E': sb.append ('#'); break; default: sb.append ('Q'); break; } } else { // not Q sb.append (ch); } } return sb.toString (); } /** Stream that also closes the lock, if closed. */ private static class LockOS extends java.io.BufferedOutputStream { /** lock */ private FileLock flock; /** * @param os is an OutputStream for writing in * @param lock is a lock for the stream */ public LockOS (OutputStream os, FileLock lock) throws IOException { super(os); flock = lock; } /** overriden */ public void close() throws IOException { flock.releaseLock(); super.close(); } } /** The class allows reading of folder via URL. Because of html * oriented user interface the document has html format. * * @author Ales Novak * @version 0.10 May 15, 1998 */ private static final class FIS extends InputStream { /** delegated reader that reads the document */ private StringReader reader; /** * @param folder is a folder */ public FIS (FileObject folder) throws IOException { reader = new StringReader(createDocument(folder)); } /** creates html document as string */ private String createDocument(FileObject folder) throws IOException { StringBuffer buff = new StringBuffer(150); StringBuffer lit = new StringBuffer(15); FileObject[] fobia = folder.getChildren(); String name; buff.append("<HTML>\n"); // NOI18N buff.append("<BODY>\n"); // NOI18N FileObject parent = folder.getParent(); if (parent != null) { // lit.setLength(0); // lit.append('/').append(parent.getPackageName('/')); buff.append("<P>"); // NOI18N buff.append("<A HREF=").append("..").append(">").append("..").append("</A>").append("\n"); // NOI18N buff.append("</P>"); // NOI18N } for (int i = 0; i < fobia.length; i++) { lit.setLength(0); lit.append(fobia[i].getNameExt()); name = lit.toString (); if (fobia[i].isFolder()) { lit.append('/'); // NOI18N } buff.append("<P>"); // NOI18N buff.append("<A HREF=").append((Object)lit).append(">").append(name).append("</A>").append("\n"); // NOI18N buff.append("</P>"); // NOI18N } buff.append("</BODY>\n"); // NOI18N buff.append("</HTML>\n"); // NOI18N return buff.toString(); } //************************************** stream methods ********** public int read() throws IOException { return reader.read(); } public int read(byte[] b, int off, int len) throws IOException { char[] ch = new char[len]; int r = reader.read(ch, 0, len); for (int i = 0; i < r; i++) b[off + i] = (byte) ch[i]; return r; } public long skip(long skip) throws IOException { return reader.skip(skip); } public void close() throws IOException { reader.close(); } public void reset() throws IOException { reader.reset(); } public boolean markSupported() { return false; } } // end of FIS }