package aQute.bnd.service.url; import java.io.Closeable; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URI; import java.net.URLConnection; import java.util.Date; import java.util.regex.Matcher; import java.util.regex.Pattern; import aQute.bnd.http.HttpRequestException; import aQute.lib.io.IO; /** * Represents a data stream that has a tag associated with it; the primary * use-case is an HTTP response stream with an ETag header. * * @author Neil Bartlett */ public class TaggedData implements Closeable { private final URLConnection con; private final int responseCode; private final String etag; private final InputStream in; private final URI url; private final File file; private final String message; @Deprecated public TaggedData(String tag, InputStream inputStream, int responseCode, long modified, URI url) { throw new RuntimeException(); } @Deprecated public TaggedData(String tag, InputStream inputStream, int responseCode) { throw new RuntimeException(); } @Deprecated public TaggedData(String tag, InputStream inputStream) { throw new RuntimeException(); } public TaggedData(URLConnection con, InputStream in) throws Exception { this(con, in, null); } public TaggedData(URLConnection con, InputStream in, File file) throws Exception { this.con = con; this.responseCode = con instanceof HttpURLConnection ? ((HttpURLConnection) con).getResponseCode() : (in != null ? 200 : -1); this.in = in == null && con != null && (responseCode / 100 == 2) ? con.getInputStream() : in; this.file = file; this.etag = con.getHeaderField("ETag"); this.url = con.getURL().toURI(); this.message = getMessage(con); } private String getMessage(URLConnection con) { try { if (con == null || !(con instanceof HttpURLConnection)) return null; HttpURLConnection h = (HttpURLConnection) con; if (h.getResponseCode() / 100 < 4) return null; StringBuffer sb = new StringBuffer(); try { InputStream in = con.getInputStream(); if (in != null) sb.append(IO.collect(in)); } catch (Exception e) { // ignore } try { InputStream errorStream = h.getErrorStream(); if (errorStream != null) sb.append(IO.collect(errorStream)); } catch (Exception e) { // ignore } return cleanHtml(sb); } catch (Exception e) { return null; } } final static Pattern HTML_TAGS_P = Pattern.compile("<!--.*-->|<[^>]+>"); final static Pattern NEWLINES_P = Pattern.compile("(\\s*\n\r?\\s*)+"); final static Pattern ENTITIES_P = Pattern.compile("&(#(?<nr>[0-9]+))|(?<name>[a-z]+);", Pattern.CASE_INSENSITIVE); private String cleanHtml(CharSequence sb) { sb = HTML_TAGS_P.matcher(sb).replaceAll(""); sb = NEWLINES_P.matcher(sb).replaceAll("\n"); StringBuffer x = new StringBuffer(); Matcher m = ENTITIES_P.matcher(sb); while (m.find()) { if (m.group("nr") != null) { char c = (char) Integer.parseInt(m.group("nr")); m.appendReplacement(x, ""); x.append(c); } else { m.appendReplacement(x, entity(m.group("name"))); } } m.appendTail(x); return x.toString(); } private String entity(String name) { switch (name) { case "nbsp" : return "\u00A0"; case "lt" : return "<"; case "gt" : return "<"; case "amp" : return "&"; case "cent" : return "¢"; case "pound" : return "£"; case "euro" : return "€"; case "copy" : return "©"; case "reg" : return "®"; case "quot" : return "\""; case "apos" : return "'"; case "yen" : return "¥"; case "sect" : return "§"; case "not" : return "¬"; case "para" : return "¶"; case "curren" : return "¤"; default : return "&" + name + ";"; } } public TaggedData(URI url, int responseCode, File file) throws Exception { this.file = file; this.con = null; this.in = null; this.etag = ""; this.responseCode = responseCode; this.url = url; this.message = null; } /** * Returns the ETag for the retrieved resource, or {@code null} if the ETag * was not provided by the server. */ public String getTag() { return etag; } /** * Returns the input stream containing the resource data. * * @throws IOException */ public InputStream getInputStream() throws IOException { return in; } public int getResponseCode() { return responseCode; } public long getModified() { if (con != null) return con.getLastModified(); return -1; } public boolean hasPayload() throws IOException { return in != null; } public URI getUrl() { return url; } public URLConnection getConnection() { return con; } @Override public String toString() { return "TaggedData [tag=" + getTag() + ", code=" + getResponseCode() + ", modified=" + new Date(getModified()) + ", url=" + getUrl() + ", state=" + getState() + (message == null ? "" : ", msg=" + message) + "]"; } public boolean isOk() { return getResponseCode() / 100 == 2; } public boolean isNotModified() { return responseCode == HttpURLConnection.HTTP_NOT_MODIFIED; } public void throwIt() { throw new HttpRequestException(this); } public State getState() { if (isNotFound()) return State.NOT_FOUND; if (isNotModified()) return State.UNMODIFIED; if (isOk()) return State.UPDATED; return State.OTHER; } public boolean isNotFound() { return responseCode == 404; } public File getFile() { return file; } @Override public void close() throws IOException { IO.close(getInputStream()); } }