409 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			409 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| /*
 | |
|  * Copyright (C) 2006 The Android Open Source Project
 | |
|  *
 | |
|  * Licensed under the Apache License, Version 2.0 (the "License");
 | |
|  * you may not use this file except in compliance with the License.
 | |
|  * You may obtain a copy of the License at
 | |
|  *
 | |
|  *      http://www.apache.org/licenses/LICENSE-2.0
 | |
|  *
 | |
|  * Unless required by applicable law or agreed to in writing, software
 | |
|  * distributed under the License is distributed on an "AS IS" BASIS,
 | |
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
|  * See the License for the specific language governing permissions and
 | |
|  * limitations under the License.
 | |
|  */
 | |
| 
 | |
| package android.webkit;
 | |
| 
 | |
| import java.io.UnsupportedEncodingException;
 | |
| import java.util.regex.Matcher;
 | |
| import java.util.regex.Pattern;
 | |
| 
 | |
| import android.net.Uri;
 | |
| import android.net.ParseException;
 | |
| import android.net.WebAddress;
 | |
| import android.util.Log;
 | |
| 
 | |
| public final class URLUtil {
 | |
| 
 | |
|     private static final String LOGTAG = "webkit";
 | |
| 
 | |
|     // to refer to bar.png under your package's asset/foo/ directory, use
 | |
|     // "file:///android_asset/foo/bar.png".
 | |
|     static final String ASSET_BASE = "file:///android_asset/";
 | |
|     // to refer to bar.png under your package's res/drawable/ directory, use
 | |
|     // "file:///android_res/drawable/bar.png". Use "drawable" to refer to
 | |
|     // "drawable-hdpi" directory as well.
 | |
|     static final String RESOURCE_BASE = "file:///android_res/";
 | |
|     static final String FILE_BASE = "file://";
 | |
|     static final String PROXY_BASE = "file:///cookieless_proxy/";
 | |
| 
 | |
|     /**
 | |
|      * Cleans up (if possible) user-entered web addresses
 | |
|      */
 | |
|     public static String guessUrl(String inUrl) {
 | |
| 
 | |
|         String retVal = inUrl;
 | |
|         WebAddress webAddress;
 | |
| 
 | |
|         Log.v(LOGTAG, "guessURL before queueRequest: " + inUrl);
 | |
| 
 | |
|         if (inUrl.length() == 0) return inUrl;
 | |
|         if (inUrl.startsWith("about:")) return inUrl;
 | |
|         // Do not try to interpret data scheme URLs
 | |
|         if (inUrl.startsWith("data:")) return inUrl;
 | |
|         // Do not try to interpret file scheme URLs
 | |
|         if (inUrl.startsWith("file:")) return inUrl;
 | |
|         // Do not try to interpret javascript scheme URLs
 | |
|         if (inUrl.startsWith("javascript:")) return inUrl;
 | |
| 
 | |
|         // bug 762454: strip period off end of url
 | |
|         if (inUrl.endsWith(".") == true) {
 | |
|             inUrl = inUrl.substring(0, inUrl.length() - 1);
 | |
|         }
 | |
| 
 | |
|         try {
 | |
|             webAddress = new WebAddress(inUrl);
 | |
|         } catch (ParseException ex) {
 | |
| 
 | |
|             if (DebugFlags.URL_UTIL) {
 | |
|                 Log.v(LOGTAG, "smartUrlFilter: failed to parse url = " + inUrl);
 | |
|             }
 | |
|             return retVal;
 | |
|         }
 | |
| 
 | |
|         // Check host
 | |
|         if (webAddress.mHost.indexOf('.') == -1) {
 | |
|             // no dot: user probably entered a bare domain.  try .com
 | |
|             webAddress.mHost = "www." + webAddress.mHost + ".com";
 | |
|         }
 | |
|         return webAddress.toString();
 | |
|     }
 | |
| 
 | |
|     public static String composeSearchUrl(String inQuery, String template,
 | |
|                                           String queryPlaceHolder) {
 | |
|         int placeHolderIndex = template.indexOf(queryPlaceHolder);
 | |
|         if (placeHolderIndex < 0) {
 | |
|             return null;
 | |
|         }
 | |
| 
 | |
|         String query;
 | |
|         StringBuilder buffer = new StringBuilder();
 | |
|         buffer.append(template.substring(0, placeHolderIndex));
 | |
| 
 | |
|         try {
 | |
|             query = java.net.URLEncoder.encode(inQuery, "utf-8");
 | |
|             buffer.append(query);
 | |
|         } catch (UnsupportedEncodingException ex) {
 | |
|             return null;
 | |
|         }
 | |
| 
 | |
|         buffer.append(template.substring(
 | |
|                 placeHolderIndex + queryPlaceHolder.length()));
 | |
| 
 | |
|         return buffer.toString();
 | |
|     }
 | |
| 
 | |
|     public static byte[] decode(byte[] url) throws IllegalArgumentException {
 | |
|         if (url.length == 0) {
 | |
|             return new byte[0];
 | |
|         }
 | |
| 
 | |
|         // Create a new byte array with the same length to ensure capacity
 | |
|         byte[] tempData = new byte[url.length];
 | |
| 
 | |
|         int tempCount = 0;
 | |
|         for (int i = 0; i < url.length; i++) {
 | |
|             byte b = url[i];
 | |
|             if (b == '%') {
 | |
|                 if (url.length - i > 2) {
 | |
|                     b = (byte) (parseHex(url[i + 1]) * 16
 | |
|                             + parseHex(url[i + 2]));
 | |
|                     i += 2;
 | |
|                 } else {
 | |
|                     throw new IllegalArgumentException("Invalid format");
 | |
|                 }
 | |
|             }
 | |
|             tempData[tempCount++] = b;
 | |
|         }
 | |
|         byte[] retData = new byte[tempCount];
 | |
|         System.arraycopy(tempData, 0, retData, 0, tempCount);
 | |
|         return retData;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return True iff the url is correctly URL encoded
 | |
|      */
 | |
|     static boolean verifyURLEncoding(String url) {
 | |
|         int count = url.length();
 | |
|         if (count == 0) {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         int index = url.indexOf('%');
 | |
|         while (index >= 0 && index < count) {
 | |
|             if (index < count - 2) {
 | |
|                 try {
 | |
|                     parseHex((byte) url.charAt(++index));
 | |
|                     parseHex((byte) url.charAt(++index));
 | |
|                 } catch (IllegalArgumentException e) {
 | |
|                     return false;
 | |
|                 }
 | |
|             } else {
 | |
|                 return false;
 | |
|             }
 | |
|             index = url.indexOf('%', index + 1);
 | |
|         }
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     private static int parseHex(byte b) {
 | |
|         if (b >= '0' && b <= '9') return (b - '0');
 | |
|         if (b >= 'A' && b <= 'F') return (b - 'A' + 10);
 | |
|         if (b >= 'a' && b <= 'f') return (b - 'a' + 10);
 | |
| 
 | |
|         throw new IllegalArgumentException("Invalid hex char '" + b + "'");
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return True iff the url is an asset file.
 | |
|      */
 | |
|     public static boolean isAssetUrl(String url) {
 | |
|         return (null != url) && url.startsWith(ASSET_BASE);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return True iff the url is a resource file.
 | |
|      * @hide
 | |
|      */
 | |
|     public static boolean isResourceUrl(String url) {
 | |
|         return (null != url) && url.startsWith(RESOURCE_BASE);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return True iff the url is an proxy url to allow cookieless network 
 | |
|      * requests from a file url.
 | |
|      * @deprecated Cookieless proxy is no longer supported.
 | |
|      */
 | |
|     @Deprecated
 | |
|     public static boolean isCookielessProxyUrl(String url) {
 | |
|         return (null != url) && url.startsWith(PROXY_BASE);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return True iff the url is a local file.
 | |
|      */
 | |
|     public static boolean isFileUrl(String url) {
 | |
|         return (null != url) && (url.startsWith(FILE_BASE) &&
 | |
|                                  !url.startsWith(ASSET_BASE) &&
 | |
|                                  !url.startsWith(PROXY_BASE));
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return True iff the url is an about: url.
 | |
|      */
 | |
|     public static boolean isAboutUrl(String url) {
 | |
|         return (null != url) && url.startsWith("about:");
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return True iff the url is a data: url.
 | |
|      */
 | |
|     public static boolean isDataUrl(String url) {
 | |
|         return (null != url) && url.startsWith("data:");
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return True iff the url is a javascript: url.
 | |
|      */
 | |
|     public static boolean isJavaScriptUrl(String url) {
 | |
|         return (null != url) && url.startsWith("javascript:");
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return True iff the url is an http: url.
 | |
|      */
 | |
|     public static boolean isHttpUrl(String url) {
 | |
|         return (null != url) &&
 | |
|                (url.length() > 6) &&
 | |
|                url.substring(0, 7).equalsIgnoreCase("http://");
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return True iff the url is an https: url.
 | |
|      */
 | |
|     public static boolean isHttpsUrl(String url) {
 | |
|         return (null != url) &&
 | |
|                (url.length() > 7) &&
 | |
|                url.substring(0, 8).equalsIgnoreCase("https://");
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return True iff the url is a network url.
 | |
|      */
 | |
|     public static boolean isNetworkUrl(String url) {
 | |
|         if (url == null || url.length() == 0) {
 | |
|             return false;
 | |
|         }
 | |
|         return isHttpUrl(url) || isHttpsUrl(url);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return True iff the url is a content: url.
 | |
|      */
 | |
|     public static boolean isContentUrl(String url) {
 | |
|         return (null != url) && url.startsWith("content:");
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return True iff the url is valid.
 | |
|      */
 | |
|     public static boolean isValidUrl(String url) {
 | |
|         if (url == null || url.length() == 0) {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         return (isAssetUrl(url) ||
 | |
|                 isResourceUrl(url) ||
 | |
|                 isFileUrl(url) ||
 | |
|                 isAboutUrl(url) ||
 | |
|                 isHttpUrl(url) ||
 | |
|                 isHttpsUrl(url) ||
 | |
|                 isJavaScriptUrl(url) ||
 | |
|                 isContentUrl(url));
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Strips the url of the anchor.
 | |
|      */
 | |
|     public static String stripAnchor(String url) {
 | |
|         int anchorIndex = url.indexOf('#');
 | |
|         if (anchorIndex != -1) {
 | |
|             return url.substring(0, anchorIndex);
 | |
|         }
 | |
|         return url;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Guesses canonical filename that a download would have, using
 | |
|      * the URL and contentDisposition. File extension, if not defined,
 | |
|      * is added based on the mimetype
 | |
|      * @param url Url to the content
 | |
|      * @param contentDisposition Content-Disposition HTTP header or null
 | |
|      * @param mimeType Mime-type of the content or null
 | |
|      * 
 | |
|      * @return suggested filename
 | |
|      */
 | |
|     public static final String guessFileName(
 | |
|             String url,
 | |
|             String contentDisposition,
 | |
|             String mimeType) {
 | |
|         String filename = null;
 | |
|         String extension = null;
 | |
| 
 | |
|         // If we couldn't do anything with the hint, move toward the content disposition
 | |
|         if (filename == null && contentDisposition != null) {
 | |
|             filename = parseContentDisposition(contentDisposition);
 | |
|             if (filename != null) {
 | |
|                 int index = filename.lastIndexOf('/') + 1;
 | |
|                 if (index > 0) {
 | |
|                     filename = filename.substring(index);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // If all the other http-related approaches failed, use the plain uri
 | |
|         if (filename == null) {
 | |
|             String decodedUrl = Uri.decode(url);
 | |
|             if (decodedUrl != null) {
 | |
|                 int queryIndex = decodedUrl.indexOf('?');
 | |
|                 // If there is a query string strip it, same as desktop browsers
 | |
|                 if (queryIndex > 0) {
 | |
|                     decodedUrl = decodedUrl.substring(0, queryIndex);
 | |
|                 }
 | |
|                 if (!decodedUrl.endsWith("/")) {
 | |
|                     int index = decodedUrl.lastIndexOf('/') + 1;
 | |
|                     if (index > 0) {
 | |
|                         filename = decodedUrl.substring(index);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // Finally, if couldn't get filename from URI, get a generic filename
 | |
|         if (filename == null) {
 | |
|             filename = "downloadfile";
 | |
|         }
 | |
| 
 | |
|         // Split filename between base and extension
 | |
|         // Add an extension if filename does not have one
 | |
|         int dotIndex = filename.indexOf('.');
 | |
|         if (dotIndex < 0) {
 | |
|             if (mimeType != null) {
 | |
|                 extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
 | |
|                 if (extension != null) {
 | |
|                     extension = "." + extension;
 | |
|                 }
 | |
|             }
 | |
|             if (extension == null) {
 | |
|                 if (mimeType != null && mimeType.toLowerCase().startsWith("text/")) {
 | |
|                     if (mimeType.equalsIgnoreCase("text/html")) {
 | |
|                         extension = ".html";
 | |
|                     } else {
 | |
|                         extension = ".txt";
 | |
|                     }
 | |
|                 } else {
 | |
|                     extension = ".bin";
 | |
|                 }
 | |
|             }
 | |
|         } else {
 | |
|             if (mimeType != null) {
 | |
|                 // Compare the last segment of the extension against the mime type.
 | |
|                 // If there's a mismatch, discard the entire extension.
 | |
|                 int lastDotIndex = filename.lastIndexOf('.');
 | |
|                 String typeFromExt = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
 | |
|                         filename.substring(lastDotIndex + 1));
 | |
|                 if (typeFromExt != null && !typeFromExt.equalsIgnoreCase(mimeType)) {
 | |
|                     extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
 | |
|                     if (extension != null) {
 | |
|                         extension = "." + extension;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             if (extension == null) {
 | |
|                 extension = filename.substring(dotIndex);
 | |
|             }
 | |
|             filename = filename.substring(0, dotIndex);
 | |
|         }
 | |
| 
 | |
|         return filename + extension;
 | |
|     }
 | |
| 
 | |
|     /** Regex used to parse content-disposition headers */
 | |
|     private static final Pattern CONTENT_DISPOSITION_PATTERN =
 | |
|             Pattern.compile("attachment;\\s*filename\\s*=\\s*(\"?)([^\"]*)\\1\\s*$",
 | |
|             Pattern.CASE_INSENSITIVE);
 | |
| 
 | |
|     /*
 | |
|      * Parse the Content-Disposition HTTP Header. The format of the header
 | |
|      * is defined here: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html
 | |
|      * This header provides a filename for content that is going to be
 | |
|      * downloaded to the file system. We only support the attachment type.
 | |
|      * Note that RFC 2616 specifies the filename value must be double-quoted.
 | |
|      * Unfortunately some servers do not quote the value so to maintain
 | |
|      * consistent behaviour with other browsers, we allow unquoted values too.
 | |
|      */
 | |
|     static String parseContentDisposition(String contentDisposition) {
 | |
|         try {
 | |
|             Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition);
 | |
|             if (m.find()) {
 | |
|                 return m.group(2);
 | |
|             }
 | |
|         } catch (IllegalStateException ex) {
 | |
|              // This function is defined as returning null when it can't parse the header
 | |
|         }
 | |
|         return null;
 | |
|     }
 | |
| }
 | 
