1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.serverOPDS_convert_v1_to_v2 = exports.serverOPDS_convert_v1_to_v2_PATH = void 0;
|
4 | var tslib_1 = require("tslib");
|
5 | var css2json = require("css2json");
|
6 | var debug_ = require("debug");
|
7 | var DotProp = require("dot-prop");
|
8 | var express = require("express");
|
9 | var jsonMarkup = require("json-markup");
|
10 | var morgan = require("morgan");
|
11 | var path = require("path");
|
12 | var request = require("request");
|
13 | var requestPromise = require("request-promise-native");
|
14 | var xmldom = require("xmldom");
|
15 | var serializable_1 = require("r2-lcp-js/dist/es5/src/serializable");
|
16 | var converter_1 = require("r2-opds-js/dist/es5/src/opds/converter");
|
17 | var opds_1 = require("r2-opds-js/dist/es5/src/opds/opds1/opds");
|
18 | var opds_entry_1 = require("r2-opds-js/dist/es5/src/opds/opds1/opds-entry");
|
19 | var UrlUtils_1 = require("r2-utils-js/dist/es5/src/_utils/http/UrlUtils");
|
20 | var JsonUtils_1 = require("r2-utils-js/dist/es5/src/_utils/JsonUtils");
|
21 | var BufferUtils_1 = require("r2-utils-js/dist/es5/src/_utils/stream/BufferUtils");
|
22 | var xml_js_mapper_1 = require("r2-utils-js/dist/es5/src/_utils/xml-js-mapper");
|
23 | var json_schema_validate_1 = require("../utils/json-schema-validate");
|
24 | var request_ext_1 = require("./request-ext");
|
25 | var server_trailing_slash_redirect_1 = require("./server-trailing-slash-redirect");
|
26 | var debug = debug_("r2:streamer#http/server-opds-convert-v1-to-v2");
|
27 | exports.serverOPDS_convert_v1_to_v2_PATH = "/opds-v1-v2-convert";
|
28 | function serverOPDS_convert_v1_to_v2(_server, topRouter) {
|
29 | var _this = this;
|
30 | var jsonStyle = "\n.json-markup {\n line-height: 17px;\n font-size: 13px;\n font-family: monospace;\n white-space: pre;\n}\n.json-markup-key {\n font-weight: bold;\n}\n.json-markup-bool {\n color: firebrick;\n}\n.json-markup-string {\n color: green;\n}\n.json-markup-null {\n color: gray;\n}\n.json-markup-number {\n color: blue;\n}\n";
|
31 | var routerOPDS_convert_v1_to_v2 = express.Router({ strict: false });
|
32 | routerOPDS_convert_v1_to_v2.use(morgan("combined", { stream: { write: function (msg) { return debug(msg); } } }));
|
33 | routerOPDS_convert_v1_to_v2.use(server_trailing_slash_redirect_1.trailingSlashRedirect);
|
34 | routerOPDS_convert_v1_to_v2.get("/", function (_req, res) {
|
35 | var html = "<html><head>";
|
36 | html += "<script type=\"text/javascript\">function encodeURIComponent_RFC3986(str) { " +
|
37 | "return encodeURIComponent(str).replace(/[!'()*]/g, (c) => { " +
|
38 | "return \"%\" + c.charCodeAt(0).toString(16); }); }" +
|
39 | "function go(evt) {" +
|
40 | "if (evt) { evt.preventDefault(); } var url = " +
|
41 | "location.origin +" +
|
42 | (" '" + exports.serverOPDS_convert_v1_to_v2_PATH + "/' +") +
|
43 | " encodeURIComponent_RFC3986(document.getElementById(\"url\").value);" +
|
44 | "location.href = url;}</script>";
|
45 | html += "</head>";
|
46 | html += "<body><h1>OPDS 1 -> 2 converter</h1>";
|
47 | html += "<form onsubmit=\"go();return false;\">" +
|
48 | "<input type=\"text\" name=\"url\" id=\"url\" size=\"80\">" +
|
49 | "<input type=\"submit\" value=\"Go!\"></form>";
|
50 | html += "</body></html>";
|
51 | res.status(200).send(html);
|
52 | });
|
53 | routerOPDS_convert_v1_to_v2.param("urlEncoded", function (req, _res, next, value, _name) {
|
54 | req.urlEncoded = value;
|
55 | next();
|
56 | });
|
57 | routerOPDS_convert_v1_to_v2.get("/:" + request_ext_1._urlEncoded + "(*)", function (req, res) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
|
58 | var reqparams, urlDecoded, isSecureHttp, rootUrl, failure, success, headers, needsStreamingResponse, response, err_1;
|
59 | var _this = this;
|
60 | return tslib_1.__generator(this, function (_a) {
|
61 | switch (_a.label) {
|
62 | case 0:
|
63 | reqparams = req.params;
|
64 | if (!reqparams.urlEncoded) {
|
65 | reqparams.urlEncoded = req.urlEncoded;
|
66 | }
|
67 | urlDecoded = reqparams.urlEncoded;
|
68 | debug(urlDecoded);
|
69 | isSecureHttp = req.secure ||
|
70 | req.protocol === "https" ||
|
71 | req.get("X-Forwarded-Proto") === "https";
|
72 | rootUrl = (isSecureHttp ? "https://" : "http://")
|
73 | + req.headers.host;
|
74 | failure = function (err) {
|
75 | debug(err);
|
76 | res.status(500).send("<html><body><p>Internal Server Error</p><p>"
|
77 | + err + "</p></body></html>");
|
78 | };
|
79 | success = function (response) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
|
80 | var responseData, err_2, responseStr, responseXml, isEntry, opds1Feed, opds1Entry, opds2Feed, opds2Publication, funk, jsonObjOPDS1, jsonObjOPDS2, validationStr, doValidate, jsonSchemasRootpath, jsonSchemasNames, validationErrors, _i, validationErrors_1, err, val, valueStr, title, val, valueStr, title, pubIndex, jsonPubTitlePath, css, jsonPrettyOPDS1, jsonPrettyOPDS2;
|
81 | return tslib_1.__generator(this, function (_a) {
|
82 | switch (_a.label) {
|
83 | case 0:
|
84 | if (response.statusCode && (response.statusCode < 200 || response.statusCode >= 300)) {
|
85 | failure("HTTP CODE " + response.statusCode);
|
86 | return [2];
|
87 | }
|
88 | _a.label = 1;
|
89 | case 1:
|
90 | _a.trys.push([1, 3, , 4]);
|
91 | return [4, BufferUtils_1.streamToBufferPromise(response)];
|
92 | case 2:
|
93 | responseData = _a.sent();
|
94 | return [3, 4];
|
95 | case 3:
|
96 | err_2 = _a.sent();
|
97 | debug(err_2);
|
98 | res.status(500).send("<html><body><p>Internal Server Error</p><p>"
|
99 | + err_2 + "</p></body></html>");
|
100 | return [2];
|
101 | case 4:
|
102 | responseStr = responseData.toString("utf8");
|
103 | responseXml = new xmldom.DOMParser().parseFromString(responseStr);
|
104 | if (!responseXml || !responseXml.documentElement) {
|
105 | res.status(500).send("<html><body><p>Internal Server Error</p><p>"
|
106 | + "XML parse fail" + "</p></body></html>");
|
107 | return [2];
|
108 | }
|
109 | isEntry = responseXml.documentElement.localName === "entry";
|
110 | if (isEntry) {
|
111 | opds1Entry = xml_js_mapper_1.XML.deserialize(responseXml, opds_entry_1.Entry);
|
112 | try {
|
113 | opds2Publication = converter_1.convertOpds1ToOpds2_EntryToPublication(opds1Entry);
|
114 | }
|
115 | catch (err) {
|
116 | debug("OPDS 1 -> 2 conversion FAILED (Entry)");
|
117 | debug(err);
|
118 | res.status(500).send("<html><body><p>Internal Server Error</p><p>"
|
119 | + err + "</p></body></html>");
|
120 | return [2];
|
121 | }
|
122 | }
|
123 | else {
|
124 | opds1Feed = xml_js_mapper_1.XML.deserialize(responseXml, opds_1.OPDS);
|
125 | try {
|
126 | opds2Feed = converter_1.convertOpds1ToOpds2(opds1Feed);
|
127 | }
|
128 | catch (err) {
|
129 | debug("OPDS 1 -> 2 conversion FAILED");
|
130 | debug(err);
|
131 | res.status(500).send("<html><body><p>Internal Server Error</p><p>"
|
132 | + err + "</p></body></html>");
|
133 | return [2];
|
134 | }
|
135 | }
|
136 | funk = function (obj) {
|
137 | if ((obj.href && typeof obj.href === "string") ||
|
138 | (obj.Href && typeof obj.Href === "string")) {
|
139 | var fullHref = obj.href ? obj.href : obj.Href;
|
140 | var notFull = !UrlUtils_1.isHTTP(fullHref);
|
141 | if (notFull) {
|
142 | fullHref = UrlUtils_1.ensureAbsolute(urlDecoded, fullHref);
|
143 | }
|
144 | if ((obj.type && obj.type.indexOf("application/atom+xml") >= 0) ||
|
145 | (obj.Type && obj.Type.indexOf("application/atom+xml") >= 0)) {
|
146 | obj.__href__ = rootUrl + req.originalUrl.substr(0, req.originalUrl.indexOf(exports.serverOPDS_convert_v1_to_v2_PATH + "/")) +
|
147 | exports.serverOPDS_convert_v1_to_v2_PATH + "/" + UrlUtils_1.encodeURIComponent_RFC3986(fullHref);
|
148 | }
|
149 | else if (notFull) {
|
150 | obj.__href__ = fullHref;
|
151 | }
|
152 | }
|
153 | };
|
154 | jsonObjOPDS1 = serializable_1.TaJsonSerialize(opds1Entry ? opds1Entry : opds1Feed);
|
155 | JsonUtils_1.traverseJsonObjects(jsonObjOPDS1, funk);
|
156 | jsonObjOPDS2 = serializable_1.TaJsonSerialize(opds2Publication ? opds2Publication : opds2Feed);
|
157 | doValidate = !reqparams.jsonPath || reqparams.jsonPath === "all";
|
158 | if (doValidate) {
|
159 | jsonSchemasRootpath = path.join(process.cwd(), "misc", "json-schema");
|
160 | jsonSchemasNames = [
|
161 | "opds/publication",
|
162 | "opds/acquisition-object",
|
163 | "opds/catalog-entry",
|
164 | "opds/feed-metadata",
|
165 | "opds/properties",
|
166 | "webpub-manifest/publication",
|
167 | "webpub-manifest/contributor-object",
|
168 | "webpub-manifest/contributor",
|
169 | "webpub-manifest/link",
|
170 | "webpub-manifest/metadata",
|
171 | "webpub-manifest/subcollection",
|
172 | "webpub-manifest/properties",
|
173 | "webpub-manifest/subject",
|
174 | "webpub-manifest/subject-object",
|
175 | "webpub-manifest/extensions/epub/metadata",
|
176 | "webpub-manifest/extensions/epub/subcollections",
|
177 | "webpub-manifest/extensions/epub/properties",
|
178 | "webpub-manifest/extensions/presentation/metadata",
|
179 | "webpub-manifest/extensions/presentation/properties",
|
180 | "webpub-manifest/language-map",
|
181 | ];
|
182 | if (!opds2Publication) {
|
183 | jsonSchemasNames.unshift("opds/feed");
|
184 | }
|
185 | validationErrors = json_schema_validate_1.jsonSchemaValidate(jsonSchemasRootpath, jsonSchemasNames, jsonObjOPDS2);
|
186 | if (validationErrors) {
|
187 | validationStr = "";
|
188 | for (_i = 0, validationErrors_1 = validationErrors; _i < validationErrors_1.length; _i++) {
|
189 | err = validationErrors_1[_i];
|
190 | debug("JSON Schema validation FAIL.");
|
191 | debug(err);
|
192 | if (opds2Publication) {
|
193 | val = DotProp.get(jsonObjOPDS2, err.jsonPath);
|
194 | valueStr = (typeof val === "string") ?
|
195 | "" + val :
|
196 | ((val instanceof Array || typeof val === "object") ?
|
197 | "" + JSON.stringify(val) :
|
198 | "");
|
199 | debug(valueStr);
|
200 | title = DotProp.get(jsonObjOPDS2, "metadata.title");
|
201 | debug(title);
|
202 | validationStr +=
|
203 | "\n\"" + title + "\"\n\n" + err.ajvMessage + ": " + valueStr + "\n\n'" + err.ajvDataPath.replace(/^\./, "") + "' (" + err.ajvSchemaPath + ")\n\n";
|
204 | }
|
205 | else {
|
206 | val = DotProp.get(jsonObjOPDS2, err.jsonPath);
|
207 | valueStr = (typeof val === "string") ?
|
208 | "" + val :
|
209 | ((val instanceof Array || typeof val === "object") ?
|
210 | "" + JSON.stringify(val) :
|
211 | "");
|
212 | debug(valueStr);
|
213 | title = "";
|
214 | pubIndex = "";
|
215 | if (/^publications\.[0-9]+/.test(err.jsonPath)) {
|
216 | jsonPubTitlePath = err.jsonPath.replace(/^(publications\.[0-9]+).*/, "$1.metadata.title");
|
217 | debug(jsonPubTitlePath);
|
218 | title = DotProp.get(jsonObjOPDS2, jsonPubTitlePath);
|
219 | debug(title);
|
220 | pubIndex = err.jsonPath.replace(/^publications\.([0-9]+).*/, "$1");
|
221 | debug(pubIndex);
|
222 | }
|
223 | validationStr +=
|
224 | "\n___________INDEX___________ #" + pubIndex + " \"" + title + "\"\n\n" + err.ajvMessage + ": " + valueStr + "\n\n'" + err.ajvDataPath.replace(/^\./, "") + "' (" + err.ajvSchemaPath + ")\n\n";
|
225 | }
|
226 | }
|
227 | }
|
228 | }
|
229 | JsonUtils_1.traverseJsonObjects(jsonObjOPDS2, funk);
|
230 | css = css2json(jsonStyle);
|
231 | jsonPrettyOPDS1 = jsonMarkup(jsonObjOPDS1, css);
|
232 | jsonPrettyOPDS2 = jsonMarkup(jsonObjOPDS2, css);
|
233 | res.status(200).send("<html><body>" +
|
234 | "<h1>OPDS2 JSON " +
|
235 | (opds2Publication ? "entry" : "feed") +
|
236 | " (converted from OPDS1 XML/ATOM)</h1>" +
|
237 | "<h2><a href=\"" + urlDecoded + "\">" + urlDecoded + "</a></h2>" +
|
238 | "<hr>" +
|
239 | "<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"90%\" " +
|
240 | "style=\"table-layout:fixed;width:90%\">" +
|
241 | "<thead><tr><th>OPDS1</th><th>OPDS2</th></tr></thead>" +
|
242 | "<tbody><tr>" +
|
243 | "<td valign=\"top\" width=\"50%\">" +
|
244 | "<div style=\"overflow-x: auto;margin:0;padding:0;width:100%;height:auto;\">" +
|
245 | jsonPrettyOPDS1 + "</div></td>" +
|
246 | "<td valign=\"top\" width=\"50%\">" +
|
247 | "<div style=\"overflow-x: auto;margin:0;padding:0;width:100%;height:auto;\">" +
|
248 | jsonPrettyOPDS2 + "</div></td>" +
|
249 | "</tbody></tr>" +
|
250 | "</table>" +
|
251 | (doValidate ? (validationStr ? ("<hr><p><pre>" + validationStr + "</pre></p>") : ("<hr><p>JSON SCHEMA OK.</p>")) : "") +
|
252 | "</body></html>");
|
253 | return [2];
|
254 | }
|
255 | });
|
256 | }); };
|
257 | headers = {
|
258 | "Accept": "application/json,application/xml",
|
259 | "Accept-Language": "en-UK,en-US;q=0.7,en;q=0.5",
|
260 | "User-Agent": "READIUM2",
|
261 | };
|
262 | needsStreamingResponse = true;
|
263 | if (!needsStreamingResponse) return [3, 1];
|
264 | request.get({
|
265 | headers: headers,
|
266 | method: "GET",
|
267 | uri: urlDecoded,
|
268 | })
|
269 | .on("response", success)
|
270 | .on("error", failure);
|
271 | return [3, 7];
|
272 | case 1:
|
273 | response = void 0;
|
274 | _a.label = 2;
|
275 | case 2:
|
276 | _a.trys.push([2, 4, , 5]);
|
277 | return [4, requestPromise({
|
278 | headers: headers,
|
279 | method: "GET",
|
280 | resolveWithFullResponse: true,
|
281 | uri: urlDecoded,
|
282 | })];
|
283 | case 3:
|
284 | response = _a.sent();
|
285 | return [3, 5];
|
286 | case 4:
|
287 | err_1 = _a.sent();
|
288 | failure(err_1);
|
289 | return [2];
|
290 | case 5: return [4, success(response)];
|
291 | case 6:
|
292 | _a.sent();
|
293 | _a.label = 7;
|
294 | case 7: return [2];
|
295 | }
|
296 | });
|
297 | }); });
|
298 | topRouter.use(exports.serverOPDS_convert_v1_to_v2_PATH, routerOPDS_convert_v1_to_v2);
|
299 | }
|
300 | exports.serverOPDS_convert_v1_to_v2 = serverOPDS_convert_v1_to_v2;
|
301 |
|
\ | No newline at end of file |