Added Android code
[wl-app.git] / Android / r2-streamer / r2-server / src / main / java / org / readium / r2_streamer / server / handler / ResourceHandler.java
diff --git a/Android/r2-streamer/r2-server/src/main/java/org/readium/r2_streamer/server/handler/ResourceHandler.java b/Android/r2-streamer/r2-server/src/main/java/org/readium/r2_streamer/server/handler/ResourceHandler.java
new file mode 100755 (executable)
index 0000000..b697cbe
--- /dev/null
@@ -0,0 +1,185 @@
+package org.readium.r2_streamer.server.handler;
+
+import org.readium.r2_streamer.fetcher.EpubFetcher;
+import org.readium.r2_streamer.fetcher.EpubFetcherException;
+import org.readium.r2_streamer.model.publication.Encryption;
+import org.readium.r2_streamer.model.publication.link.Link;
+import org.readium.r2_streamer.parser.EncryptionDecoder;
+import org.readium.r2_streamer.server.ResponseStatus;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+
+import fi.iki.elonen.NanoHTTPD;
+import fi.iki.elonen.NanoHTTPD.IHTTPSession;
+import fi.iki.elonen.NanoHTTPD.Method;
+import fi.iki.elonen.NanoHTTPD.Response;
+import fi.iki.elonen.NanoHTTPD.Response.IStatus;
+import fi.iki.elonen.NanoHTTPD.Response.Status;
+import fi.iki.elonen.router.RouterNanoHTTPD.DefaultHandler;
+import fi.iki.elonen.router.RouterNanoHTTPD.UriResource;
+
+import static fi.iki.elonen.NanoHTTPD.MIME_PLAINTEXT;
+
+/**
+ * Created by Shrikant Badwaik on 10-Feb-17.
+ */
+
+public class ResourceHandler extends DefaultHandler {
+    private static final String TAG = "ResourceHandler";
+
+    public ResourceHandler() {
+    }
+
+    @Override
+    public String getMimeType() {
+        return null;
+    }
+
+    private final String[] fonts = {".woff", ".ttf", ".obf", ".woff2", ".eot", ".otf"};
+
+    @Override
+    public String getText() {
+        return ResponseStatus.FAILURE_RESPONSE;
+    }
+
+    @Override
+    public IStatus getStatus() {
+        return Status.OK;
+    }
+
+    @Override
+    public Response get(UriResource uriResource, Map<String, String> urlParams, IHTTPSession session) {
+        Method method = session.getMethod();
+        String uri = session.getUri();
+
+        System.out.println(TAG + " Method: " + method + ", Url: " + uri);
+
+        try {
+            EpubFetcher fetcher = uriResource.initParameter(EpubFetcher.class);
+            int offset = uri.indexOf("/", 0);
+            int startIndex = uri.indexOf("/", offset + 1);
+            String filePath = uri.substring(startIndex + 1);
+            Link link = fetcher.publication.getResourceMimeType(filePath);
+            String mimeType = link.getTypeLink();
+
+            // If the content is of type html return the response this is done to
+            // skip the check for following font deobfuscation check
+            if (mimeType.equals("application/xhtml+xml")) {
+                return serveResponse(session, fetcher.getDataInputStream(filePath), mimeType);
+            }
+
+            // ********************
+            //  FONT DEOBFUSCATION
+            // ********************
+            if (isFontFile(filePath)) { // Check if the incoming request for the font file is encrypted
+                Encryption encryption = Encryption.getEncryptionFormFontFilePath(
+                        filePath,
+                        fetcher.publication.encryptions);
+                if (encryption != null) { // Decode the font file if encryption exists
+                    return serveResponse(session,
+                            EncryptionDecoder.decode(
+                                    fetcher.publication.metadata.identifier,
+                                    fetcher.getDataInputStream(encryption.getProfile()),
+                                    encryption),
+                            mimeType);
+                }
+            }
+            return serveResponse(session, fetcher.getDataInputStream(filePath), mimeType);
+        } catch (EpubFetcherException e) {
+            System.out.println(TAG + " EpubFetcherException " + e.toString());
+            return NanoHTTPD.newFixedLengthResponse(Status.INTERNAL_ERROR, getMimeType(), ResponseStatus.FAILURE_RESPONSE);
+        }
+    }
+
+    private Response serveResponse(IHTTPSession session, InputStream inputStream, String mimeType) {
+        Response response;
+        String rangeRequest = session.getHeaders().get("range");
+
+        try {
+            // Calculate etag
+            String etag = Integer.toHexString(inputStream.hashCode());
+
+            // Support skipping:
+            long startFrom = 0;
+            long endAt = -1;
+            if (rangeRequest != null) {
+                if (rangeRequest.startsWith("bytes=")) {
+                    rangeRequest = rangeRequest.substring("bytes=".length());
+                    int minus = rangeRequest.indexOf('-');
+                    try {
+                        if (minus > 0) {
+                            startFrom = Long.parseLong(rangeRequest.substring(0, minus));
+                            endAt = Long.parseLong(rangeRequest.substring(minus + 1));
+                        }
+                    } catch (NumberFormatException ignored) {
+                    }
+                }
+            }
+
+            // Change return code and add Content-Range header when skipping is requested
+            long streamLength = inputStream.available();
+            if (rangeRequest != null && startFrom >= 0) {
+                if (startFrom >= streamLength) {
+                    response = createResponse(Response.Status.RANGE_NOT_SATISFIABLE, MIME_PLAINTEXT, "");
+                    response.addHeader("Content-Range", "bytes 0-0/" + streamLength);
+                    response.addHeader("ETag", etag);
+                } else {
+                    if (endAt < 0) {
+                        endAt = streamLength - 1;
+                    }
+                    long newLen = endAt - startFrom + 1;
+                    if (newLen < 0) {
+                        newLen = 0;
+                    }
+
+                    final long dataLen = newLen;
+                    inputStream.skip(startFrom);
+
+                    response = createResponse(Response.Status.PARTIAL_CONTENT, mimeType, inputStream);
+                    response.addHeader("Content-Length", "" + dataLen);
+                    response.addHeader("Content-Range", "bytes " + startFrom + "-" + endAt + "/" + streamLength);
+                    response.addHeader("ETag", etag);
+                }
+            } else {
+                if (etag.equals(session.getHeaders().get("if-none-match")))
+                    response = createResponse(Response.Status.NOT_MODIFIED, mimeType, "");
+                else {
+                    response = createResponse(Response.Status.OK, mimeType, inputStream);
+                    response.addHeader("Content-Length", "" + streamLength);
+                    response.addHeader("ETag", etag);
+                }
+            }
+        } catch (IOException | NullPointerException ioe) {
+            response = getResponse("Forbidden: Reading file failed");
+        }
+
+        return (response == null) ? getResponse("Error 404: File not found") : response;
+    }
+
+    private Response createResponse(Status status, String mimeType, InputStream message) {
+        Response response = NanoHTTPD.newChunkedResponse(status, mimeType, message);
+        response.addHeader("Accept-Ranges", "bytes");
+        return response;
+    }
+
+    private Response createResponse(Status status, String mimeType, String message) {
+        Response response = NanoHTTPD.newFixedLengthResponse(status, mimeType, message);
+        response.addHeader("Accept-Ranges", "bytes");
+        return response;
+    }
+
+    private Response getResponse(String message) {
+        return createResponse(Response.Status.OK, "text/plain", message);
+    }
+
+    private boolean isFontFile(String file) {
+        for (String font : fonts) {
+            if (file.endsWith(font)) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
\ No newline at end of file