1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.serverAssets = void 0;
|
4 | const tslib_1 = require("tslib");
|
5 | const debug_ = require("debug");
|
6 | const express = require("express");
|
7 | const mime = require("mime-types");
|
8 | const path = require("path");
|
9 | const zipHasEntry_1 = require("r2-shared-js/dist/es7-es2016/src/_utils/zipHasEntry");
|
10 | const transformer_1 = require("r2-shared-js/dist/es7-es2016/src/transform/transformer");
|
11 | const RangeUtils_1 = require("r2-utils-js/dist/es7-es2016/src/_utils/http/RangeUtils");
|
12 | const BufferUtils_1 = require("r2-utils-js/dist/es7-es2016/src/_utils/stream/BufferUtils");
|
13 | const request_ext_1 = require("./request-ext");
|
14 | const debug = debug_("r2:streamer#http/server-assets");
|
15 | function serverAssets(server, routerPathBase64) {
|
16 | const routerAssets = express.Router({ strict: false });
|
17 | routerAssets.get("/", (req, res) => tslib_1.__awaiter(this, void 0, void 0, function* () {
|
18 | const reqparams = req.params;
|
19 | if (!reqparams.pathBase64) {
|
20 | reqparams.pathBase64 = req.pathBase64;
|
21 | }
|
22 | if (!reqparams.asset) {
|
23 | reqparams.asset = req.asset;
|
24 | }
|
25 | if (!reqparams.lcpPass64) {
|
26 | reqparams.lcpPass64 = req.lcpPass64;
|
27 | }
|
28 | const isShow = req.query.show;
|
29 | const isHead = req.method.toLowerCase() === "head";
|
30 | if (isHead) {
|
31 | debug("HEAD !!!!!!!!!!!!!!!!!!!");
|
32 | }
|
33 | const pathBase64Str = Buffer.from(reqparams.pathBase64, "base64").toString("utf8");
|
34 | let publication;
|
35 | try {
|
36 | publication = yield server.loadOrGetCachedPublication(pathBase64Str);
|
37 | }
|
38 | catch (err) {
|
39 | debug(err);
|
40 | res.status(500).send("<html><body><p>Internal Server Error</p><p>"
|
41 | + err + "</p></body></html>");
|
42 | return;
|
43 | }
|
44 | const zipInternal = publication.findFromInternal("zip");
|
45 | if (!zipInternal) {
|
46 | const err = "No publication zip!";
|
47 | debug(err);
|
48 | res.status(500).send("<html><body><p>Internal Server Error</p><p>"
|
49 | + err + "</p></body></html>");
|
50 | return;
|
51 | }
|
52 | const zip = zipInternal.Value;
|
53 | const pathInZip = reqparams.asset;
|
54 | if (!zipHasEntry_1.zipHasEntry(zip, pathInZip, undefined)) {
|
55 | const err = "Asset not in zip! " + pathInZip;
|
56 | debug(err);
|
57 | res.status(500).send("<html><body><p>Internal Server Error</p><p>"
|
58 | + err + "</p></body></html>");
|
59 | return;
|
60 | }
|
61 | const isDivina = publication.Metadata && publication.Metadata.RDFType &&
|
62 | (/http[s]?:\/\/schema\.org\/ComicStory$/.test(publication.Metadata.RDFType) ||
|
63 | /http[s]?:\/\/schema\.org\/VisualNarrative$/.test(publication.Metadata.RDFType));
|
64 | let link;
|
65 | const findLinkRecursive = (relativePath, l) => {
|
66 | if (l.Href === relativePath) {
|
67 | return l;
|
68 | }
|
69 | let found;
|
70 | if (l.Children) {
|
71 | for (const child of l.Children) {
|
72 | found = findLinkRecursive(relativePath, child);
|
73 | if (found) {
|
74 | return found;
|
75 | }
|
76 | }
|
77 | }
|
78 | if (l.Alternate) {
|
79 | for (const alt of l.Alternate) {
|
80 | found = findLinkRecursive(relativePath, alt);
|
81 | if (found) {
|
82 | return found;
|
83 | }
|
84 | }
|
85 | }
|
86 | return undefined;
|
87 | };
|
88 | if ((publication.Resources || publication.Spine || publication.Links)
|
89 | && pathInZip.indexOf("META-INF/") !== 0
|
90 | && !pathInZip.endsWith(".opf")) {
|
91 | const relativePath = pathInZip;
|
92 | if (publication.Resources) {
|
93 | for (const l of publication.Resources) {
|
94 | link = findLinkRecursive(relativePath, l);
|
95 | if (link) {
|
96 | break;
|
97 | }
|
98 | }
|
99 | }
|
100 | if (!link) {
|
101 | if (publication.Spine) {
|
102 | for (const l of publication.Spine) {
|
103 | link = findLinkRecursive(relativePath, l);
|
104 | if (link) {
|
105 | break;
|
106 | }
|
107 | }
|
108 | }
|
109 | }
|
110 | if (!link) {
|
111 | if (publication.Links) {
|
112 | for (const l of publication.Links) {
|
113 | link = findLinkRecursive(relativePath, l);
|
114 | if (link) {
|
115 | break;
|
116 | }
|
117 | }
|
118 | }
|
119 | }
|
120 | if (!link &&
|
121 | !isDivina) {
|
122 | const err = "Asset not declared in publication spine/resources!" + relativePath;
|
123 | debug(err);
|
124 | res.status(500).send("<html><body><p>Internal Server Error</p><p>"
|
125 | + err + "</p></body></html>");
|
126 | return;
|
127 | }
|
128 | }
|
129 | if (server.isSecured() && !link &&
|
130 | (pathInZip.indexOf("META-INF/") === 0 || pathInZip.endsWith(".opf"))) {
|
131 | res.status(200).send("<html><body></body></html>");
|
132 | return;
|
133 | }
|
134 | let mediaType = mime.lookup(pathInZip);
|
135 | if (link && link.TypeLink) {
|
136 | mediaType = link.TypeLink;
|
137 | }
|
138 | const isText = (typeof mediaType === "string") && (mediaType.indexOf("text/") === 0 ||
|
139 | mediaType.indexOf("application/xhtml") === 0 ||
|
140 | mediaType.indexOf("application/xml") === 0 ||
|
141 | mediaType.indexOf("application/json") === 0 ||
|
142 | mediaType.indexOf("application/svg") === 0 ||
|
143 | mediaType.indexOf("application/smil") === 0 ||
|
144 | mediaType.indexOf("+json") > 0 ||
|
145 | mediaType.indexOf("+smil") > 0 ||
|
146 | mediaType.indexOf("+svg") > 0 ||
|
147 | mediaType.indexOf("+xhtml") > 0 ||
|
148 | mediaType.indexOf("+xml") > 0);
|
149 | const isEncrypted = link && link.Properties && link.Properties.Encrypted;
|
150 | const isPartialByteRangeRequest = ((req.headers && req.headers.range) ? true : false);
|
151 | let partialByteBegin = 0;
|
152 | let partialByteEnd = -1;
|
153 | if (isPartialByteRangeRequest) {
|
154 | debug(req.headers.range);
|
155 | const ranges = RangeUtils_1.parseRangeHeader(req.headers.range);
|
156 | if (ranges && ranges.length) {
|
157 | if (ranges.length > 1) {
|
158 | const err = "Too many HTTP ranges: " + req.headers.range;
|
159 | debug(err);
|
160 | res.status(416).send("<html><body><p>Internal Server Error</p><p>"
|
161 | + err + "</p></body></html>");
|
162 | return;
|
163 | }
|
164 | partialByteBegin = ranges[0].begin;
|
165 | partialByteEnd = ranges[0].end;
|
166 | if (partialByteBegin < 0) {
|
167 | partialByteBegin = 0;
|
168 | }
|
169 | }
|
170 | debug(`${pathInZip} >> ${partialByteBegin}-${partialByteEnd}`);
|
171 | }
|
172 | let zipStream_;
|
173 | try {
|
174 | zipStream_ = isPartialByteRangeRequest && !isEncrypted ?
|
175 | yield zip.entryStreamRangePromise(pathInZip, partialByteBegin, partialByteEnd) :
|
176 | yield zip.entryStreamPromise(pathInZip);
|
177 | }
|
178 | catch (err) {
|
179 | debug(err);
|
180 | res.status(500).send("<html><body><p>Internal Server Error</p><p>"
|
181 | + err + "</p></body></html>");
|
182 | return;
|
183 | }
|
184 | const doTransform = true;
|
185 | const sessionInfo = req.query[request_ext_1.URL_PARAM_SESSION_INFO];
|
186 | if (doTransform && link) {
|
187 | const fullUrl = `${server.serverUrl()}${req.originalUrl}`;
|
188 | let transformedStream;
|
189 | try {
|
190 | transformedStream = yield transformer_1.Transformers.tryStream(publication, link, fullUrl, zipStream_, isPartialByteRangeRequest, partialByteBegin, partialByteEnd, sessionInfo);
|
191 | }
|
192 | catch (err) {
|
193 | debug(err);
|
194 | res.status(500).send("<html><body><p>Internal Server Error</p><p>"
|
195 | + err + "</p></body></html>");
|
196 | return;
|
197 | }
|
198 | if (transformedStream) {
|
199 | if (transformedStream !== zipStream_) {
|
200 | debug("Asset transformed ok: " + link.Href);
|
201 | }
|
202 | zipStream_ = transformedStream;
|
203 | }
|
204 | else {
|
205 | const err = "Transform fail (encryption scheme not supported?)";
|
206 | debug(err);
|
207 | res.status(500).send("<html><body><p>Internal Server Error</p><p>"
|
208 | + err + "</p></body></html>");
|
209 | return;
|
210 | }
|
211 | }
|
212 | if (isShow) {
|
213 | let zipData;
|
214 | try {
|
215 | zipData = yield BufferUtils_1.streamToBufferPromise(zipStream_.stream);
|
216 | }
|
217 | catch (err) {
|
218 | debug(err);
|
219 | res.status(500).send("<html><body><p>Internal Server Error</p><p>"
|
220 | + err + "</p></body></html>");
|
221 | return;
|
222 | }
|
223 | if (zipData) {
|
224 | debug("CHECK: " + zipStream_.length + " ==> " + zipData.length);
|
225 | }
|
226 | res.status(200).send("<html><body>" +
|
227 | "<h1>" + path.basename(pathBase64Str) + "</h1>" +
|
228 | "<h2>" + mediaType + "</h2>" +
|
229 | ((isText && zipData) ?
|
230 | ("<p><pre>" +
|
231 | zipData.toString("utf8").replace(/&/g, "&")
|
232 | .replace(/</g, "<")
|
233 | .replace(/>/g, ">")
|
234 | .replace(/"/g, """)
|
235 | .replace(/'/g, "'") +
|
236 | "</pre></p>")
|
237 | : "<p>BINARY</p>") + "</body></html>");
|
238 | return;
|
239 | }
|
240 | server.setResponseCORS(res);
|
241 | if (isPartialByteRangeRequest || isEncrypted) {
|
242 | server.setResponseCacheHeaders(res, false);
|
243 | }
|
244 | else {
|
245 | server.setResponseCacheHeaders(res, true);
|
246 | }
|
247 | if (mediaType) {
|
248 | res.set("Content-Type", mediaType);
|
249 | }
|
250 | res.setHeader("Accept-Ranges", "bytes");
|
251 | if (isPartialByteRangeRequest) {
|
252 | if (partialByteEnd < 0) {
|
253 | partialByteEnd = zipStream_.length - 1;
|
254 | }
|
255 | const partialByteLength = isPartialByteRangeRequest ?
|
256 | partialByteEnd - partialByteBegin + 1 :
|
257 | zipStream_.length;
|
258 | res.setHeader("Content-Length", `${partialByteLength}`);
|
259 | const rangeHeader = `bytes ${partialByteBegin}-${partialByteEnd}/${zipStream_.length}`;
|
260 | res.setHeader("Content-Range", rangeHeader);
|
261 | res.status(206);
|
262 | }
|
263 | else {
|
264 | res.setHeader("Content-Length", `${zipStream_.length}`);
|
265 | res.status(200);
|
266 | }
|
267 | if (isHead) {
|
268 | res.end();
|
269 | }
|
270 | else {
|
271 | zipStream_.stream
|
272 | .on("error", function f() {
|
273 | debug("ZIP ERROR " + pathInZip);
|
274 | })
|
275 | .pipe(res)
|
276 | .on("error", function f() {
|
277 | debug("RES ERROR " + pathInZip);
|
278 | })
|
279 | .on("close", function f() {
|
280 | res.end();
|
281 | });
|
282 | }
|
283 | }));
|
284 | routerPathBase64.param("asset", (req, _res, next, value, _name) => {
|
285 | if (value) {
|
286 | value = value.replace(/\/\/+/g, "/");
|
287 | }
|
288 | req.asset = value;
|
289 | next();
|
290 | });
|
291 | routerPathBase64.use("/:" + request_ext_1._pathBase64 + "/:" + request_ext_1._asset + "(*)", routerAssets);
|
292 | }
|
293 | exports.serverAssets = serverAssets;
|
294 |
|
\ | No newline at end of file |