diff options
Diffstat (limited to 'core/java/android/webkit/URLUtil.java')
-rw-r--r-- | core/java/android/webkit/URLUtil.java | 363 |
1 files changed, 363 insertions, 0 deletions
diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java new file mode 100644 index 0000000..0e8144e --- /dev/null +++ b/core/java/android/webkit/URLUtil.java @@ -0,0 +1,363 @@ +/* + * 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.Config; +import android.util.Log; + +public final class URLUtil { + + private static final String LOGTAG = "webkit"; + + static final String ASSET_BASE = "file:///android_asset/"; + 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 (Config.LOGV) { + 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; + } + + 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 an proxy url to allow cookieless network + * requests from a file url. + * @deprecated Cookieless proxy is no longer supported. + */ + 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) || + 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*\"([^\"]*)\""); + + /* + * 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. + */ + private static String parseContentDisposition(String contentDisposition) { + try { + Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition); + if (m.find()) { + return m.group(1); + } + } catch (IllegalStateException ex) { + // This function is defined as returning null when it can't parse the header + } + return null; + } +} |