UNPKG

r2-streamer-js

Version:

Readium 2 'streamer' for NodeJS (TypeScript)

521 lines 26.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Server = exports.MAX_PREFETCH_LINKS = void 0; var tslib_1 = require("tslib"); var child_process = require("child_process"); var debug_ = require("debug"); var express = require("express"); var fs = require("fs"); var http = require("http"); var https = require("https"); var path = require("path"); var tmp_1 = require("tmp"); var serializable_1 = require("r2-lcp-js/dist/es5/src/serializable"); var opds2_1 = require("r2-opds-js/dist/es5/src/opds/opds2/opds2"); var publication_1 = require("r2-shared-js/dist/es5/src/models/publication"); var publication_parser_1 = require("r2-shared-js/dist/es5/src/parser/publication-parser"); var UrlUtils_1 = require("r2-utils-js/dist/es5/src/_utils/http/UrlUtils"); var zipFactory_1 = require("r2-utils-js/dist/es5/src/_utils/zip/zipFactory"); var self_signed_1 = require("../utils/self-signed"); var server_assets_1 = require("./server-assets"); var server_lcp_lsd_show_1 = require("./server-lcp-lsd-show"); var server_manifestjson_1 = require("./server-manifestjson"); var server_mediaoverlays_1 = require("./server-mediaoverlays"); var server_opds_browse_v1_1 = require("./server-opds-browse-v1"); var server_opds_browse_v2_1 = require("./server-opds-browse-v2"); var server_opds_convert_v1_to_v2_1 = require("./server-opds-convert-v1-to-v2"); var server_opds_local_feed_1 = require("./server-opds-local-feed"); var server_pub_1 = require("./server-pub"); var server_root_1 = require("./server-root"); var server_secure_1 = require("./server-secure"); var server_url_1 = require("./server-url"); var server_version_1 = require("./server-version"); var debug = debug_("r2:streamer#http/server"); var isValidHexPassphraseHashSha256 = function (str) { if (str.length !== 64) { return false; } var isHex = true; for (var i = 0; i < str.length; i += 2) { var hexByte = str.substr(i, 2).toLowerCase(); if (!/^[0-9a-f][0-9a-f]$/.test(hexByte)) { isHex = false; break; } var parsedInt = parseInt(hexByte, 16); if (isNaN(parsedInt)) { isHex = false; break; } } return isHex; }; exports.MAX_PREFETCH_LINKS = 10; var Server = (function () { function Server(options) { this.lcpBeginToken = "*-"; this.lcpEndToken = "-*"; this.disableReaders = !!(options === null || options === void 0 ? void 0 : options.disableReaders); this.disableDecryption = !!(options === null || options === void 0 ? void 0 : options.disableDecryption); this.disableRemotePubUrl = !!(options === null || options === void 0 ? void 0 : options.disableRemotePubUrl); this.disableOPDS = !!(options === null || options === void 0 ? void 0 : options.disableOPDS); this.enableSignedExpiry = !!(options === null || options === void 0 ? void 0 : options.enableSignedExpiry); this.maxPrefetchLinks = (options === null || options === void 0 ? void 0 : options.maxPrefetchLinks) ? options.maxPrefetchLinks : exports.MAX_PREFETCH_LINKS; this.publications = []; this.pathPublicationMap = {}; this.publicationsOPDSfeed = undefined; this.publicationsOPDSfeedNeedsUpdate = true; this.creatingPublicationsOPDS = false; this.opdsJsonFilePath = (0, tmp_1.tmpNameSync)({ prefix: "readium2-OPDS2-", postfix: ".json" }); this.expressApp = express(); (0, server_secure_1.serverSecure)(this, this.expressApp); var staticOptions = { etag: false, }; if (!this.disableReaders) { this.expressApp.use("/readerNYPL", express.static("misc/readers/reader-NYPL", staticOptions)); this.expressApp.use("/readerHADRIEN", express.static("misc/readers/reader-HADRIEN", staticOptions)); } (0, server_root_1.serverRoot)(this, this.expressApp); (0, server_version_1.serverVersion)(this, this.expressApp); if (!this.disableRemotePubUrl) { (0, server_url_1.serverRemotePub)(this, this.expressApp); (0, server_lcp_lsd_show_1.serverLCPLSD_show)(this, this.expressApp); } if (!this.disableOPDS) { (0, server_opds_browse_v1_1.serverOPDS_browse_v1)(this, this.expressApp); (0, server_opds_browse_v2_1.serverOPDS_browse_v2)(this, this.expressApp); (0, server_opds_local_feed_1.serverOPDS_local_feed)(this, this.expressApp); (0, server_opds_convert_v1_to_v2_1.serverOPDS_convert_v1_to_v2)(this, this.expressApp); } var routerPathBase64 = (0, server_pub_1.serverPub)(this, this.expressApp); (0, server_manifestjson_1.serverManifestJson)(this, routerPathBase64); (0, server_mediaoverlays_1.serverMediaOverlays)(this, routerPathBase64); (0, server_assets_1.serverAssets)(this, routerPathBase64); } Server.prototype.preventRobots = function () { this.expressApp.get("/robots.txt", function (_req, res) { var robotsTxt = "User-agent: *\nDisallow: /\n"; res.header("Content-Type", "text/plain"); res.status(200).send(robotsTxt); }); }; Server.prototype.expressUse = function (pathf, func) { this.expressApp.use(pathf, func); }; Server.prototype.expressGet = function (paths, func) { this.expressApp.get(paths, func); }; Server.prototype.isStarted = function () { return (typeof this.serverInfo() !== "undefined") && (typeof this.httpServer !== "undefined") || (typeof this.httpsServer !== "undefined"); }; Server.prototype.isSecured = function () { return (typeof this.serverInfo() !== "undefined") && (typeof this.httpsServer !== "undefined"); }; Server.prototype.getSecureHTTPHeader = function (url) { return (0, server_secure_1.serverSecureHTTPHeader)(this, url); }; Server.prototype.start = function (port, secure) { return tslib_1.__awaiter(this, void 0, void 0, function () { var envPort, p; var _this = this; return tslib_1.__generator(this, function (_a) { if (this.isStarted()) { return [2, Promise.resolve(this.serverInfo())]; } envPort = 0; try { envPort = process.env.PORT ? parseInt(process.env.PORT, 10) : 0; } catch (err) { debug(err); envPort = 0; } p = port || envPort || 3000; debug("PORT: ".concat(port, " || ").concat(envPort, " || 3000 => ").concat(p)); if (secure) { this.httpServer = undefined; return [2, new Promise(function (resolve, reject) { return tslib_1.__awaiter(_this, void 0, void 0, function () { var certData, err_1; var _this = this; return tslib_1.__generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 2, , 3]); return [4, (0, self_signed_1.generateSelfSignedData)()]; case 1: certData = _a.sent(); return [3, 3]; case 2: err_1 = _a.sent(); debug(err_1); reject("err"); return [2]; case 3: this.httpsServer = https.createServer({ key: certData.private, cert: certData.cert }, this.expressApp).listen(p, function () { _this.serverData = tslib_1.__assign(tslib_1.__assign({}, certData), { urlHost: "127.0.0.1", urlPort: p, urlScheme: "https" }); resolve(_this.serverData); }); return [2]; } }); }); })]; } else { this.httpsServer = undefined; return [2, new Promise(function (resolve, _reject) { _this.httpServer = http.createServer(_this.expressApp).listen(p, function () { _this.serverData = { urlHost: "127.0.0.1", urlPort: p, urlScheme: "http", }; resolve(_this.serverData); }); })]; } return [2]; }); }); }; Server.prototype.stop = function () { if (this.isStarted()) { if (this.httpServer) { this.httpServer.close(); this.httpServer = undefined; } if (this.httpsServer) { this.httpsServer.close(); this.httpsServer = undefined; } this.serverData = undefined; this.uncachePublications(); } }; Server.prototype.serverInfo = function () { return this.serverData; }; Server.prototype.serverUrl = function () { if (!this.isStarted()) { return undefined; } var info = this.serverInfo(); if (!info) { return undefined; } if (info.urlPort === 443 || info.urlPort === 80) { return "".concat(info.urlScheme, "://").concat(info.urlHost); } return "".concat(info.urlScheme, "://").concat(info.urlHost, ":").concat(info.urlPort); }; Server.prototype.setResponseCacheHeaders = function (res, enableCaching) { if (enableCaching) { res.setHeader("Cache-Control", "public,max-age=86400"); } else { res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); res.setHeader("Pragma", "no-cache"); res.setHeader("Expires", "0"); } }; Server.prototype.setResponseCORS = function (res) { res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS"); res.setHeader("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Ranges, Content-Range, Range, Link, Transfer-Encoding, X-Requested-With, Authorization, Accept, Origin, User-Agent, DNT, Cache-Control, Keep-Alive, If-Modified-Since"); res.setHeader("Access-Control-Expose-Headers", "Content-Type, Content-Length, Accept-Ranges, Content-Range, Range, Link, Transfer-Encoding, X-Requested-With, Authorization, Accept, Origin, User-Agent, DNT, Cache-Control, Keep-Alive, If-Modified-Since"); }; Server.prototype.addPublications = function (pubs) { var _this = this; pubs.forEach(function (pub) { if (_this.publications.indexOf(pub) < 0) { _this.publicationsOPDSfeedNeedsUpdate = true; _this.publications.push(pub); } }); return pubs.map(function (pub) { var pubid = (0, UrlUtils_1.encodeURIComponent_RFC3986)(Buffer.from(pub).toString("base64")); return "/pub/".concat(pubid, "/manifest.json"); }); }; Server.prototype.removePublications = function (pubs) { var _this = this; pubs.forEach(function (pub) { _this.uncachePublication(pub); var i = _this.publications.indexOf(pub); if (i >= 0) { _this.publicationsOPDSfeedNeedsUpdate = true; _this.publications.splice(i, 1); } }); return pubs.map(function (pub) { var pubid = (0, UrlUtils_1.encodeURIComponent_RFC3986)(Buffer.from(pub).toString("base64")); return "/pub/".concat(pubid, "/manifest.json"); }); }; Server.prototype.getPublications = function () { return this.publications; }; Server.prototype.loadOrGetCachedPublication = function (filePath) { return tslib_1.__awaiter(this, void 0, void 0, function () { var publication, zip, publicationJsonStr, publicationJsonObj, err_2, err_3, contentKeys, contentKeyPath, contentKey, lcpContentKeysPath, contentKeysData, contentKeysMap, _i, contentKeysMap_1, keyValuePair, key, value, contentKeysUnique, errMsg, userKeys, lcpUserKeyPath, userKey, lcpUserKeysPath, userKeysData, userKeysMap, _a, userKeysMap_1, keyValuePair, key, value, userKeysUnique, err_4, errMsg, err_5, errMsg; return tslib_1.__generator(this, function (_b) { switch (_b.label) { case 0: publication = this.cachedPublication(filePath); if (!!publication) return [3, 16]; if (!filePath.endsWith("_manifest.json")) return [3, 5]; _b.label = 1; case 1: _b.trys.push([1, 3, , 4]); return [4, (0, zipFactory_1.zipLoadPromise)(filePath.replace(/_manifest\.json$/, ""))]; case 2: zip = _b.sent(); publicationJsonStr = fs.readFileSync(filePath, { encoding: "utf8" }); publicationJsonObj = global.JSON.parse(publicationJsonStr); publication = (0, serializable_1.TaJsonDeserialize)(publicationJsonObj, publication_1.Publication); publication.AddToInternal("filename", path.basename(filePath)); publication.AddToInternal("type", "daisy"); publication.AddToInternal("zip", zip); return [3, 4]; case 3: err_2 = _b.sent(); debug(err_2); return [2, Promise.reject(err_2)]; case 4: return [3, 8]; case 5: _b.trys.push([5, 7, , 8]); return [4, (0, publication_parser_1.PublicationParsePromise)(filePath)]; case 6: publication = _b.sent(); return [3, 8]; case 7: err_3 = _b.sent(); debug(err_3); return [2, Promise.reject(err_3)]; case 8: if (!publication) { return [2, Promise.reject("!PUBLICATION??")]; } if (!publication.LCP && !this.disableDecryption) { try { contentKeys = []; contentKeyPath = path.join(path.dirname(filePath), path.basename(filePath) + ".contentkey"); if (fs.existsSync(contentKeyPath)) { contentKey = fs.readFileSync(contentKeyPath, { encoding: "utf8" }); if (contentKey) { contentKey = contentKey.trim(); if (isValidHexPassphraseHashSha256(contentKey)) { contentKeys.push(contentKey); } } } lcpContentKeysPath = path.join(process.cwd(), "LCP", ".contentkeys"); if (fs.existsSync(lcpContentKeysPath)) { contentKeysData = fs.readFileSync(lcpContentKeysPath, { encoding: "utf8" }); if (contentKeysData) { contentKeysData = contentKeysData.trim(); contentKeysMap = contentKeysData.split("\n").map(function (contentKeyLine) { contentKeyLine = contentKeyLine.trim(); if (!contentKeyLine) { return null; } var keyValuePair = contentKeyLine.split("_::_"); if (keyValuePair[0] && keyValuePair[1]) { return [keyValuePair[0].trim(), keyValuePair[1].trim()]; } return null; }).filter(function (item) { return !!item; }); for (_i = 0, contentKeysMap_1 = contentKeysMap; _i < contentKeysMap_1.length; _i++) { keyValuePair = contentKeysMap_1[_i]; key = keyValuePair[0]; value = keyValuePair[1]; if (key === path.relative(process.cwd(), filePath)) { if (isValidHexPassphraseHashSha256(value)) { contentKeys.push(value); } } } } } contentKeysUnique = Array.from(new Set(contentKeys)); debug("SUCCESS contentKeys:"); debug(filePath); debug(path.relative(process.cwd(), filePath)); debug(contentKeys.length); debug(contentKeysUnique.length); if (contentKeysUnique.length) { publication["AES256CBCContentKey"] = Buffer.from(contentKeysUnique[0], "hex"); } } catch (err) { debug(err); errMsg = "FAIL AES256CBCContentKey: " + err; debug(errMsg); } } if (!(publication.LCP && !this.disableDecryption)) return [3, 15]; _b.label = 9; case 9: _b.trys.push([9, 14, , 15]); userKeys = []; lcpUserKeyPath = path.join(path.dirname(filePath), path.basename(filePath) + ".userkey"); if (fs.existsSync(lcpUserKeyPath)) { userKey = fs.readFileSync(lcpUserKeyPath, { encoding: "utf8" }); if (userKey) { userKey = userKey.trim(); if (isValidHexPassphraseHashSha256(userKey)) { userKeys.push(userKey); } } } lcpUserKeysPath = path.join(process.cwd(), "LCP", ".userkeys"); if (fs.existsSync(lcpUserKeysPath)) { userKeysData = fs.readFileSync(lcpUserKeysPath, { encoding: "utf8" }); if (userKeysData) { userKeysData = userKeysData.trim(); userKeysMap = userKeysData.split("\n").map(function (userKeyLine) { userKeyLine = userKeyLine.trim(); if (!userKeyLine) { return null; } var keyValuePair = userKeyLine.split("_::_"); if (keyValuePair[0] && keyValuePair[1]) { return [keyValuePair[0].trim(), keyValuePair[1].trim()]; } return null; }).filter(function (item) { return !!item; }); for (_a = 0, userKeysMap_1 = userKeysMap; _a < userKeysMap_1.length; _a++) { keyValuePair = userKeysMap_1[_a]; key = keyValuePair[0]; value = keyValuePair[1]; if (key === publication.LCP.Provider || key === publication.LCP.ID) { if (isValidHexPassphraseHashSha256(value)) { userKeys.push(value); } } } } } userKeysUnique = Array.from(new Set(userKeys)); _b.label = 10; case 10: _b.trys.push([10, 12, , 13]); return [4, publication.LCP.tryUserKeys(userKeysUnique)]; case 11: _b.sent(); debug("SUCCESS publication.LCP.tryUserKeys():"); debug(filePath); debug(publication.LCP.Provider); debug(publication.LCP.ID); debug(userKeys.length); debug(userKeysUnique.length); if (publication.LCP.ContentKey) { debug(publication.LCP.ContentKey.toString("hex")); } return [3, 13]; case 12: err_4 = _b.sent(); publication.LCP.ContentKey = undefined; debug(err_4); errMsg = "FAIL publication.LCP.tryUserKeys(): " + err_4; debug(errMsg); return [3, 13]; case 13: return [3, 15]; case 14: err_5 = _b.sent(); publication.LCP.ContentKey = undefined; debug(err_5); errMsg = "FAIL before publication.LCP.tryUserKeys(): " + err_5; debug(errMsg); return [3, 15]; case 15: this.cachePublication(filePath, publication); _b.label = 16; case 16: return [2, publication]; } }); }); }; Server.prototype.isPublicationCached = function (filePath) { return typeof this.cachedPublication(filePath) !== "undefined"; }; Server.prototype.cachedPublication = function (filePath) { return this.pathPublicationMap[filePath]; }; Server.prototype.cachePublication = function (filePath, pub) { if (!this.isPublicationCached(filePath)) { this.pathPublicationMap[filePath] = pub; } }; Server.prototype.uncachePublication = function (filePath) { if (this.isPublicationCached(filePath)) { var pub = this.cachedPublication(filePath); if (pub) { try { pub.freeDestroy(); } catch (ex) { debug(ex); } } this.pathPublicationMap[filePath] = undefined; delete this.pathPublicationMap[filePath]; } }; Server.prototype.uncachePublications = function () { var _this = this; Object.keys(this.pathPublicationMap).forEach(function (filePath) { _this.uncachePublication(filePath); }); }; Server.prototype.publicationsOPDS = function () { if (this.publicationsOPDSfeedNeedsUpdate) { this.publicationsOPDSfeed = undefined; if (fs.existsSync(this.opdsJsonFilePath)) { fs.unlinkSync(this.opdsJsonFilePath); } } if (this.publicationsOPDSfeed) { return this.publicationsOPDSfeed; } debug("OPDS2.json => ".concat(this.opdsJsonFilePath)); if (!fs.existsSync(this.opdsJsonFilePath)) { if (!this.creatingPublicationsOPDS) { this.creatingPublicationsOPDS = true; this.publicationsOPDSfeedNeedsUpdate = false; var jsFile = path.join(__dirname, "opds2-create-cli.js"); var args_1 = [jsFile, this.opdsJsonFilePath]; this.publications.forEach(function (pub) { var filePathBase64 = (0, UrlUtils_1.encodeURIComponent_RFC3986)(Buffer.from(pub).toString("base64")); args_1.push(filePathBase64); }); debug("SPAWN OPDS2-create: ".concat(args_1[0])); var child = child_process.spawn("node", args_1, { cwd: process.cwd(), env: process.env, }); child.stdout.on("data", function (data) { debug(data.toString()); }); child.stderr.on("data", function (data) { debug(data.toString()); }); } return undefined; } this.creatingPublicationsOPDS = false; var jsonStr = fs.readFileSync(this.opdsJsonFilePath, { encoding: "utf8" }); if (!jsonStr) { return undefined; } var json = global.JSON.parse(jsonStr); this.publicationsOPDSfeed = (0, serializable_1.TaJsonDeserialize)(json, opds2_1.OPDSFeed); return this.publicationsOPDSfeed; }; return Server; }()); exports.Server = Server; //# sourceMappingURL=server.js.map