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