summaryrefslogtreecommitdiffstats
path: root/core/java/android/webkit/URLUtil.java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android/webkit/URLUtil.java')
-rw-r--r--core/java/android/webkit/URLUtil.java363
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;
+ }
+}