Added Android code
[wl-app.git] / Android / r2-streamer / r2-server / src / main / java / org / readium / r2_streamer / server / handler / ResourceHandler.java
1 package org.readium.r2_streamer.server.handler;
2
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;
9
10 import java.io.IOException;
11 import java.io.InputStream;
12 import java.util.Map;
13
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;
22
23 import static fi.iki.elonen.NanoHTTPD.MIME_PLAINTEXT;
24
25 /**
26  * Created by Shrikant Badwaik on 10-Feb-17.
27  */
28
29 public class ResourceHandler extends DefaultHandler {
30     private static final String TAG = "ResourceHandler";
31
32     public ResourceHandler() {
33     }
34
35     @Override
36     public String getMimeType() {
37         return null;
38     }
39
40     private final String[] fonts = {".woff", ".ttf", ".obf", ".woff2", ".eot", ".otf"};
41
42     @Override
43     public String getText() {
44         return ResponseStatus.FAILURE_RESPONSE;
45     }
46
47     @Override
48     public IStatus getStatus() {
49         return Status.OK;
50     }
51
52     @Override
53     public Response get(UriResource uriResource, Map<String, String> urlParams, IHTTPSession session) {
54         Method method = session.getMethod();
55         String uri = session.getUri();
56
57         System.out.println(TAG + " Method: " + method + ", Url: " + uri);
58
59         try {
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();
66
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);
71             }
72
73             // ********************
74             //  FONT DEOBFUSCATION
75             // ********************
76             if (isFontFile(filePath)) { // Check if the incoming request for the font file is encrypted
77                 Encryption encryption = Encryption.getEncryptionFormFontFilePath(
78                         filePath,
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()),
85                                     encryption),
86                             mimeType);
87                 }
88             }
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);
93         }
94     }
95
96     private Response serveResponse(IHTTPSession session, InputStream inputStream, String mimeType) {
97         Response response;
98         String rangeRequest = session.getHeaders().get("range");
99
100         try {
101             // Calculate etag
102             String etag = Integer.toHexString(inputStream.hashCode());
103
104             // Support skipping:
105             long startFrom = 0;
106             long endAt = -1;
107             if (rangeRequest != null) {
108                 if (rangeRequest.startsWith("bytes=")) {
109                     rangeRequest = rangeRequest.substring("bytes=".length());
110                     int minus = rangeRequest.indexOf('-');
111                     try {
112                         if (minus > 0) {
113                             startFrom = Long.parseLong(rangeRequest.substring(0, minus));
114                             endAt = Long.parseLong(rangeRequest.substring(minus + 1));
115                         }
116                     } catch (NumberFormatException ignored) {
117                     }
118                 }
119             }
120
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);
128                 } else {
129                     if (endAt < 0) {
130                         endAt = streamLength - 1;
131                     }
132                     long newLen = endAt - startFrom + 1;
133                     if (newLen < 0) {
134                         newLen = 0;
135                     }
136
137                     final long dataLen = newLen;
138                     inputStream.skip(startFrom);
139
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);
144                 }
145             } else {
146                 if (etag.equals(session.getHeaders().get("if-none-match")))
147                     response = createResponse(Response.Status.NOT_MODIFIED, mimeType, "");
148                 else {
149                     response = createResponse(Response.Status.OK, mimeType, inputStream);
150                     response.addHeader("Content-Length", "" + streamLength);
151                     response.addHeader("ETag", etag);
152                 }
153             }
154         } catch (IOException | NullPointerException ioe) {
155             response = getResponse("Forbidden: Reading file failed");
156         }
157
158         return (response == null) ? getResponse("Error 404: File not found") : response;
159     }
160
161     private Response createResponse(Status status, String mimeType, InputStream message) {
162         Response response = NanoHTTPD.newChunkedResponse(status, mimeType, message);
163         response.addHeader("Accept-Ranges", "bytes");
164         return response;
165     }
166
167     private Response createResponse(Status status, String mimeType, String message) {
168         Response response = NanoHTTPD.newFixedLengthResponse(status, mimeType, message);
169         response.addHeader("Accept-Ranges", "bytes");
170         return response;
171     }
172
173     private Response getResponse(String message) {
174         return createResponse(Response.Status.OK, "text/plain", message);
175     }
176
177     private boolean isFontFile(String file) {
178         for (String font : fonts) {
179             if (file.endsWith(font)) {
180                 return true;
181             }
182         }
183         return false;
184     }
185 }