/* GNU GENERAL PUBLIC LICENSE Copyright (C) 2006 The Lobo Project This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either verion 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU 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 Mar 13, 2005 */ package org.lobobrowser.request; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.util.EventObject; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import java.util.logging.Logger; import org.eclipse.jdt.annotation.NonNull; import org.lobobrowser.clientlet.ClientletResponse; import org.lobobrowser.ua.ProgressType; import org.lobobrowser.ua.RequestType; import org.lobobrowser.util.GenericEventListener; import org.lobobrowser.util.InputProgressEvent; import org.lobobrowser.util.MonitoredInputStream; import org.lobobrowser.util.Strings; import org.lobobrowser.util.Urls; import org.lobobrowser.util.io.BufferExceededException; import org.lobobrowser.util.io.IORoutines; import org.lobobrowser.util.io.RecordedInputStream; /** * @author J. H. S. */ public class ClientletResponseImpl implements ClientletResponse { private static final Logger logger = Logger.getLogger(ClientletResponseImpl.class.getName()); private static final int MAX_CACHE_BUFFER_SIZE = 10 * 1024 * 1024; private final URLConnection connection; private final RequestHandler requestHandler; private final boolean isCacheable; private final CacheInfo cacheInfo; private final boolean fromCache; private final RequestType requestType; // Security note: This URL must be final. private final @NonNull URL responseURL; private InputStream inputStream; public ClientletResponseImpl(final RequestHandler rhandler, final URLConnection connection, final @NonNull URL responseURL, final boolean fromCache, final CacheInfo cacheInfo, final boolean isCacheable, final RequestType requestType) { this.connection = connection; this.responseURL = responseURL; this.requestHandler = rhandler; this.isCacheable = isCacheable; this.cacheInfo = cacheInfo; this.fromCache = fromCache; this.requestType = requestType; } public ClientletResponseImpl(final RequestHandler rhandler, final URL url, final boolean fromCache, final CacheInfo cacheInfo, final boolean isCacheable, final String requestMethod, final RequestType requestType) throws IOException { this.connection = url.openConnection(); this.responseURL = url; this.requestHandler = rhandler; this.isCacheable = isCacheable; this.cacheInfo = cacheInfo; this.fromCache = fromCache; this.requestType = requestType; } public boolean isNewNavigationAction() { final RequestType rt = this.requestType; return (rt != RequestType.HISTORY) && (rt != RequestType.SOFT_RELOAD) && (rt != RequestType.HARD_RELOAD); } public boolean matches(final String mimeType, final String[] fileExtensions) { final String responseMimeType = this.getMimeType(); if ((responseMimeType == null) || "application/octet-stream".equalsIgnoreCase(responseMimeType) || "content/unknown".equalsIgnoreCase(responseMimeType)) { final String path = this.responseURL.getPath(); if (path == null) { return false; } final String pathTL = path.toLowerCase(); for (final String fileExtension : fileExtensions) { String fileExtensionTL = fileExtension.toLowerCase(); if (!fileExtensionTL.startsWith(".")) { fileExtensionTL = "." + fileExtensionTL; } if (pathTL.endsWith(fileExtensionTL)) { return true; } } return false; } else { return responseMimeType.equalsIgnoreCase(mimeType); } } public String getLastRequestMethod() { return this.requestHandler.getLatestRequestMethod(); } public void handleProgress(final ProgressType progressType, final @NonNull URL url, final String method, final int value, final int max) { this.requestHandler.handleProgress(progressType, url, method, value, max); } /* * (non-Javadoc) * * @see org.xamjwg.clientlet.ClientletResponse#isFromCache() */ public boolean isFromCache() { return this.fromCache; } public boolean isCacheable() { return this.isCacheable; } /* * (non-Javadoc) * * @see org.xamjwg.dom.ClientletResponse#getResponseURL() */ public @NonNull URL getResponseURL() { // Assumes connection doesn't use internal redirection. return this.responseURL; } /* * (non-Javadoc) * * @see org.xamjwg.dom.ClientletResponse#getHeader(java.lang.String) */ public String getHeader(final String name) { return this.connection.getHeaderField(name); } /* * (non-Javadoc) * * @see org.xamjwg.dom.ClientletResponse#getHeaders(java.lang.String, char) */ public String[] getHeaders(final String name) { final Map<String, List<String>> headers = this.connection.getHeaderFields(); final List<String> valuesList = headers.get(name); return valuesList == null ? null : valuesList.toArray(new String[0]); } /* * (non-Javadoc) * * @see org.xamjwg.dom.ClientletResponse#getHeaderNames() */ public Iterator<String> getHeaderNames() { final Map<String, List<String>> headers = this.connection.getHeaderFields(); return headers.keySet().iterator(); } /* * (non-Javadoc) * * @see org.xamjwg.dom.ClientletResponse#getInputStream() */ public InputStream getInputStream() throws IOException { if (this.inputStream == null) { final URLConnection connection = this.connection; InputStream in; if (connection instanceof HttpURLConnection) { in = IORoutines.getDecodedErrorStream(((HttpURLConnection) connection)); if (in == null) { in = IORoutines.getDecodedStream(connection); } } else { in = connection.getInputStream(); } final int contentLength = connection.getContentLength(); final int bufferSize = contentLength <= 0 ? 4096 : Math.min(contentLength, 8192); final URL responseURL = this.getResponseURL(); // if(logger.isLoggable(Level.INFO))logger.info("getInputStream(): Connection stream is " // + in); InputStream bis; if (this.requestHandler != null) { final MonitoredInputStream mis = new MonitoredInputStream(in); mis.evtProgress.addListener(new GenericEventListener() { public void processEvent(final EventObject event) { final InputProgressEvent pe = (InputProgressEvent) event; requestHandler.handleProgress(org.lobobrowser.ua.ProgressType.CONTENT_LOADING, responseURL, getLastRequestMethod(), pe.getProgress(), contentLength); } }); // TODO Buffer size too big if contentLength small bis = new BufferedInputStream(mis, bufferSize); } else { bis = new BufferedInputStream(in, bufferSize); } if (this.isCacheable) { this.inputStream = new RecordedInputStream(bis, MAX_CACHE_BUFFER_SIZE); } else { this.inputStream = bis; } } return this.inputStream; } /* * (non-Javadoc) * * @see org.xamjwg.dom.ClientletResponse#getContentType() */ public String getContentType() { return this.connection.getContentType(); } /* * (non-Javadoc) * * @see org.xamjwg.clientlet.ClientletResponse#getMimeType() */ public String getMimeType() { final String contentType = this.getContentType(); if (contentType == null) { return null; } final int scIdx = contentType.indexOf(';'); return scIdx == -1 ? contentType.trim() : contentType.substring(0, scIdx).trim(); } public int getContentLength() { return this.connection.getContentLength(); } public void ensureReachedEOF() throws IOException { // Don't get cached inputStream - could be null here. final InputStream in = this.getInputStream(); if (in instanceof RecordedInputStream) { final RecordedInputStream rin = (RecordedInputStream) in; if (!rin.hasReachedEOF()) { rin.consumeToEOF(); } } } public byte[] getStoredContent() { // Should call ensureReachedEOF() which will also ensure // inputStream is not null. final InputStream in = this.inputStream; if (in instanceof RecordedInputStream) { final RecordedInputStream rin = (RecordedInputStream) in; if (rin.hasReachedEOF()) { try { return rin.getBytesRead(); } catch (final BufferExceededException bee) { logger.warning("getStoredContent(): Recorded stream buffer size exceeded."); return null; } } } return null; } private String getDefaultCharset() { final URL url = this.getResponseURL(); if (Urls.isLocalFile(url)) { final String charset = System.getProperty("file.encoding"); return charset == null ? "ISO-8859-1" : charset; } else { return "ISO-8859-1"; } } /* * (non-Javadoc) * * @see org.xamjwg.clientlet.ClientletResponse#getCharset() */ public String getCharset() { final String contentType = this.getContentType(); if (contentType == null) { return this.getDefaultCharset(); } 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 this.getDefaultCharset(); } public boolean isCharsetProvided() { final String contentType = this.getContentType(); if (contentType == null) { return false; } 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)) { return true; } } } } return false; } /* * (non-Javadoc) * * @see org.xamjwg.clientlet.ClientletResponse#getResponseCode() */ public int getResponseCode() throws IOException { if (this.connection instanceof HttpURLConnection) { return ((HttpURLConnection) this.connection).getResponseCode(); } else { return 0; } } /* * (non-Javadoc) * * @see org.xamjwg.clientlet.ClientletResponse#getResponseMessage() */ public String getResponseMessage() throws IOException { if (this.connection instanceof HttpURLConnection) { return ((HttpURLConnection) this.connection).getResponseMessage(); } else { return ""; } } @Override public String toString() { return "ClientletResponseImpl[url=" + this.responseURL + ",method=" + this.getLastRequestMethod() + ",mimeType=" + this.getMimeType() + ",fromCache=" + this.isFromCache() + ",requestType=" + this.requestType + "]"; } public Object getPersistentCachedObject(final ClassLoader classLoader) { final CacheInfo cacheInfo = this.cacheInfo; return cacheInfo == null ? null : cacheInfo.getPersistentObject(classLoader); } public Object getTransientCachedObject() { final CacheInfo cacheInfo = this.cacheInfo; return cacheInfo == null ? null : cacheInfo.getTransientObject(); } /* Commented because nothing is using it. And it returns null for a primitive return type! public int getTransientCachedObjectSize() { final CacheInfo cacheInfo = this.cacheInfo; return cacheInfo == null ? null : cacheInfo.getTransientObjectSize(); }*/ private Serializable newPeristentCachedObject; private Object newTransientCachedObject; private int newTransientObjectSize; public void setNewPersistentCachedObject(final Serializable object) { this.newPeristentCachedObject = object; } public void setNewTransientCachedObject(final Object object, final int approxSize) { this.newTransientCachedObject = object; this.newTransientObjectSize = approxSize; } public Serializable getNewPersistentCachedObject() { return newPeristentCachedObject; } public Object getNewTransientCachedObject() { return newTransientCachedObject; } public int getNewTransientObjectSize() { return newTransientObjectSize; } public java.util.Date getDate() { final String dateText = this.connection.getHeaderField("Date"); if (dateText == null) { return null; } try { return Urls.PATTERN_RFC1123.parse(dateText); } catch (final java.text.ParseException pe) { logger.warning("getDate(): Bad date '" + dateText + "' from " + this.getResponseURL() + "."); return null; } } public RequestType getRequestType() { return this.requestType; } }