package org.lobobrowser.protocol.data; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.net.URLConnection; import java.net.URLDecoder; import java.util.Base64; import java.util.HashMap; /** * http://www.ietf.org/rfc/rfc2397.txt * * * dataurl := "data:" [ mediatype ] [ ";base64" ] "," data mediatype := [ type * "/" subtype ] *( ";" parameter ) data := *urlchar parameter := attribute "=" * value * * * @author toenz * */ public class DataURLConnection extends URLConnection { private final HashMap<String, String> headerMap = new HashMap<>(); private byte[] content = new byte[0]; protected DataURLConnection(final URL url) { super(url); loadHeaderMap(); } @Override public void connect() throws IOException { } private void loadHeaderMap() { final String UTF8 = "UTF-8"; this.headerMap.clear(); final String path = getURL().getPath(); int index2 = path.indexOf(","); if (index2 == -1) { index2 = path.lastIndexOf(";"); } String value = path.substring(index2 + 1).trim(); String mediatype; if (index2 == -1) { mediatype = "text/plain;charset=US-ASCII"; } else { mediatype = path.substring(0, index2).trim(); } boolean base64 = false; String[] split = mediatype.split("[;]"); if (split.length == 0) { split = new String[] {"text/plain"}; } else if (split[0].equals("")) { split[0] = "text/plain"; } this.headerMap.put("content-type", split[0]); try { for (int i = 1; i < split.length; i++) { if (split[i].contains("=")) { final int index = split[i].indexOf("="); final String attr = split[i].substring(0, index); final String v = split[i].substring(index + 1); this.headerMap.put(attr, java.net.URLDecoder.decode(v, UTF8)); } else if (split[i].equalsIgnoreCase("base64")) { base64 = true; } } String charset = this.getHeaderField("charset"); if (charset == null) { charset = UTF8; } value = removeSpaceCharacters(value); if (base64) { try { this.content = Base64.getDecoder().decode(value); } catch (final IllegalArgumentException iae) { // TODO: Fix for GH #15, but need to verify if this is specified by a standard value = URLDecoder.decode(value, "UTF-8"); this.content = Base64.getDecoder().decode(value); } } else { this.content = decodeUrl(value.toCharArray()); } } catch (final IOException e) { e.printStackTrace(); } } private static String removeSpaceCharacters(String value) { value = value.replace("\n", ""); value = value.replace("\r", ""); value = value.replace(" ", ""); value = value.replace("\t", ""); return value; } @Override public int getContentLength() { return content.length; } @Override public String getHeaderField(final int n) { // TODO: Looks highly inefficient to convert the keyset to array every time! return headerMap.get(headerMap.keySet().toArray()[n]); } @Override public String getHeaderField(final String name) { return headerMap.get(name); } @Override public InputStream getInputStream() throws IOException { return new ByteArrayInputStream(content); } public static final byte[] decodeUrl(final char[] chars) { final char ESCAPE_CHAR = '%'; if (chars == null) { return null; } final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); for (int i = 0; i < chars.length; i++) { final int b = chars[i]; if (b == '+') { buffer.write(' '); } else if (b == ESCAPE_CHAR) { try { final int u = digit16(chars[++i]); final int l = digit16(chars[++i]); buffer.write((char) ((u << 4) + l)); } catch (final ArrayIndexOutOfBoundsException e) { throw new RuntimeException("Invalid URL encoding: ", e); } } else { buffer.write(b); } } return buffer.toByteArray(); } private static int digit16(final char c) { if ((c >= 'A') && (c <= 'Z')) { return (10 + c) - 'A'; } else if ((c >= 'a') && (c <= 'z')) { return (10 + c) - 'a'; } else { return c - '0'; } } }