Home | History | Annotate | Download | only in url
      1 /*
      2  *  Licensed to the Apache Software Foundation (ASF) under one or more
      3  *  contributor license agreements.  See the NOTICE file distributed with
      4  *  this work for additional information regarding copyright ownership.
      5  *  The ASF licenses this file to You under the Apache License, Version 2.0
      6  *  (the "License"); you may not use this file except in compliance with
      7  *  the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  *  Unless required by applicable law or agreed to in writing, software
     12  *  distributed under the License is distributed on an "AS IS" BASIS,
     13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  *  See the License for the specific language governing permissions and
     15  *  limitations under the License.
     16  */
     17 
     18 package libcore.net.url;
     19 
     20 import java.io.BufferedInputStream;
     21 import java.io.ByteArrayInputStream;
     22 import java.io.ByteArrayOutputStream;
     23 import java.io.File;
     24 import java.io.FileInputStream;
     25 import java.io.FilePermission;
     26 import java.io.IOException;
     27 import java.io.InputStream;
     28 import java.io.PrintStream;
     29 import java.net.URL;
     30 import java.net.URLConnection;
     31 import java.util.Collections;
     32 import java.util.Comparator;
     33 import java.util.List;
     34 import java.util.Map;
     35 import java.util.TreeMap;
     36 import libcore.net.UriCodec;
     37 
     38 /**
     39  * This subclass extends <code>URLConnection</code>.
     40  * <p>
     41  * This class is responsible for connecting, getting content and input stream of
     42  * the file.
     43  */
     44 public class FileURLConnection extends URLConnection {
     45 
     46     private static final Comparator<String> HEADER_COMPARATOR = new Comparator<String>() {
     47         @Override
     48         public int compare(String a, String b) {
     49             if (a == b) {
     50                 return 0;
     51             } else if (a == null) {
     52                 return -1;
     53             } else if (b == null) {
     54                 return 1;
     55             } else {
     56                 return String.CASE_INSENSITIVE_ORDER.compare(a, b);
     57             }
     58         }
     59     };
     60 
     61     private String filename;
     62 
     63     private InputStream is;
     64 
     65     private long length = -1;
     66 
     67     private long lastModified = -1;
     68 
     69     private boolean isDir;
     70 
     71     private FilePermission permission;
     72 
     73     /**
     74      * A set of three key value pairs representing the headers we support.
     75      */
     76     private final String[] headerKeysAndValues;
     77 
     78     private static final int CONTENT_TYPE_VALUE_IDX = 1;
     79     private static final int CONTENT_LENGTH_VALUE_IDX = 3;
     80     private static final int LAST_MODIFIED_VALUE_IDX = 5;
     81 
     82     private Map<String, List<String>> headerFields;
     83 
     84     /**
     85      * Creates an instance of <code>FileURLConnection</code> for establishing
     86      * a connection to the file pointed by this <code>URL<code>
     87      *
     88      * @param url The URL this connection is connected to
     89      */
     90     public FileURLConnection(URL url) {
     91         super(url);
     92         filename = url.getFile();
     93         if (filename == null) {
     94             filename = "";
     95         }
     96         filename = UriCodec.decode(filename);
     97         headerKeysAndValues = new String[] {
     98                 "content-type", null,
     99                 "content-length", null,
    100                 "last-modified", null };
    101     }
    102 
    103     /**
    104      * This methods will attempt to obtain the input stream of the file pointed
    105      * by this <code>URL</code>. If the file is a directory, it will return
    106      * that directory listing as an input stream.
    107      *
    108      * @throws IOException
    109      *             if an IO error occurs while connecting
    110      */
    111     @Override
    112     public void connect() throws IOException {
    113         File f = new File(filename);
    114         IOException error = null;
    115         if (f.isDirectory()) {
    116             isDir = true;
    117             is = getDirectoryListing(f);
    118             // use -1 for the contentLength
    119             lastModified = f.lastModified();
    120             headerKeysAndValues[CONTENT_TYPE_VALUE_IDX] = "text/html";
    121         } else {
    122             try {
    123                 is = new BufferedInputStream(new FileInputStream(f));
    124             } catch (IOException ioe) {
    125                 error = ioe;
    126             }
    127 
    128             if (error == null) {
    129                 length = f.length();
    130                 lastModified = f.lastModified();
    131                 headerKeysAndValues[CONTENT_TYPE_VALUE_IDX] = getContentTypeForPlainFiles();
    132             } else {
    133                 headerKeysAndValues[CONTENT_TYPE_VALUE_IDX] = "content/unknown";
    134             }
    135         }
    136 
    137         headerKeysAndValues[CONTENT_LENGTH_VALUE_IDX] = String.valueOf(length);
    138         headerKeysAndValues[LAST_MODIFIED_VALUE_IDX] = String.valueOf(lastModified);
    139 
    140         connected = true;
    141         if (error != null) {
    142             throw error;
    143         }
    144     }
    145 
    146     @Override
    147     public String getHeaderField(String key) {
    148         if (!connected) {
    149             try {
    150                 connect();
    151             } catch (IOException ioe) {
    152                 return null;
    153             }
    154         }
    155 
    156         for (int i = 0; i < headerKeysAndValues.length; i += 2) {
    157             if (headerKeysAndValues[i].equalsIgnoreCase(key)) {
    158                 return headerKeysAndValues[i + 1];
    159             }
    160         }
    161 
    162         return null;
    163     }
    164 
    165     @Override
    166     public String getHeaderFieldKey(int position) {
    167         if (!connected) {
    168             try {
    169                 connect();
    170             } catch (IOException ioe) {
    171                 return null;
    172             }
    173         }
    174 
    175         if (position < 0 || position > headerKeysAndValues.length / 2) {
    176             return null;
    177         }
    178 
    179         return headerKeysAndValues[position * 2];
    180     }
    181 
    182     @Override
    183     public String getHeaderField(int position) {
    184         if (!connected) {
    185             try {
    186                 connect();
    187             } catch (IOException ioe) {
    188                 return null;
    189             }
    190         }
    191 
    192         if (position < 0 || position > headerKeysAndValues.length / 2) {
    193             return null;
    194         }
    195 
    196         return headerKeysAndValues[(position * 2) + 1];
    197     }
    198 
    199     @Override
    200     public Map<String, List<String>> getHeaderFields() {
    201         if (headerFields == null) {
    202             final TreeMap<String, List<String>> headerFieldsMap = new TreeMap<>(HEADER_COMPARATOR);
    203 
    204             for (int i = 0; i < headerKeysAndValues.length; i+=2) {
    205                 headerFieldsMap.put(headerKeysAndValues[i],
    206                         Collections.singletonList(headerKeysAndValues[i + 1]));
    207             }
    208 
    209             headerFields = Collections.unmodifiableMap(headerFieldsMap);
    210         }
    211 
    212         return headerFields;
    213     }
    214 
    215     /**
    216      * Returns the length of the file in bytes, or {@code -1} if the length cannot be
    217      * represented as an {@code int}. See {@link #getContentLengthLong()} for a method that can
    218      * handle larger files.
    219      */
    220     @Override
    221     public int getContentLength() {
    222         long length = getContentLengthLong();
    223         return length <= Integer.MAX_VALUE ? (int) length : -1;
    224     }
    225 
    226     /**
    227      * Returns the length of the file in bytes.
    228      */
    229     private long getContentLengthLong() {
    230         try {
    231             if (!connected) {
    232                 connect();
    233             }
    234         } catch (IOException e) {
    235             // default is -1
    236         }
    237         return length;
    238     }
    239 
    240     /**
    241      * Returns the content type of the resource. Just takes a guess based on the
    242      * name.
    243      *
    244      * @return the content type
    245      */
    246     @Override
    247     public String getContentType() {
    248         // The content-type header field is always at position 0.
    249         return getHeaderField(0);
    250     }
    251 
    252     private String getContentTypeForPlainFiles() {
    253         String result = guessContentTypeFromName(url.getFile());
    254         if (result != null) {
    255             return result;
    256         }
    257 
    258         try {
    259             result = guessContentTypeFromStream(is);
    260         } catch (IOException e) {
    261             // Ignore
    262         }
    263         if (result != null) {
    264             return result;
    265         }
    266 
    267         return "content/unknown";
    268     }
    269 
    270     /**
    271      * Returns the directory listing of the file component as an input stream.
    272      *
    273      * @return the input stream of the directory listing
    274      */
    275     private InputStream getDirectoryListing(File f) {
    276         String fileList[] = f.list();
    277         ByteArrayOutputStream bytes = new java.io.ByteArrayOutputStream();
    278         PrintStream out = new PrintStream(bytes);
    279         out.print("<title>Directory Listing</title>\n");
    280         out.print("<base href=\"file:");
    281         out.print(f.getPath().replace('\\', '/') + "/\"><h1>" + f.getPath()
    282                 + "</h1>\n<hr>\n");
    283         int i;
    284         for (i = 0; i < fileList.length; i++) {
    285             out.print(fileList[i] + "<br>\n");
    286         }
    287         out.close();
    288         return new ByteArrayInputStream(bytes.toByteArray());
    289     }
    290 
    291     /**
    292      * Returns the input stream of the object referred to by this
    293      * <code>URLConnection</code>
    294      *
    295      * File Sample : "/ZIP211/+/harmony/tools/javac/resources/javac.properties"
    296      * Invalid File Sample:
    297      * "/ZIP/+/harmony/tools/javac/resources/javac.properties"
    298      * "ZIP211/+/harmony/tools/javac/resources/javac.properties"
    299      *
    300      * @return input stream of the object
    301      *
    302      * @throws IOException
    303      *             if an IO error occurs
    304      */
    305     @Override
    306     public InputStream getInputStream() throws IOException {
    307         if (!connected) {
    308             connect();
    309         }
    310         return is;
    311     }
    312 
    313     /**
    314      * Returns the permission, in this case the subclass, FilePermission object
    315      * which represents the permission necessary for this URLConnection to
    316      * establish the connection.
    317      *
    318      * @return the permission required for this URLConnection.
    319      *
    320      * @throws IOException
    321      *             if an IO exception occurs while creating the permission.
    322      */
    323     @Override
    324     public java.security.Permission getPermission() throws IOException {
    325         if (permission == null) {
    326             String path = filename;
    327             if (File.separatorChar != '/') {
    328                 path = path.replace('/', File.separatorChar);
    329             }
    330             permission = new FilePermission(path, "read");
    331         }
    332         return permission;
    333     }
    334 }
    335