/*
GNU LESSER GENERAL PUBLIC LICENSE
Copyright (C) 2006 The Lobo Project
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Contact info: lobochief@users.sourceforge.net
*/
/*
* Created on Jun 12, 2005
*/
package org.lobobrowser.util;
import java.net.URL;
import java.net.URLConnection;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.StringTokenizer;
import java.util.TimeZone;
import java.util.logging.Logger;
import org.eclipse.jdt.annotation.NonNull;
public class Urls {
private static final Logger logger = Logger.getLogger(Urls.class.getName());
public static final DateFormat PATTERN_RFC1123 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
static {
final DateFormat df = PATTERN_RFC1123;
df.setTimeZone(TimeZone.getTimeZone("GMT"));
}
private Urls() {
super();
}
/** Whether the URL refers to a resource in the local file system. */
public static boolean isLocal(final java.net.URL url) {
if (isLocalFile(url)) {
return true;
}
final String protocol = url.getProtocol();
if ("jar".equalsIgnoreCase(protocol)) {
final String path = url.getPath();
final int emIdx = path.lastIndexOf('!');
final String subUrlString = emIdx == -1 ? path : path.substring(0, emIdx);
try {
final URL subUrl = new URL(subUrlString);
return isLocal(subUrl);
} catch (final java.net.MalformedURLException mfu) {
return false;
}
} else {
return false;
}
}
/** Whether the URL is a file in the local file system. */
public static boolean isLocalFile(final java.net.URL url) {
final String scheme = url.getProtocol();
return "file".equalsIgnoreCase(scheme) && !hasHost(url);
}
public static boolean hasHost(final java.net.URL url) {
final String host = url.getHost();
return (host != null) && !"".equals(host);
}
/**
* Creates an absolute URL in a manner equivalent to major browsers.
*/
public static @NonNull URL createURL(final URL baseUrl, final String relativeUrl) throws java.net.MalformedURLException {
return new URL(baseUrl, relativeUrl);
}
/**
* Returns the time when the document should be considered expired. The time
* will be zero if the document always needs to be revalidated. It will be
* <code>null</code> if no expiration time is specified.
*/
public static Long getExpiration(final URLConnection connection, final long baseTime) {
final String cacheControl = connection.getHeaderField("Cache-Control");
if (cacheControl != null) {
final StringTokenizer tok = new StringTokenizer(cacheControl, ",");
while (tok.hasMoreTokens()) {
final String token = tok.nextToken().trim().toLowerCase();
if ("must-revalidate".equals(token)) {
return new Long(0);
} else if (token.startsWith("max-age")) {
final int eqIdx = token.indexOf('=');
if (eqIdx != -1) {
final String value = token.substring(eqIdx + 1).trim();
try {
final long seconds = Long.parseLong(value);
return new Long(baseTime + (seconds * 1000L));
} catch (final NumberFormatException nfe) {
logger.warning("getExpiration(): Bad Cache-Control max-age value: " + value);
// ignore
}
}
}
}
}
final String expires = connection.getHeaderField("Expires");
if (expires != null) {
try {
synchronized (PATTERN_RFC1123) {
final Date expDate = PATTERN_RFC1123.parse(expires);
return new Long(expDate.getTime());
}
} catch (final java.text.ParseException pe) {
int seconds;
try {
seconds = Integer.parseInt(expires);
return new Long(baseTime + (seconds * 1000));
} catch (final NumberFormatException nfe) {
logger.warning("getExpiration(): Bad Expires header value: " + expires);
}
}
}
// For issue #99
// When there is no cache setting; assume a 60 second cache expiry time, for now.
// return baseTime + (60 * 1000);
// ^^ Update: Assume expiry time only if ETag header is present.
// We have not implemented the ETag header yet, but the presence of it is a good indicator that the response could be cached.
final String etag = connection.getHeaderField("Etag");
return etag == null ? 0 : baseTime + (60 * 1000);
}
public static List<NameValuePair> getHeaders(final URLConnection connection) {
// Random access index recommended.
final List<NameValuePair> headers = new ArrayList<>();
for (int n = 0;; n++) {
final String value = connection.getHeaderField(n);
if (value == null) {
break;
}
// Key may be null for n == 0.
final String key = connection.getHeaderFieldKey(n);
if (key != null) {
headers.add(new NameValuePair(key, value));
}
}
return headers;
}
public static String getCharset(final URLConnection connection) {
final String contentType = connection.getContentType();
if (contentType == null) {
return getDefaultCharset(connection);
}
final StringTokenizer tok = new StringTokenizer(contentType, ";");
if (tok.hasMoreTokens()) {
tok.nextToken();
while (tok.hasMoreTokens()) {
final String assignment = tok.nextToken().trim();
final int eqIdx = assignment.indexOf('=');
if (eqIdx != -1) {
final String varName = assignment.substring(0, eqIdx).trim();
if ("charset".equalsIgnoreCase(varName)) {
final String varValue = assignment.substring(eqIdx + 1);
return Strings.unquote(varValue.trim());
}
}
}
}
return getDefaultCharset(connection);
}
private static String getDefaultCharset(final URLConnection connection) {
final URL url = connection.getURL();
if (Urls.isLocalFile(url)) {
final String charset = System.getProperty("file.encoding");
return charset == null ? "ISO-8859-1" : charset;
} else {
return "ISO-8859-1";
}
}
public static String getNoRefForm(final URL url) {
final String host = url.getHost();
final int port = url.getPort();
final String portText = port == -1 ? "" : ":" + port;
final String userInfo = url.getUserInfo();
final String userInfoText = (userInfo == null) || (userInfo.length() == 0) ? "" : userInfo + "@";
final String hostPort = (host == null) || (host.length() == 0) ? "" : "//" + userInfoText + host + portText;
return url.getProtocol() + ":" + hostPort + url.getFile();
}
/**
* Comparison that does not consider Ref.
*
* @param url1
* @param url2
*/
public static boolean sameNoRefURL(final URL url1, final URL url2) {
return java.util.Objects.equals(url1.getHost(), url2.getHost()) && java.util.Objects.equals(url1.getProtocol(), url2.getProtocol())
&& (url1.getPort() == url2.getPort()) && java.util.Objects.equals(url1.getFile(), url2.getFile())
&& java.util.Objects.equals(url1.getUserInfo(), url2.getUserInfo());
}
/**
* Returns the port of a URL always. When the port is not explicitly set, it
* returns the default port
*/
public static int getPort(final URL url) {
final int setPort = url.getPort();
return setPort == -1 ? url.getDefaultPort() : setPort;
}
/**
* Converts the given URL into a valid URL by encoding illegal characters.
* Right now it is implemented like in IE7: only spaces are replaced with
* "%20". (Firefox 3 also encodes other non-ASCII and some ASCII characters).
*
* @param the
* URL to convert
* @return the encoded URL
*/
public static String encodeIllegalCharacters(final String url) {
return url.replace(" ", "%20");
}
/**
* Converts the given URL into a valid URL by removing control characters
* (ASCII code < 32).
*
* @param the
* URL to convert
* @return the encoded URL
*/
public static String removeControlCharacters(final String url) {
final StringBuilder sb = new StringBuilder(url.length());
for (int i = 0; i < url.length(); i++) {
final char c = url.charAt(i);
if (c >= 32) {
sb.append(c);
}
}
return sb.toString();
}
}