• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1  /*
2   * Copyright (C) 2006 The Android Open Source Project
3   *
4   * Licensed under the Apache License, Version 2.0 (the "License");
5   * you may not use this file except in compliance with the License.
6   * You may obtain a copy of the License at
7   *
8   *      http://www.apache.org/licenses/LICENSE-2.0
9   *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package android.webkit;
18  
19  import java.io.UnsupportedEncodingException;
20  import java.util.Locale;
21  import java.util.regex.Matcher;
22  import java.util.regex.Pattern;
23  
24  import android.net.Uri;
25  import android.net.ParseException;
26  import android.net.WebAddress;
27  import android.util.Log;
28  
29  public final class URLUtil {
30  
31      private static final String LOGTAG = "webkit";
32      private static final boolean TRACE = false;
33  
34      // to refer to bar.png under your package's asset/foo/ directory, use
35      // "file:///android_asset/foo/bar.png".
36      static final String ASSET_BASE = "file:///android_asset/";
37      // to refer to bar.png under your package's res/drawable/ directory, use
38      // "file:///android_res/drawable/bar.png". Use "drawable" to refer to
39      // "drawable-hdpi" directory as well.
40      static final String RESOURCE_BASE = "file:///android_res/";
41      static final String FILE_BASE = "file://";
42      static final String PROXY_BASE = "file:///cookieless_proxy/";
43      static final String CONTENT_BASE = "content:";
44  
45      /**
46       * Cleans up (if possible) user-entered web addresses
47       */
guessUrl(String inUrl)48      public static String guessUrl(String inUrl) {
49  
50          String retVal = inUrl;
51          WebAddress webAddress;
52  
53          if (TRACE) Log.v(LOGTAG, "guessURL before queueRequest: " + inUrl);
54  
55          if (inUrl.length() == 0) return inUrl;
56          if (inUrl.startsWith("about:")) return inUrl;
57          // Do not try to interpret data scheme URLs
58          if (inUrl.startsWith("data:")) return inUrl;
59          // Do not try to interpret file scheme URLs
60          if (inUrl.startsWith("file:")) return inUrl;
61          // Do not try to interpret javascript scheme URLs
62          if (inUrl.startsWith("javascript:")) return inUrl;
63  
64          // bug 762454: strip period off end of url
65          if (inUrl.endsWith(".") == true) {
66              inUrl = inUrl.substring(0, inUrl.length() - 1);
67          }
68  
69          try {
70              webAddress = new WebAddress(inUrl);
71          } catch (ParseException ex) {
72  
73              if (TRACE) {
74                  Log.v(LOGTAG, "smartUrlFilter: failed to parse url = " + inUrl);
75              }
76              return retVal;
77          }
78  
79          // Check host
80          if (webAddress.getHost().indexOf('.') == -1) {
81              // no dot: user probably entered a bare domain.  try .com
82              webAddress.setHost("www." + webAddress.getHost() + ".com");
83          }
84          return webAddress.toString();
85      }
86  
composeSearchUrl(String inQuery, String template, String queryPlaceHolder)87      public static String composeSearchUrl(String inQuery, String template,
88                                            String queryPlaceHolder) {
89          int placeHolderIndex = template.indexOf(queryPlaceHolder);
90          if (placeHolderIndex < 0) {
91              return null;
92          }
93  
94          String query;
95          StringBuilder buffer = new StringBuilder();
96          buffer.append(template.substring(0, placeHolderIndex));
97  
98          try {
99              query = java.net.URLEncoder.encode(inQuery, "utf-8");
100              buffer.append(query);
101          } catch (UnsupportedEncodingException ex) {
102              return null;
103          }
104  
105          buffer.append(template.substring(
106                  placeHolderIndex + queryPlaceHolder.length()));
107  
108          return buffer.toString();
109      }
110  
decode(byte[] url)111      public static byte[] decode(byte[] url) throws IllegalArgumentException {
112          if (url.length == 0) {
113              return new byte[0];
114          }
115  
116          // Create a new byte array with the same length to ensure capacity
117          byte[] tempData = new byte[url.length];
118  
119          int tempCount = 0;
120          for (int i = 0; i < url.length; i++) {
121              byte b = url[i];
122              if (b == '%') {
123                  if (url.length - i > 2) {
124                      b = (byte) (parseHex(url[i + 1]) * 16
125                              + parseHex(url[i + 2]));
126                      i += 2;
127                  } else {
128                      throw new IllegalArgumentException("Invalid format");
129                  }
130              }
131              tempData[tempCount++] = b;
132          }
133          byte[] retData = new byte[tempCount];
134          System.arraycopy(tempData, 0, retData, 0, tempCount);
135          return retData;
136      }
137  
138      /**
139       * @return True iff the url is correctly URL encoded
140       */
verifyURLEncoding(String url)141      static boolean verifyURLEncoding(String url) {
142          int count = url.length();
143          if (count == 0) {
144              return false;
145          }
146  
147          int index = url.indexOf('%');
148          while (index >= 0 && index < count) {
149              if (index < count - 2) {
150                  try {
151                      parseHex((byte) url.charAt(++index));
152                      parseHex((byte) url.charAt(++index));
153                  } catch (IllegalArgumentException e) {
154                      return false;
155                  }
156              } else {
157                  return false;
158              }
159              index = url.indexOf('%', index + 1);
160          }
161          return true;
162      }
163  
parseHex(byte b)164      private static int parseHex(byte b) {
165          if (b >= '0' && b <= '9') return (b - '0');
166          if (b >= 'A' && b <= 'F') return (b - 'A' + 10);
167          if (b >= 'a' && b <= 'f') return (b - 'a' + 10);
168  
169          throw new IllegalArgumentException("Invalid hex char '" + b + "'");
170      }
171  
172      /**
173       * @return True iff the url is an asset file.
174       */
isAssetUrl(String url)175      public static boolean isAssetUrl(String url) {
176          return (null != url) && url.startsWith(ASSET_BASE);
177      }
178  
179      /**
180       * @return True iff the url is a resource file.
181       * @hide
182       */
isResourceUrl(String url)183      public static boolean isResourceUrl(String url) {
184          return (null != url) && url.startsWith(RESOURCE_BASE);
185      }
186  
187      /**
188       * @return True iff the url is a proxy url to allow cookieless network
189       * requests from a file url.
190       * @deprecated Cookieless proxy is no longer supported.
191       */
192      @Deprecated
isCookielessProxyUrl(String url)193      public static boolean isCookielessProxyUrl(String url) {
194          return (null != url) && url.startsWith(PROXY_BASE);
195      }
196  
197      /**
198       * @return True iff the url is a local file.
199       */
isFileUrl(String url)200      public static boolean isFileUrl(String url) {
201          return (null != url) && (url.startsWith(FILE_BASE) &&
202                                   !url.startsWith(ASSET_BASE) &&
203                                   !url.startsWith(PROXY_BASE));
204      }
205  
206      /**
207       * @return True iff the url is an about: url.
208       */
isAboutUrl(String url)209      public static boolean isAboutUrl(String url) {
210          return (null != url) && url.startsWith("about:");
211      }
212  
213      /**
214       * @return True iff the url is a data: url.
215       */
isDataUrl(String url)216      public static boolean isDataUrl(String url) {
217          return (null != url) && url.startsWith("data:");
218      }
219  
220      /**
221       * @return True iff the url is a javascript: url.
222       */
isJavaScriptUrl(String url)223      public static boolean isJavaScriptUrl(String url) {
224          return (null != url) && url.startsWith("javascript:");
225      }
226  
227      /**
228       * @return True iff the url is an http: url.
229       */
isHttpUrl(String url)230      public static boolean isHttpUrl(String url) {
231          return (null != url) &&
232                 (url.length() > 6) &&
233                 url.substring(0, 7).equalsIgnoreCase("http://");
234      }
235  
236      /**
237       * @return True iff the url is an https: url.
238       */
isHttpsUrl(String url)239      public static boolean isHttpsUrl(String url) {
240          return (null != url) &&
241                 (url.length() > 7) &&
242                 url.substring(0, 8).equalsIgnoreCase("https://");
243      }
244  
245      /**
246       * @return True iff the url is a network url.
247       */
isNetworkUrl(String url)248      public static boolean isNetworkUrl(String url) {
249          if (url == null || url.length() == 0) {
250              return false;
251          }
252          return isHttpUrl(url) || isHttpsUrl(url);
253      }
254  
255      /**
256       * @return True iff the url is a content: url.
257       */
isContentUrl(String url)258      public static boolean isContentUrl(String url) {
259          return (null != url) && url.startsWith(CONTENT_BASE);
260      }
261  
262      /**
263       * @return True iff the url is valid.
264       */
isValidUrl(String url)265      public static boolean isValidUrl(String url) {
266          if (url == null || url.length() == 0) {
267              return false;
268          }
269  
270          return (isAssetUrl(url) ||
271                  isResourceUrl(url) ||
272                  isFileUrl(url) ||
273                  isAboutUrl(url) ||
274                  isHttpUrl(url) ||
275                  isHttpsUrl(url) ||
276                  isJavaScriptUrl(url) ||
277                  isContentUrl(url));
278      }
279  
280      /**
281       * Strips the url of the anchor.
282       */
stripAnchor(String url)283      public static String stripAnchor(String url) {
284          int anchorIndex = url.indexOf('#');
285          if (anchorIndex != -1) {
286              return url.substring(0, anchorIndex);
287          }
288          return url;
289      }
290  
291      /**
292       * Guesses canonical filename that a download would have, using
293       * the URL and contentDisposition. File extension, if not defined,
294       * is added based on the mimetype
295       * @param url Url to the content
296       * @param contentDisposition Content-Disposition HTTP header or null
297       * @param mimeType Mime-type of the content or null
298       *
299       * @return suggested filename
300       */
guessFileName( String url, String contentDisposition, String mimeType)301      public static final String guessFileName(
302              String url,
303              String contentDisposition,
304              String mimeType) {
305          String filename = null;
306          String extension = null;
307  
308          // If we couldn't do anything with the hint, move toward the content disposition
309          if (filename == null && contentDisposition != null) {
310              filename = parseContentDisposition(contentDisposition);
311              if (filename != null) {
312                  int index = filename.lastIndexOf('/') + 1;
313                  if (index > 0) {
314                      filename = filename.substring(index);
315                  }
316              }
317          }
318  
319          // If all the other http-related approaches failed, use the plain uri
320          if (filename == null) {
321              String decodedUrl = Uri.decode(url);
322              if (decodedUrl != null) {
323                  int queryIndex = decodedUrl.indexOf('?');
324                  // If there is a query string strip it, same as desktop browsers
325                  if (queryIndex > 0) {
326                      decodedUrl = decodedUrl.substring(0, queryIndex);
327                  }
328                  if (!decodedUrl.endsWith("/")) {
329                      int index = decodedUrl.lastIndexOf('/') + 1;
330                      if (index > 0) {
331                          filename = decodedUrl.substring(index);
332                      }
333                  }
334              }
335          }
336  
337          // Finally, if couldn't get filename from URI, get a generic filename
338          if (filename == null) {
339              filename = "downloadfile";
340          }
341  
342          // Split filename between base and extension
343          // Add an extension if filename does not have one
344          int dotIndex = filename.indexOf('.');
345          if (dotIndex < 0) {
346              if (mimeType != null) {
347                  extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
348                  if (extension != null) {
349                      extension = "." + extension;
350                  }
351              }
352              if (extension == null) {
353                  if (mimeType != null && mimeType.toLowerCase(Locale.ROOT).startsWith("text/")) {
354                      if (mimeType.equalsIgnoreCase("text/html")) {
355                          extension = ".html";
356                      } else {
357                          extension = ".txt";
358                      }
359                  } else {
360                      extension = ".bin";
361                  }
362              }
363          } else {
364              if (mimeType != null) {
365                  // Compare the last segment of the extension against the mime type.
366                  // If there's a mismatch, discard the entire extension.
367                  int lastDotIndex = filename.lastIndexOf('.');
368                  String typeFromExt = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
369                          filename.substring(lastDotIndex + 1));
370                  if (typeFromExt != null && !typeFromExt.equalsIgnoreCase(mimeType)) {
371                      extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
372                      if (extension != null) {
373                          extension = "." + extension;
374                      }
375                  }
376              }
377              if (extension == null) {
378                  extension = filename.substring(dotIndex);
379              }
380              filename = filename.substring(0, dotIndex);
381          }
382  
383          return filename + extension;
384      }
385  
386      /** Regex used to parse content-disposition headers */
387      private static final Pattern CONTENT_DISPOSITION_PATTERN =
388              Pattern.compile("attachment;\\s*filename\\s*=\\s*(\"?)([^\"]*)\\1\\s*$",
389              Pattern.CASE_INSENSITIVE);
390  
391      /*
392       * Parse the Content-Disposition HTTP Header. The format of the header
393       * is defined here: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html
394       * This header provides a filename for content that is going to be
395       * downloaded to the file system. We only support the attachment type.
396       * Note that RFC 2616 specifies the filename value must be double-quoted.
397       * Unfortunately some servers do not quote the value so to maintain
398       * consistent behaviour with other browsers, we allow unquoted values too.
399       */
parseContentDisposition(String contentDisposition)400      static String parseContentDisposition(String contentDisposition) {
401          try {
402              Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition);
403              if (m.find()) {
404                  return m.group(2);
405              }
406          } catch (IllegalStateException ex) {
407               // This function is defined as returning null when it can't parse the header
408          }
409          return null;
410      }
411  }
412