diff --git a/index.js b/index.js index 11182b4..551fb1c 100644 --- a/index.js +++ b/index.js @@ -5,6 +5,8 @@ const Router = require('@koa/router') const multer = require('@koa/multer') const logger = require('koa-logger') const sendfile = require('koa-sendfile') +const serve = require('koa-static') +const mount = require('koa-mount') const { mkdirp } = require('mkdirp') const fs = require('fs') const { spawn } = require('child_process') @@ -76,7 +78,7 @@ function expireKey (key) { function flash (ctx, data) { console.log(data) - ctx.cookies.set('flash', encodeURIComponent(JSON.stringify(data)), {overwrite: true, httpOnly: false}) + ctx.cookies.set('flash', encodeURIComponent(JSON.stringify(data)), {overwrite: true, httpOnly: false, sameSite: 'strict', maxAge: 10 * 1000}) } const app = new Koa() @@ -142,7 +144,8 @@ router.post('/generate', async ctx => { const info = { created: new Date(), agent: agent, - file: null + file: null, + urls: [] } ctx.keys.set(key, info) expireKey(key) @@ -200,123 +203,153 @@ router.post('/upload', upload.single('file'), async ctx => { return } - if (!ctx.request.file || ctx.request.file.size === 0) { - flash(ctx, { - message: 'Invalid file submitted', - success: false, - key: key - }) - ctx.redirect('back', '/') - if (ctx.request.file) { + const info = ctx.keys.get(key) + expireKey(key) + + let url = null + if (ctx.request.body.url) { + url = ctx.request.body.url.trim() + if (url.length > 0 && !info.urls.includes(url)) { + info.urls.push(url) + } + } + + let conversion = null + let filename = "" + + if (ctx.request.file) { + if (ctx.request.file.size === 0) { + flash(ctx, { + message: 'Invalid file submitted', + success: false, + key: key + }) + ctx.redirect('back', '/') fs.unlink(ctx.request.file.path, (err) => { if (err) console.error(err) else console.log('Removed file', ctx.request.file.path) }) + return + } + + const mimetype = ctx.request.file.mimetype + + const type = await FileType.fromFile(ctx.request.file.path) + + if ((!type || !allowedTypes.includes(type.mime)) && !allowedTypes.includes(mimetype)) { + flash(ctx, { + message: 'Uploaded file is of an invalid type: ' + ctx.request.file.originalname + ' (' + (type? type.mime : 'unknown mimetype') + ')', + success: false, + key: key + }) + ctx.redirect('back', '/') + fs.unlink(ctx.request.file.path, (err) => { + if (err) console.error(err) + else console.log('Removed file', ctx.request.file.path) + }) + return + } + + let data = null + filename = ctx.request.file.originalname + + if (mimetype === TYPE_EPUB && info.agent.includes('Kindle')) { + // convert to .mobi + conversion = 'kindlegen' + const outname = ctx.request.file.path.replace(/\.epub$/i, '.mobi') + filename = filename.replace(/\.kepub\.epub$/i, '.epub').replace(/\.epub$/i, '.mobi') + + data = await new Promise((resolve, reject) => { + const kindlegen = spawn('kindlegen', [basename(ctx.request.file.path), '-dont_append_source', '-c1', '-o', basename(outname)], { + stdio: 'inherit', + cwd: dirname(ctx.request.file.path) + }) + kindlegen.once('close', (code) => { + fs.unlink(ctx.request.file.path, (err) => { + if (err) console.error(err) + else console.log('Removed file', ctx.request.file.path) + }) + fs.unlink(ctx.request.file.path.replace(/\.epub$/i, '.mobi8'), (err) => { + if (err) console.error(err) + else console.log('Removed file', ctx.request.file.path.replace(/\.epub$/i, '.mobi8')) + }) + if (code !== 0) { + console.warn('kindlegen error code ' + code) + } + + resolve(outname) + }) + }) + + } else if (mimetype === TYPE_EPUB && info.agent.includes('Kobo') && ctx.request.body.kepubify) { + // convert to Kobo EPUB + conversion = 'kepubify' + const outname = ctx.request.file.path.replace(/\.epub$/i, '.kepub.epub') + filename = filename.replace(/\.kepub\.epub$/i, '.epub').replace(/\.epub$/i, '.kepub.epub') + + data = await new Promise((resolve, reject) => { + const kepubify = spawn('kepubify', ['-v', '-u', '-o', basename(outname), basename(ctx.request.file.path)], { + stdio: 'inherit', + cwd: dirname(ctx.request.file.path) + }) + kepubify.once('close', (code) => { + fs.unlink(ctx.request.file.path, (err) => { + if (err) console.error(err) + else console.log('Removed file', ctx.request.file.path) + }) + if (code !== 0) { + reject('kepubify error code ' + code) + return + } + + resolve(outname) + }) + }) + } else { + // No conversion + data = ctx.request.file.path + } + + expireKey(key) + if (info.file && info.file.path) { + await new Promise((resolve, reject) => fs.unlink(info.file.path, (err) => { + if (err) return reject(err) + else console.log('Removed previously uploaded file', info.file.path) + resolve() + })) + } + info.file = { + name: filename, + path: data, + // size: ctx.request.file.size, + uploaded: new Date() } - return } - const mimetype = ctx.request.file.mimetype + let messages = [] + if (ctx.request.file) { + messages.push('Upload successful! ' + (conversion ? ' Ebook was converted with ' + conversion + ' and sent' : ' Sent')+' to '+(info.agent.includes('Kobo') ? 'a Kobo device.' : (info.agent.includes('Kindle') ? 'a Kindle device.' : 'a device.'))) + messages.push('Filename: ' + filename) + } + if (url) { + messages.push("Added url: " + url) + } - const type = await FileType.fromFile(ctx.request.file.path) - - if ((!type || !allowedTypes.includes(type.mime)) && !allowedTypes.includes(mimetype)) { + if (messages.length === 0) { flash(ctx, { - message: 'Uploaded file is of an invalid type: ' + ctx.request.file.originalname + ' (' + (type? type.mime : 'unknown mimetype') + ')', + message: 'No file or url selected', success: false, key: key }) ctx.redirect('back', '/') - fs.unlink(ctx.request.file.path, (err) => { - if (err) console.error(err) - else console.log('Removed file', ctx.request.file.path) - }) return } - const info = ctx.keys.get(key) - expireKey(key) - - let data = null - let filename = ctx.request.file.originalname - let conversion = null - - if (mimetype === TYPE_EPUB && info.agent.includes('Kindle')) { - // convert to .mobi - conversion = 'kindlegen' - const outname = ctx.request.file.path.replace(/\.epub$/i, '.mobi') - filename = filename.replace(/\.kepub\.epub$/i, '.epub').replace(/\.epub$/i, '.mobi') - - data = await new Promise((resolve, reject) => { - const kindlegen = spawn('kindlegen', [basename(ctx.request.file.path), '-dont_append_source', '-c1', '-o', basename(outname)], { - stdio: 'inherit', - cwd: dirname(ctx.request.file.path) - }) - kindlegen.once('close', (code) => { - fs.unlink(ctx.request.file.path, (err) => { - if (err) console.error(err) - else console.log('Removed file', ctx.request.file.path) - }) - fs.unlink(ctx.request.file.path.replace(/\.epub$/i, '.mobi8'), (err) => { - if (err) console.error(err) - else console.log('Removed file', ctx.request.file.path.replace(/\.epub$/i, '.mobi8')) - }) - if (code !== 0) { - console.warn('kindlegen error code ' + code) - } - - resolve(outname) - }) - }) - - } else if (mimetype === TYPE_EPUB && info.agent.includes('Kobo') && ctx.request.body.kepubify) { - // convert to Kobo EPUB - conversion = 'kepubify' - const outname = ctx.request.file.path.replace(/\.epub$/i, '.kepub.epub') - filename = filename.replace(/\.kepub\.epub$/i, '.epub').replace(/\.epub$/i, '.kepub.epub') - - data = await new Promise((resolve, reject) => { - const kepubify = spawn('kepubify', ['-v', '-u', '-o', basename(outname), basename(ctx.request.file.path)], { - stdio: 'inherit', - cwd: dirname(ctx.request.file.path) - }) - kepubify.once('close', (code) => { - fs.unlink(ctx.request.file.path, (err) => { - if (err) console.error(err) - else console.log('Removed file', ctx.request.file.path) - }) - if (code !== 0) { - reject('kepubify error code ' + code) - return - } - - resolve(outname) - }) - }) - } else { - // No conversion - data = ctx.request.file.path - } - - expireKey(key) - if (info.file && info.file.path) { - await new Promise((resolve, reject) => fs.unlink(info.file.path, (err) => { - if (err) return reject(err) - else console.log('Removed previously uploaded file', info.file.path) - resolve() - })) - } - info.file = { - name: filename, - path: data, - // size: ctx.request.file.size, - uploaded: new Date() - } - flash(ctx, { - message: 'Upload successful!
'+(conversion ? ' Ebook was converted with ' + conversion + ' and sent' : ' Sent')+' to '+(info.agent.includes('Kobo') ? 'a Kobo device.' : (info.agent.includes('Kindle') ? 'a Kindle device.' : 'a device.'))+'
Filename: ' + filename, + message: messages.join("
"), success: true, - key: key + key: key, + url: url }) ctx.redirect('back', '/') }) @@ -349,28 +382,30 @@ router.get('/status/:key', async ctx => { file: info.file ? { name: info.file.name, // size: info.file.size - } : null + } : null, + urls: info.urls } }) -router.get('/style.css', async ctx => { - await sendfile(ctx, 'style.css') -}) - router.get('/receive', async ctx => { - await sendfile(ctx, 'download.html') + await sendfile(ctx, 'static/download.html') }) router.get('/', async ctx => { const agent = ctx.get('user-agent') console.log(ctx.ip, agent) - await sendfile(ctx, agent.includes('Kobo') || agent.includes('Kindle')? 'download.html' : 'upload.html') + await sendfile(ctx, agent.includes('Kobo') || agent.includes('Kindle')? 'static/download.html' : 'static/upload.html') }) app.use(router.routes()) app.use(router.allowedMethods()) +app.use(serve("static")) + +app.use(mount("/node_modules/crypto-js", serve("node_modules/crypto-js"))) + + fs.rm('uploads', {recursive: true}, (err) => { if (err) throw err mkdirp('uploads').then (() => { diff --git a/package-lock.json b/package-lock.json index 239ca46..ac21a8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,9 @@ "file-type": "^16.5.4", "koa": "^2.14.2", "koa-logger": "^3.2.1", + "koa-mount": "^4.0.0", "koa-sendfile": "^3.0.0", + "koa-static": "^5.0.0", "mkdirp": "^3.0.1", "multer": "^1.4.5-lts.1", "transliteration": "^2.3.5" @@ -1054,6 +1056,62 @@ "node": ">= 7.6.0" } }, + "node_modules/koa-mount": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/koa-mount/-/koa-mount-4.0.0.tgz", + "integrity": "sha512-rm71jaA/P+6HeCpoRhmCv8KVBIi0tfGuO/dMKicbQnQW/YJntJ6MnnspkodoA4QstMVEZArsCphmd0bJEtoMjQ==", + "dependencies": { + "debug": "^4.0.1", + "koa-compose": "^4.1.0" + }, + "engines": { + "node": ">= 7.6.0" + } + }, + "node_modules/koa-send": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/koa-send/-/koa-send-5.0.1.tgz", + "integrity": "sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==", + "dependencies": { + "debug": "^4.1.1", + "http-errors": "^1.7.3", + "resolve-path": "^1.4.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/koa-send/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa-send/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa-send/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/koa-sendfile": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/koa-sendfile/-/koa-sendfile-3.0.0.tgz", @@ -1066,6 +1124,26 @@ "node": ">= 10" } }, + "node_modules/koa-static": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/koa-static/-/koa-static-5.0.0.tgz", + "integrity": "sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==", + "dependencies": { + "debug": "^3.1.0", + "koa-send": "^5.0.0" + }, + "engines": { + "node": ">= 7.6.0" + } + }, + "node_modules/koa-static/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, "node_modules/koa/node_modules/http-errors": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", @@ -1245,6 +1323,14 @@ "resolved": "https://registry.npmjs.org/passthrough-counter/-/passthrough-counter-1.0.0.tgz", "integrity": "sha512-Wy8PXTLqPAN0oEgBrlnsXPMww3SYJ44tQ8aVrGAI4h4JZYCS0oYqsPqtPR8OhJpv6qFbpbB7XAn0liKV7EXubA==" }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-to-regexp": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", @@ -1327,6 +1413,58 @@ "node": ">=0.10.0" } }, + "node_modules/resolve-path": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/resolve-path/-/resolve-path-1.4.0.tgz", + "integrity": "sha512-i1xevIst/Qa+nA9olDxLWnLk8YZbi8R/7JPbCMcgyWaFR6bKWaexgJgEB5oc2PKMjYdrHynyz0NY+if+H98t1w==", + "dependencies": { + "http-errors": "~1.6.2", + "path-is-absolute": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/resolve-path/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/resolve-path/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/resolve-path/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" + }, + "node_modules/resolve-path/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "node_modules/resolve-path/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", diff --git a/package.json b/package.json index b069aac..9e49a1f 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,9 @@ "file-type": "^16.5.4", "koa": "^2.14.2", "koa-logger": "^3.2.1", + "koa-mount": "^4.0.0", "koa-sendfile": "^3.0.0", + "koa-static": "^5.0.0", "mkdirp": "^3.0.1", "multer": "^1.4.5-lts.1", "transliteration": "^2.3.5" diff --git a/static/common.js b/static/common.js new file mode 100644 index 0000000..60eee2b --- /dev/null +++ b/static/common.js @@ -0,0 +1,41 @@ +'use strict' + +function log(str) { + var node = document.createElement("div") + node.textContent = str + logs.appendChild(node) +} +window.addEventListener("error", function (event) { + log(event.filename + ":" + event.lineno + " " + event.message) +}, false) + +function xhr(method, url, cb) { + var x = new XMLHttpRequest() + x.onload = function () { + cb(x) + } + x.onerror = function () { + cb(x) + } + x.open(method, url, true) + x.send(null) +} + +var isIOS = /iPad|iPhone|iPod/.test(navigator.platform) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1) + +function getCookies() { + var cookieRegex = /([\w\.]+)\s*=\s*(?:"((?:\\"|[^"])*)"|(.*?))\s*(?:[;,]|$)/g + var cookies = {} + var match + while( (match = cookieRegex.exec(document.cookie)) !== null ) { + var value = match[2] || match[3] + cookies[match[1]] = decodeURIComponent(value) + try { + cookies[match[1]] = JSON.parse(cookies[match[1]]) + } catch (err) {} + } + return cookies +} +// function deleteCookie(name) { +// document.cookie = name + "= ; expires = Thu, 01 Jan 1970 00:00:00 GMT" +// } diff --git a/download.html b/static/download.html similarity index 71% rename from download.html rename to static/download.html index 7daf0a5..84eeaed 100644 --- a/download.html +++ b/static/download.html @@ -4,7 +4,8 @@ Send to Kobo/Kindle - + + @@ -21,13 +22,15 @@ -
-
-
Downloads
-
-
+
+
+
+
Downloads
+ +
+


@@ -39,35 +42,26 @@
+
+

Send to Kobo/Kindle

-
+ - + +

EPUB, MOBI, PDF,
TXT, CBZ, CBR



EPUB, MOBI, PDF,
TXT, CBZ, CBR


@@ -33,37 +35,19 @@
+
+