Ranides Atterwim 4 лет назад
Родитель
Сommit
1e4f3748c0

+ 12 - 2
assira.core/src/main/java/net/ranides/assira/io/uri/URIHandle.java

@@ -19,6 +19,8 @@ import java.nio.file.Path;
 import java.time.Instant;
 import java.util.Set;
 import net.ranides.assira.collection.query.CQuery;
+import net.ranides.assira.text.IOStrings;
+import net.ranides.assira.text.StringUtils;
 
 /**
  *
@@ -71,13 +73,21 @@ public interface URIHandle extends Closeable {
     OutputStream ostream() throws IOException;
     
     Reader reader() throws IOException;
-    
+
     Reader reader(Charset cs) throws IOException;
     
     Writer writer() throws IOException;
     
     Writer writer(Charset cs) throws IOException;
-    
+
+    default String content() throws IOException {
+        return IOStrings.read(reader());
+    }
+
+    default String content(Charset cs) throws IOException {
+        return IOStrings.read(reader(cs));
+    }
+
     void delete() throws IOException;
     
     void create() throws IOException;

+ 188 - 0
assira.core/src/main/java/net/ranides/assira/io/uri/impl/CDataHandler.java

@@ -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);
+        }
+    }
+    
+}

+ 29 - 0
assira.core/src/test/java/net/ranides/assira/io/uri/CDataHandlerTest.java

@@ -0,0 +1,29 @@
+package net.ranides.assira.io.uri;
+
+import org.junit.Test;
+
+import net.ranides.assira.io.uri.impl.CDataHandler;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+
+import static org.junit.Assert.*;
+
+public class CDataHandlerTest {
+
+    @Test
+    public void testText() throws IOException, URISyntaxException {
+        URIHandle handle1 = new CDataHandler().resolve("data:text/plain;charset=UTF-8;page=21,the%20data:1234,5678");
+        URIHandle handle2 = new CDataHandler().resolve("data:text/plain,hello%20world");
+        URIHandle handle3 = new CDataHandler().resolve("data:text/plain;charset=UTF-8;base64,xbpyZWJpxJkgbmEgxYLEhWNlIHNrYWN6ZSBwbyDFgsSFY2U=");
+        URIHandle handle4 = new CDataHandler().resolve("data:text/plain;base64,xbpyZWJpxJkgbmEgxYLEhWNlIHNrYWN6ZSBwbyDFgsSFY2U=");
+        URIHandle handle5 = new CDataHandler().resolve("data:text/plain;charset=iso-8859-7,%be%20%f0%20%ee");
+
+
+        assertEquals("the data:1234,5678", handle1.content());
+        assertEquals("hello world", handle2.content());
+        assertEquals("źrebię na łące skacze po łące", handle3.content());
+        assertEquals("źrebię na łące skacze po łące", handle4.content());
+        assertEquals("Ύ π ξ", handle5.content());
+    }
+}