1 package org.readium.r2_streamer.server.handler;
3 import org.readium.r2_streamer.fetcher.EpubFetcher;
4 import org.readium.r2_streamer.fetcher.EpubFetcherException;
5 import org.readium.r2_streamer.model.publication.Encryption;
6 import org.readium.r2_streamer.model.publication.link.Link;
7 import org.readium.r2_streamer.parser.EncryptionDecoder;
8 import org.readium.r2_streamer.server.ResponseStatus;
10 import java.io.IOException;
11 import java.io.InputStream;
14 import fi.iki.elonen.NanoHTTPD;
15 import fi.iki.elonen.NanoHTTPD.IHTTPSession;
16 import fi.iki.elonen.NanoHTTPD.Method;
17 import fi.iki.elonen.NanoHTTPD.Response;
18 import fi.iki.elonen.NanoHTTPD.Response.IStatus;
19 import fi.iki.elonen.NanoHTTPD.Response.Status;
20 import fi.iki.elonen.router.RouterNanoHTTPD.DefaultHandler;
21 import fi.iki.elonen.router.RouterNanoHTTPD.UriResource;
23 import static fi.iki.elonen.NanoHTTPD.MIME_PLAINTEXT;
26 * Created by Shrikant Badwaik on 10-Feb-17.
29 public class ResourceHandler extends DefaultHandler {
30 private static final String TAG = "ResourceHandler";
32 public ResourceHandler() {
36 public String getMimeType() {
40 private final String[] fonts = {".woff", ".ttf", ".obf", ".woff2", ".eot", ".otf"};
43 public String getText() {
44 return ResponseStatus.FAILURE_RESPONSE;
48 public IStatus getStatus() {
53 public Response get(UriResource uriResource, Map<String, String> urlParams, IHTTPSession session) {
54 Method method = session.getMethod();
55 String uri = session.getUri();
57 System.out.println(TAG + " Method: " + method + ", Url: " + uri);
60 EpubFetcher fetcher = uriResource.initParameter(EpubFetcher.class);
61 int offset = uri.indexOf("/", 0);
62 int startIndex = uri.indexOf("/", offset + 1);
63 String filePath = uri.substring(startIndex + 1);
64 Link link = fetcher.publication.getResourceMimeType(filePath);
65 String mimeType = link.getTypeLink();
67 // If the content is of type html return the response this is done to
68 // skip the check for following font deobfuscation check
69 if (mimeType.equals("application/xhtml+xml")) {
70 return serveResponse(session, fetcher.getDataInputStream(filePath), mimeType);
73 // ********************
75 // ********************
76 if (isFontFile(filePath)) { // Check if the incoming request for the font file is encrypted
77 Encryption encryption = Encryption.getEncryptionFormFontFilePath(
79 fetcher.publication.encryptions);
80 if (encryption != null) { // Decode the font file if encryption exists
81 return serveResponse(session,
82 EncryptionDecoder.decode(
83 fetcher.publication.metadata.identifier,
84 fetcher.getDataInputStream(encryption.getProfile()),
89 return serveResponse(session, fetcher.getDataInputStream(filePath), mimeType);
90 } catch (EpubFetcherException e) {
91 System.out.println(TAG + " EpubFetcherException " + e.toString());
92 return NanoHTTPD.newFixedLengthResponse(Status.INTERNAL_ERROR, getMimeType(), ResponseStatus.FAILURE_RESPONSE);
96 private Response serveResponse(IHTTPSession session, InputStream inputStream, String mimeType) {
98 String rangeRequest = session.getHeaders().get("range");
102 String etag = Integer.toHexString(inputStream.hashCode());
107 if (rangeRequest != null) {
108 if (rangeRequest.startsWith("bytes=")) {
109 rangeRequest = rangeRequest.substring("bytes=".length());
110 int minus = rangeRequest.indexOf('-');
113 startFrom = Long.parseLong(rangeRequest.substring(0, minus));
114 endAt = Long.parseLong(rangeRequest.substring(minus + 1));
116 } catch (NumberFormatException ignored) {
121 // Change return code and add Content-Range header when skipping is requested
122 long streamLength = inputStream.available();
123 if (rangeRequest != null && startFrom >= 0) {
124 if (startFrom >= streamLength) {
125 response = createResponse(Response.Status.RANGE_NOT_SATISFIABLE, MIME_PLAINTEXT, "");
126 response.addHeader("Content-Range", "bytes 0-0/" + streamLength);
127 response.addHeader("ETag", etag);
130 endAt = streamLength - 1;
132 long newLen = endAt - startFrom + 1;
137 final long dataLen = newLen;
138 inputStream.skip(startFrom);
140 response = createResponse(Response.Status.PARTIAL_CONTENT, mimeType, inputStream);
141 response.addHeader("Content-Length", "" + dataLen);
142 response.addHeader("Content-Range", "bytes " + startFrom + "-" + endAt + "/" + streamLength);
143 response.addHeader("ETag", etag);
146 if (etag.equals(session.getHeaders().get("if-none-match")))
147 response = createResponse(Response.Status.NOT_MODIFIED, mimeType, "");
149 response = createResponse(Response.Status.OK, mimeType, inputStream);
150 response.addHeader("Content-Length", "" + streamLength);
151 response.addHeader("ETag", etag);
154 } catch (IOException | NullPointerException ioe) {
155 response = getResponse("Forbidden: Reading file failed");
158 return (response == null) ? getResponse("Error 404: File not found") : response;
161 private Response createResponse(Status status, String mimeType, InputStream message) {
162 Response response = NanoHTTPD.newChunkedResponse(status, mimeType, message);
163 response.addHeader("Accept-Ranges", "bytes");
167 private Response createResponse(Status status, String mimeType, String message) {
168 Response response = NanoHTTPD.newFixedLengthResponse(status, mimeType, message);
169 response.addHeader("Accept-Ranges", "bytes");
173 private Response getResponse(String message) {
174 return createResponse(Response.Status.OK, "text/plain", message);
177 private boolean isFontFile(String file) {
178 for (String font : fonts) {
179 if (file.endsWith(font)) {