Source for gnu.java.net.protocol.file.Connection

   1: /* FileURLConnection.java -- URLConnection class for "file" protocol
   2:    Copyright (C) 1998, 1999, 2003 Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10:  
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: package gnu.java.net.protocol.file;
  39: 
  40: import gnu.classpath.SystemProperties;
  41: 
  42: import java.io.BufferedInputStream;
  43: import java.io.BufferedOutputStream;
  44: import java.io.ByteArrayInputStream;
  45: import java.io.ByteArrayOutputStream;
  46: import java.io.File;
  47: import java.io.FileInputStream;
  48: import java.io.FileOutputStream;
  49: import java.io.FilePermission;
  50: import java.io.IOException;
  51: import java.io.InputStream;
  52: import java.io.OutputStream;
  53: import java.io.OutputStreamWriter;
  54: import java.io.Writer;
  55: import java.net.ProtocolException;
  56: import java.net.URL;
  57: import java.net.URLConnection;
  58: import java.security.Permission;
  59: import java.text.SimpleDateFormat;
  60: import java.util.Date;
  61: import java.util.Locale;
  62: import java.net.MalformedURLException;
  63: 
  64: /**
  65:  * This subclass of java.net.URLConnection models a URLConnection via
  66:  * the "file" protocol.
  67:  *
  68:  * @author Aaron M. Renn (arenn@urbanophile.com)
  69:  * @author Nic Ferrier (nferrier@tapsellferrier.co.uk)
  70:  * @author Warren Levy (warrenl@cygnus.com)
  71:  */
  72: public class Connection extends URLConnection
  73: {
  74:   /**
  75:    * Default permission for a file
  76:    */
  77:   private static final String DEFAULT_PERMISSION = "read";
  78: 
  79:   private static class StaticData
  80:   {
  81:     /**
  82:      * HTTP-style DateFormat, used to format the last-modified header.
  83:      */
  84:     static SimpleDateFormat dateFormat
  85:       = new SimpleDateFormat("EEE, dd MMM yyyy hh:mm:ss 'GMT'",
  86:                              new Locale ("En", "Us", "Unix"));
  87: 
  88:     static String lineSeparator =
  89:       SystemProperties.getProperty("line.separator");
  90:   }
  91: 
  92:   
  93:   /**
  94:    * This is a File object for this connection
  95:    */
  96:   private File file;
  97: 
  98:   /**
  99:    * If a directory, contains a list of files in the directory.
 100:    */
 101:   private byte[] directoryListing;
 102: 
 103:   /**
 104:    * InputStream if we are reading from the file
 105:    */
 106:   private InputStream inputStream;
 107: 
 108:   /**
 109:    * OutputStream if we are writing to the file
 110:    */
 111:   private OutputStream outputStream;
 112:   
 113:   /**
 114:    * FilePermission to read the file
 115:    */
 116:   private FilePermission permission;
 117: 
 118:   /**
 119:    * Calls superclass constructor to initialize.
 120:    */
 121:   public Connection(URL url)
 122:   {
 123:     super (url);
 124: 
 125:     permission = new FilePermission(getURL().getFile(), DEFAULT_PERMISSION);
 126:   }
 127:   
 128:   /**
 129:    * Unquote "%" + hex quotes characters
 130:    *
 131:    * @param str The string to unquote or null.
 132:    *
 133:    * @return The unquoted string or null if str was null.
 134:    *
 135:    * @exception MalformedURLException If the given string contains invalid
 136:    * escape sequences.
 137:    *
 138:    * Sadly the same as URI.unquote, but there's nothing we can do to
 139:    * make it accessible.
 140:    *
 141:    */
 142:   public static String unquote(String str) throws MalformedURLException
 143:   {
 144:     if (str == null)
 145:       return null;
 146:     byte[] buf = new byte[str.length()];
 147:     int pos = 0;
 148:     for (int i = 0; i < str.length(); i++)
 149:       {
 150:     char c = str.charAt(i);
 151:     if (c > 127)
 152:       throw new MalformedURLException(str + " : Invalid character");
 153:     if (c == '%')
 154:       {
 155:         if (i + 2 >= str.length())
 156:           throw new MalformedURLException(str + " : Invalid quoted character");
 157:         int hi = Character.digit(str.charAt(++i), 16);
 158:         int lo = Character.digit(str.charAt(++i), 16);
 159:         if (lo < 0 || hi < 0)
 160:           throw new MalformedURLException(str + " : Invalid quoted character");
 161:         buf[pos++] = (byte) (hi * 16 + lo);
 162:       }
 163:     else
 164:       buf[pos++] = (byte) c;
 165:       }
 166:     try
 167:       {
 168:     return new String(buf, 0, pos, "utf-8");
 169:       }
 170:     catch (java.io.UnsupportedEncodingException x2)
 171:       {
 172:     throw (Error) new InternalError().initCause(x2);
 173:       }
 174:   }
 175: 
 176:   /**
 177:    * "Connects" to the file by opening it.
 178:    */
 179:   public void connect() throws IOException
 180:   {
 181:     // Call is ignored if already connected.
 182:     if (connected)
 183:       return;
 184:     
 185:     // If not connected, then file needs to be openned.
 186:     file = new File (unquote(getURL().getFile()));
 187: 
 188:     if (! file.isDirectory())
 189:       {
 190:     if (doInput)
 191:       inputStream = new BufferedInputStream(new FileInputStream(file));
 192:     
 193:     if (doOutput)
 194:       outputStream = new BufferedOutputStream(new FileOutputStream(file));
 195:       }
 196:     else
 197:       {
 198:     if (doInput)
 199:       {
 200:             inputStream = new ByteArrayInputStream(getDirectoryListing());
 201:       }
 202: 
 203:     if (doOutput)
 204:       throw new ProtocolException
 205:         ("file: protocol does not support output on directories");
 206:       }
 207:     
 208:     connected = true;
 209:   }
 210: 
 211:   /**
 212:    * Populates the <code>directoryListing</code> field with a byte array
 213:    * containing a representation of the directory listing.
 214:    */
 215:   byte[] getDirectoryListing()
 216:     throws IOException
 217:   {
 218:     if (directoryListing == null)
 219:       {
 220:         ByteArrayOutputStream sink = new ByteArrayOutputStream();
 221:         // NB uses default character encoding for this system
 222:         Writer writer = new OutputStreamWriter(sink);
 223:     
 224:         String[] files = file.list();
 225:     
 226:         for (int i = 0; i < files.length; i++)
 227:           {
 228:             writer.write(files[i]);
 229:             writer.write(StaticData.lineSeparator);
 230:           }
 231: 
 232:         directoryListing = sink.toByteArray();
 233:       }
 234:     return directoryListing;  
 235:   }
 236:   
 237:   /**
 238:    * Opens the file for reading and returns a stream for it.
 239:    *
 240:    * @return An InputStream for this connection.
 241:    *
 242:    * @exception IOException If an error occurs
 243:    */
 244:   public InputStream getInputStream()
 245:     throws IOException
 246:   {
 247:     if (!doInput)
 248:       throw new ProtocolException("Can't open InputStream if doInput is false");
 249:     
 250:     if (!connected)
 251:       connect();
 252:     
 253:     return inputStream;
 254:   }
 255: 
 256:   /**
 257:    * Opens the file for writing and returns a stream for it.
 258:    *
 259:    * @return An OutputStream for this connection.
 260:    *
 261:    * @exception IOException If an error occurs.
 262:    */
 263:   public OutputStream getOutputStream()
 264:     throws IOException
 265:   {
 266:     if (!doOutput)
 267:       throw new
 268:     ProtocolException("Can't open OutputStream if doOutput is false");
 269: 
 270:     if (!connected)
 271:       connect();
 272:     
 273:     return outputStream;
 274:   }
 275: 
 276:   /**
 277:    * Get the last modified time of the resource.
 278:    *
 279:    * @return the time since epoch that the resource was modified.
 280:    */
 281:   public long getLastModified()
 282:   {
 283:     try
 284:       {
 285:     if (!connected)
 286:       connect();
 287: 
 288:     return file.lastModified();
 289:       }
 290:     catch (IOException e)
 291:       {
 292:     return -1;
 293:       }
 294:   }
 295:   
 296:   /**
 297:    *  Get an http-style header field. Just handle a few common ones. 
 298:    */
 299:   public String getHeaderField(String field)
 300:   {
 301:     try
 302:       {
 303:     if (!connected)
 304:       connect();
 305: 
 306:     if (field.equals("content-type"))
 307:           return guessContentTypeFromName(file.getName());
 308:     else if (field.equals("content-length"))
 309:           {
 310:             if (file.isDirectory())
 311:               {
 312:                 return Integer.toString(getContentLength());
 313:               }
 314:             return Long.toString(file.length());
 315:           }
 316:     else if (field.equals("last-modified"))
 317:       {
 318:         synchronized (StaticData.dateFormat)
 319:           {
 320:             return StaticData.dateFormat.format(
 321:                         new Date(file.lastModified()));
 322:           }
 323:       }
 324:       }
 325:     catch (IOException e)
 326:       {
 327:         // Fall through.
 328:       }
 329:     return null;
 330:   }
 331: 
 332:   /**
 333:    * Get the length of content.
 334:    *
 335:    * @return the length of the content.
 336:    */
 337:   public int getContentLength()
 338:   {
 339:     try
 340:       {
 341:     if (!connected)
 342:       connect();
 343:         
 344:         if (file.isDirectory())
 345:           {
 346:             return getDirectoryListing().length;
 347:           }
 348:     return (int) file.length();
 349:       }
 350:     catch (IOException e)
 351:       {
 352:     return -1;
 353:       }
 354:   }
 355:   
 356:   /**
 357:    * This method returns a <code>Permission</code> object representing the
 358:    * permissions required to access this URL.  This method returns a
 359:    * <code>java.io.FilePermission</code> for the file's path with a read
 360:    * permission.
 361:    *
 362:    * @return A Permission object
 363:    */
 364:   public Permission getPermission() throws IOException
 365:   {
 366:     return permission;
 367:   }
 368: }