dita-streamer-js
Version:
Readium 2 'streamer' for NodeJS (TypeScript)
293 lines • 11.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.serverAssets = void 0;
const debug_ = require("debug");
const express = require("express");
const mime = require("mime-types");
const path = require("path");
const zipHasEntry_1 = require("r2-shared-js/dist/es8-es2017/src/_utils/zipHasEntry");
const transformer_1 = require("r2-shared-js/dist/es8-es2017/src/transform/transformer");
const RangeUtils_1 = require("r2-utils-js/dist/es8-es2017/src/_utils/http/RangeUtils");
const BufferUtils_1 = require("r2-utils-js/dist/es8-es2017/src/_utils/stream/BufferUtils");
const request_ext_1 = require("./request-ext");
const debug = debug_("r2:streamer#http/server-assets");
function serverAssets(server, routerPathBase64) {
const routerAssets = express.Router({ strict: false });
routerAssets.get("/", async (req, res) => {
const reqparams = req.params;
if (!reqparams.pathBase64) {
reqparams.pathBase64 = req.pathBase64;
}
if (!reqparams.asset) {
reqparams.asset = req.asset;
}
if (!reqparams.lcpPass64) {
reqparams.lcpPass64 = req.lcpPass64;
}
const isShow = req.query.show;
const isHead = req.method.toLowerCase() === "head";
if (isHead) {
debug("HEAD !!!!!!!!!!!!!!!!!!!");
}
const pathBase64Str = Buffer.from(reqparams.pathBase64, "base64").toString("utf8");
let publication;
try {
publication = await server.loadOrGetCachedPublication(pathBase64Str);
}
catch (err) {
debug(err);
res.status(500).send("<html><body><p>Internal Server Error</p><p>"
+ err + "</p></body></html>");
return;
}
const zipInternal = publication.findFromInternal("zip");
if (!zipInternal) {
const err = "No publication zip!";
debug(err);
res.status(500).send("<html><body><p>Internal Server Error</p><p>"
+ err + "</p></body></html>");
return;
}
const zip = zipInternal.Value;
const pathInZip = reqparams.asset;
if (!zipHasEntry_1.zipHasEntry(zip, pathInZip, undefined)) {
const err = "Asset not in zip! " + pathInZip;
debug(err);
res.status(500).send("<html><body><p>Internal Server Error</p><p>"
+ err + "</p></body></html>");
return;
}
const isDivina = publication.Metadata && publication.Metadata.RDFType &&
(/http[s]?:\/\/schema\.org\/ComicStory$/.test(publication.Metadata.RDFType) ||
/http[s]?:\/\/schema\.org\/VisualNarrative$/.test(publication.Metadata.RDFType));
let link;
const findLinkRecursive = (relativePath, l) => {
if (l.Href === relativePath) {
return l;
}
let found;
if (l.Children) {
for (const child of l.Children) {
found = findLinkRecursive(relativePath, child);
if (found) {
return found;
}
}
}
if (l.Alternate) {
for (const alt of l.Alternate) {
found = findLinkRecursive(relativePath, alt);
if (found) {
return found;
}
}
}
return undefined;
};
if ((publication.Resources || publication.Spine || publication.Links)
&& pathInZip.indexOf("META-INF/") !== 0
&& !pathInZip.endsWith(".opf")) {
const relativePath = pathInZip;
if (publication.Resources) {
for (const l of publication.Resources) {
link = findLinkRecursive(relativePath, l);
if (link) {
break;
}
}
}
if (!link) {
if (publication.Spine) {
for (const l of publication.Spine) {
link = findLinkRecursive(relativePath, l);
if (link) {
break;
}
}
}
}
if (!link) {
if (publication.Links) {
for (const l of publication.Links) {
link = findLinkRecursive(relativePath, l);
if (link) {
break;
}
}
}
}
if (!link &&
!isDivina) {
const err = "Asset not declared in publication spine/resources!" + relativePath;
debug(err);
res.status(500).send("<html><body><p>Internal Server Error</p><p>"
+ err + "</p></body></html>");
return;
}
}
if (server.isSecured() && !link &&
(pathInZip.indexOf("META-INF/") === 0 || pathInZip.endsWith(".opf"))) {
res.status(200).send("<html><body></body></html>");
return;
}
let mediaType = mime.lookup(pathInZip);
if (link && link.TypeLink) {
mediaType = link.TypeLink;
}
const isText = (typeof mediaType === "string") && (mediaType.indexOf("text/") === 0 ||
mediaType.indexOf("application/xhtml") === 0 ||
mediaType.indexOf("application/xml") === 0 ||
mediaType.indexOf("application/json") === 0 ||
mediaType.indexOf("application/svg") === 0 ||
mediaType.indexOf("application/smil") === 0 ||
mediaType.indexOf("+json") > 0 ||
mediaType.indexOf("+smil") > 0 ||
mediaType.indexOf("+svg") > 0 ||
mediaType.indexOf("+xhtml") > 0 ||
mediaType.indexOf("+xml") > 0);
const isEncrypted = link && link.Properties && link.Properties.Encrypted;
const isPartialByteRangeRequest = ((req.headers && req.headers.range) ? true : false);
let partialByteBegin = 0;
let partialByteEnd = -1;
if (isPartialByteRangeRequest) {
debug(req.headers.range);
const ranges = RangeUtils_1.parseRangeHeader(req.headers.range);
if (ranges && ranges.length) {
if (ranges.length > 1) {
const err = "Too many HTTP ranges: " + req.headers.range;
debug(err);
res.status(416).send("<html><body><p>Internal Server Error</p><p>"
+ err + "</p></body></html>");
return;
}
partialByteBegin = ranges[0].begin;
partialByteEnd = ranges[0].end;
if (partialByteBegin < 0) {
partialByteBegin = 0;
}
}
debug(`${pathInZip} >> ${partialByteBegin}-${partialByteEnd}`);
}
let zipStream_;
try {
zipStream_ = isPartialByteRangeRequest && !isEncrypted ?
await zip.entryStreamRangePromise(pathInZip, partialByteBegin, partialByteEnd) :
await zip.entryStreamPromise(pathInZip);
}
catch (err) {
debug(err);
res.status(500).send("<html><body><p>Internal Server Error</p><p>"
+ err + "</p></body></html>");
return;
}
const doTransform = true;
const sessionInfo = req.query[request_ext_1.URL_PARAM_SESSION_INFO];
if (doTransform && link) {
const fullUrl = `${server.serverUrl()}${req.originalUrl}`;
let transformedStream;
try {
transformedStream = await transformer_1.Transformers.tryStream(publication, link, fullUrl, zipStream_, isPartialByteRangeRequest, partialByteBegin, partialByteEnd, sessionInfo);
}
catch (err) {
debug(err);
res.status(500).send("<html><body><p>Internal Server Error</p><p>"
+ err + "</p></body></html>");
return;
}
if (transformedStream) {
if (transformedStream !== zipStream_) {
debug("Asset transformed ok: " + link.Href);
}
zipStream_ = transformedStream;
}
else {
const err = "Transform fail (encryption scheme not supported?)";
debug(err);
res.status(500).send("<html><body><p>Internal Server Error</p><p>"
+ err + "</p></body></html>");
return;
}
}
if (isShow) {
let zipData;
try {
zipData = await BufferUtils_1.streamToBufferPromise(zipStream_.stream);
}
catch (err) {
debug(err);
res.status(500).send("<html><body><p>Internal Server Error</p><p>"
+ err + "</p></body></html>");
return;
}
if (zipData) {
debug("CHECK: " + zipStream_.length + " ==> " + zipData.length);
}
res.status(200).send("<html><body>" +
"<h1>" + path.basename(pathBase64Str) + "</h1>" +
"<h2>" + mediaType + "</h2>" +
((isText && zipData) ?
("<p><pre>" +
zipData.toString("utf8").replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'") +
"</pre></p>")
: "<p>BINARY</p>") + "</body></html>");
return;
}
server.setResponseCORS(res);
if (isPartialByteRangeRequest || isEncrypted) {
server.setResponseCacheHeaders(res, false);
}
else {
server.setResponseCacheHeaders(res, true);
}
if (mediaType) {
res.set("Content-Type", mediaType);
}
res.setHeader("Accept-Ranges", "bytes");
if (isPartialByteRangeRequest) {
if (partialByteEnd < 0) {
partialByteEnd = zipStream_.length - 1;
}
const partialByteLength = isPartialByteRangeRequest ?
partialByteEnd - partialByteBegin + 1 :
zipStream_.length;
res.setHeader("Content-Length", `${partialByteLength}`);
const rangeHeader = `bytes ${partialByteBegin}-${partialByteEnd}/${zipStream_.length}`;
res.setHeader("Content-Range", rangeHeader);
res.status(206);
}
else {
res.setHeader("Content-Length", `${zipStream_.length}`);
res.status(200);
}
if (isHead) {
res.end();
}
else {
zipStream_.stream
.on("error", function f() {
debug("ZIP ERROR " + pathInZip);
})
.pipe(res)
.on("error", function f() {
debug("RES ERROR " + pathInZip);
})
.on("close", function f() {
res.end();
});
}
});
routerPathBase64.param("asset", (req, _res, next, value, _name) => {
if (value) {
value = value.replace(/\/\/+/g, "/");
}
req.asset = value;
next();
});
routerPathBase64.use("/:" + request_ext_1._pathBase64 + "/:" + request_ext_1._asset + "(*)", routerAssets);
}
exports.serverAssets = serverAssets;
//# sourceMappingURL=server-assets.js.map