|
|
@@ -0,0 +1,188 @@
|
|
|
+/*
|
|
|
+ * @author Ranides Atterwim {@literal <ranides@gmail.com>}
|
|
|
+ * @copyright Ranides Atterwim
|
|
|
+ * @license WTFPL
|
|
|
+ * @url http://ranides.net/projects/assira
|
|
|
+ */
|
|
|
+package net.ranides.assira.io.uri.impl;
|
|
|
+
|
|
|
+import net.ranides.assira.collection.sets.MaskSet;
|
|
|
+import net.ranides.assira.generic.ValueUtils;
|
|
|
+import net.ranides.assira.io.IOHandle;
|
|
|
+import net.ranides.assira.io.IOUtils;
|
|
|
+import net.ranides.assira.io.uri.URIFlags;
|
|
|
+import net.ranides.assira.io.uri.URIHandle;
|
|
|
+import net.ranides.assira.io.uri.URIHandler;
|
|
|
+import net.ranides.assira.io.uri.URIResolver;
|
|
|
+import net.ranides.assira.text.Charsets;
|
|
|
+
|
|
|
+import java.io.ByteArrayInputStream;
|
|
|
+import java.io.IOException;
|
|
|
+import java.io.InputStream;
|
|
|
+import java.io.Reader;
|
|
|
+import java.io.StringReader;
|
|
|
+import java.io.UncheckedIOException;
|
|
|
+import java.io.UnsupportedEncodingException;
|
|
|
+import java.net.URI;
|
|
|
+import java.net.URISyntaxException;
|
|
|
+import java.net.URLDecoder;
|
|
|
+import java.nio.charset.Charset;
|
|
|
+import java.util.Arrays;
|
|
|
+import java.util.Base64;
|
|
|
+import java.util.Collection;
|
|
|
+import java.util.Set;
|
|
|
+import java.util.regex.Matcher;
|
|
|
+import java.util.regex.Pattern;
|
|
|
+
|
|
|
+/**
|
|
|
+ * This URIHandler loads files from URI data scheme.
|
|
|
+ *
|
|
|
+ * Supported schemes: data
|
|
|
+ *
|
|
|
+ *
|
|
|
+ * Please note:
|
|
|
+ * As every URIHandler, it should not be constructed directly, but connected with some instance of URIResolver.
|
|
|
+ *
|
|
|
+ * @author Ranides Atterwim {@literal <ranides@gmail.com>}
|
|
|
+ */
|
|
|
+public class CDataHandler implements URIHandler {
|
|
|
+
|
|
|
+ static final MaskSet<URIFlags> TXT_FLAGS = URIFlags.constant(
|
|
|
+ URIFlags.READABLE,
|
|
|
+ URIFlags.EXISTS,
|
|
|
+ URIFlags.FILE,
|
|
|
+ URIFlags.SIZE,
|
|
|
+ URIFlags.SEEK,
|
|
|
+ URIFlags.TEXT
|
|
|
+ );
|
|
|
+
|
|
|
+ static final MaskSet<URIFlags> BIN_FLAGS = URIFlags.constant(
|
|
|
+ URIFlags.READABLE,
|
|
|
+ URIFlags.EXISTS,
|
|
|
+ URIFlags.FILE,
|
|
|
+ URIFlags.SIZE,
|
|
|
+ URIFlags.SEEK,
|
|
|
+ URIFlags.BINARY
|
|
|
+ );
|
|
|
+
|
|
|
+ static final Pattern RE_DATA_SCHEME = Pattern.compile("^([^;,]+)?((?:;(?:[^;,]+))*?)(;base64)?,(.*)$");
|
|
|
+
|
|
|
+ private final URIResolver resolver;
|
|
|
+
|
|
|
+ public CDataHandler() {
|
|
|
+ this.resolver = URIResolver.DEFAULT;
|
|
|
+ }
|
|
|
+
|
|
|
+ public CDataHandler(URIResolver resolver) {
|
|
|
+ this.resolver = resolver;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public URIHandle resolve(URI uri) throws IOException {
|
|
|
+ return new CDataHandle(uri);
|
|
|
+ }
|
|
|
+
|
|
|
+ public URIHandle resolve(String uri) throws IOException, URISyntaxException {
|
|
|
+ return new CDataHandle(new URI(uri));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Collection<String> schemes() {
|
|
|
+ return Arrays.asList("data");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * dataurl := "data:" [ mediatype ] [ ";base64" ] "," data
|
|
|
+ * mediatype := [ type "/" subtype ] *( ";" parameter )
|
|
|
+ * data := *urlchar
|
|
|
+ * parameter := attribute "=" value
|
|
|
+ */
|
|
|
+ private class CDataHandle extends CHandle {
|
|
|
+
|
|
|
+ public final URI uri;
|
|
|
+ public final String mime;
|
|
|
+ public final boolean isText;
|
|
|
+ public final boolean isBase64;
|
|
|
+ public final Charset charset;
|
|
|
+ public final byte[] data;
|
|
|
+ public final String text;
|
|
|
+
|
|
|
+ public CDataHandle(URI uri) throws IOException {
|
|
|
+ Matcher hit = RE_DATA_SCHEME.matcher(uri.getRawSchemeSpecificPart());
|
|
|
+ if(!hit.matches()) {
|
|
|
+ throw new IOException("Invalid data URI: " + uri);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.uri = uri;
|
|
|
+ this.mime = ValueUtils.or(hit.group(1), "text/plain");
|
|
|
+ this.charset = getCharset(hit.group(2));
|
|
|
+ this.isText = mime.startsWith("text/");
|
|
|
+ this.isBase64 = hit.group(3) != null;
|
|
|
+
|
|
|
+ String body = hit.group(4);
|
|
|
+
|
|
|
+ if(!isBase64) {
|
|
|
+ text = URLDecoder.decode(body, charset.name());
|
|
|
+ data = text.getBytes(charset);
|
|
|
+ } else {
|
|
|
+ data = Base64.getDecoder().decode(body);
|
|
|
+ text = new String(data, charset);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private Charset getCharset(String x) {
|
|
|
+ Matcher hit = Pattern.compile(";[ ]*charset=([^; ]+)").matcher(x);
|
|
|
+ if(hit.find()) {
|
|
|
+ return Charset.forName(hit.group(1));
|
|
|
+ } else {
|
|
|
+ return Charsets.UTF8;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public URIResolver resolver() {
|
|
|
+ return CDataHandler.this.resolver;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean exists() {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Charset charset() {
|
|
|
+ return charset;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public URI uri() {
|
|
|
+ return uri;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String scheme() {
|
|
|
+ return "data";
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Set<URIFlags> flags() {
|
|
|
+ return isText ? TXT_FLAGS : BIN_FLAGS;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public long size() throws IOException {
|
|
|
+ return data.length;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public InputStream istream() throws IOException {
|
|
|
+ return new ByteArrayInputStream(data);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Reader reader() throws IOException {
|
|
|
+ return new StringReader(text);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+}
|