diff --git a/.gitignore b/.gitignore index 871f515..f079e65 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,6 @@ obj/ dist/ .vscode/ Debug/ -Release/ \ No newline at end of file +Release/ +node_modules/ +package-lock.json \ No newline at end of file diff --git a/build.bat b/build.bat deleted file mode 100644 index dcc21e9..0000000 --- a/build.bat +++ /dev/null @@ -1,12 +0,0 @@ -@echo off -call sass src/css/style.scss dist/style.css -call google-closure-compiler ^ - src/js/Const.js ^ - src/js/Util.js ^ - src/js/App.js ^ - src/js/DropzoneManager.js ^ - src/js/FileDownloader.js ^ - src/js/FileUpload.js ^ - src/js/VBF.js ^ - src/js/ViewManager.js ^ ---js_output_file dist/script.min.js --language_out ECMASCRIPT_NEXT \ No newline at end of file diff --git a/index.html b/index.html index 02d21fb..4a420c8 100644 --- a/index.html +++ b/index.html @@ -6,33 +6,6 @@ - - - - - + \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..26cc4e1 --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "void.cat", + "version": "1.2.0-beta1", + "description": "Free file hosting website", + "repository": { + "type": "git", + "url": "git+https://github.com/v0l/void.cat.git" + }, + "scripts": { + "build": "sass src/css/style.scss dist/style.css && webpack-cli --entry ./src/js/index.js --mode production --output ./dist/bundle.js" + }, + "keywords": [ + "upload", + "host", + "files", + "pomf" + ], + "author": "v0l", + "license": "MIT", + "bugs": { + "url": "https://github.com/v0l/void.cat/issues" + }, + "homepage": "https://github.com/v0l/void.cat#readme", + "dependencies": { + "asmcrypto.js": "^2.3.2" + }, + "devDependencies": { + "saas": "^1.0.0", + "webpack-cli": "^3.3.2" + } +} diff --git a/src/js/App.js b/src/js/App.js deleted file mode 100644 index 98d06fe..0000000 --- a/src/js/App.js +++ /dev/null @@ -1,222 +0,0 @@ -/** - * @constant {Object} - */ -const App = { - Loaded: false, - CharJsLoaded: false, - - Elements: { - get Dropzone() { return $('#dropzone') }, - get Uploads() { return $('#uploads') }, - get PageView() { return $('#page-view') }, - get PageUpload() { return $('#page-upload') }, - get PageFaq() { return $('#page-faq') }, - get PageStats() { return $('#page-stats') }, - get PageDonate() { return $('#page-donate') } - }, - - Templates: { - get Upload() { return $("template[id='tmpl-upload']") } - }, - - get IsEdge() { - return /Edge/.test(navigator.userAgent); - }, - - get IsChrome() { - return !App.IsEdge && /^Mozilla.*Chrome/.test(navigator.userAgent); - }, - - get IsFirefox() { - return !App.IsEdge && /^Mozilla.*Firefox/.test(navigator.userAgent); - }, - - /** - * Uploads the files as selected by the input form - * @param {Element} ctx - * @returns {Promise} - */ - UploadFiles: async function (ctx) { - let files = ctx.files; - let proc_files = []; - - for (let x = 0; x < files.length; x++) { - let fu = new FileUpload(files[x]); - proc_files[proc_files.length] = fu.ProcessUpload(); - } - - await Promise.all(proc_files); - }, - - ResetView: function () { - App.Elements.PageView.style.display = "none"; - App.Elements.PageUpload.style.display = "none"; - App.Elements.PageFaq.style.display = "none"; - App.Elements.PageStats.style.display = "none"; - App.Elements.PageDonate.style.display = "none"; - }, - - ShowStats: async function () { - location.hash = "#stats"; - App.ResetView(); - - if (!App.CharJsLoaded) { - await App.InsertScript("//cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js", () => { - return typeof moment !== "undefined"; - }); - await App.InsertScript("//cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.3/Chart.min.js", () => { - return typeof Chart !== "undefined"; - }); - } - - let api_rsp = await Api.GetTxChart(); - if (api_rsp.ok) { - let ctx = $('#weektxgraph').getContext('2d'); - new Chart(ctx, { - type: 'line', - data: api_rsp.data, - options: { - scales: { - xAxes: [{ - type: 'time', - time: { - source: 'data' - } - }], - yAxes: [{ - ticks: { - beginAtZero: true, - callback: function(label, index, labels) { - return Utils.FormatBytes(label); - } - } - }] - } - } - }); - } - App.Elements.PageStats.style.display = "block"; - }, - - ShowFAQ: function () { - location.hash = "#faq"; - App.ResetView(); - App.Elements.PageFaq.style.display = "block"; - }, - - ShowDonate: function () { - location.hash = "#donate"; - App.ResetView(); - App.Elements.PageDonate.style.display = "block"; - }, - - /** - * Sets up the page - */ - Init: async function () { - App.CheckBrowserSupport(); - App.MakePolyfills(); - - App.ResetView(); - - if (location.hash !== "") { - if (location.hash == "#faq") { - App.ShowFAQ(); - } else if (location.hash == "#stats") { - App.ShowStats(); - } else if (location.hash == "#donate") { - App.ShowDonate(); - } else { - App.Elements.PageView.style.display = "block"; - new ViewManager(); - } - window.site_info = await Api.GetSiteInfo(); - } else { - window.site_info = await Api.GetSiteInfo(); - App.Elements.PageUpload.style.display = "block"; - $('#dropzone').innerHTML = `Click me!
(${Utils.FormatBytes(window.site_info.data.max_upload_size)} max)`; - new DropzoneManager(App.Elements.Dropzone); - } - - if (window.site_info.ok) { - let elms = document.querySelectorAll("#footer-stats div span"); - elms[0].textContent = window.site_info.data.basic_stats.Files; - elms[1].textContent = Utils.FormatBytes(window.site_info.data.basic_stats.Size, 2); - elms[2].textContent = Utils.FormatBytes(window.site_info.data.basic_stats.Transfer_24h, 2); - } - - let faq_headers = document.querySelectorAll('#page-faq .faq-header'); - for (let x = 0; x < faq_headers.length; x++) { - faq_headers[x].addEventListener('click', function () { - this.nextElementSibling.classList.toggle("show"); - }.bind(faq_headers[x])); - } - - App.Loaded = true; - }, - - /** - * Adds in polyfills for this browser - */ - MakePolyfills: function () { - if (typeof TextEncoder === "undefined" || typeof TextDecoder === "undefined") { - App.InsertScript("//unpkg.com/text-encoding@0.6.4/lib/encoding-indexes.js"); - App.InsertScript("//unpkg.com/text-encoding@0.6.4/lib/encoding.js"); - } - }, - - /** - * Adds a script tag at the top of the header - * @param {string} src - The script src url - * @param {function} fnWait - Function to use in promise to test if the script is loaded - */ - InsertScript: async function (src, fnWait) { - var before = document.head.getElementsByTagName('script')[0]; - var newlink = document.createElement('script'); - newlink.src = src; - document.head.insertBefore(newlink, before); - - if (typeof fnWait === "function") { - await new Promise((resolve, reject) => { - let timer = setInterval(() => { - if (fnWait()) { - clearInterval(timer); - resolve(); - } - }, 100); - }); - } - }, - - /** - * Checks browser version - */ - CheckBrowserSupport: function () { - if (!App.IsFirefox) { - if (App.IsChrome) { - App.AddNoticeItem("Uploads bigger then 100MiB usually crash Chrome when uploading. Please upload with Firefox. Or check GitHub for tools."); - } - if (App.IsEdge) { - let edge_version = /Edge\/([0-9]{1,3}\.[0-9]{1,5})/.exec(navigator.userAgent)[1]; - Log.I(`Edge version is: ${edge_version}`); - if (parseFloat(edge_version) < 18.18218) { - App.AddNoticeItem("Upload progress isn't reported in the version of Edge you are using, see here for more info."); - } - } - - document.querySelector('#page-notice').style.display = "block"; - } - }, - - /** - * Adds a notice to the UI notice box - * @param {string} txt - Message to add to notice list - */ - AddNoticeItem: function (txt) { - let ne = document.createElement('li'); - ne.innerHTML = txt; - document.querySelector('#page-notice ul').appendChild(ne); - } -}; - -setTimeout(App.Init); \ No newline at end of file diff --git a/src/js/Const.js b/src/js/Const.js deleted file mode 100644 index abb10c5..0000000 --- a/src/js/Const.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * @constant {string} - Stores the current app version - */ -const AppVersion = "1.0"; -/** - * @constant {string} - The hashing algo to use to verify the file - */ -const HashingAlgo = 'SHA-256'; -/** - * @constant {string} - The encryption algoritm to use for file uploads - */ -const EncryptionAlgo = 'AES-CBC'; -/** - * @constant {object} - The 'algo' argument for importing/exporting/generating keys - */ -const EncryptionKeyDetails = { name: EncryptionAlgo, length: 128 }; -/** - * @constant {object} - The 'algo' argument for importing/exporting/generating hmac keys - */ -const HMACKeyDetails = { name: 'HMAC', hash: HashingAlgo }; -/** - * @constant {number} - Size of 1 kiB - */ -const kiB = Math.pow(1024, 1); -/** - * @constant {number} - Size of 1 MiB - */ -const MiB = Math.pow(1024, 2); -/** - * @constant {number} - Size of 1 GiB - */ -const GiB = Math.pow(1024, 3); -/** - * @constant {number} - Size of 1 TiB - */ -const TiB = Math.pow(1024, 4); -/** - * @constant {number} - Size of 1 PiB - */ -const PiB = Math.pow(1024, 5); -/** - * @constant {number} - Size of 1 EiB - */ -const EiB = Math.pow(1024, 6); -/** - * @constant {number} - Size of 1 ZiB - */ -const ZiB = Math.pow(1024, 7); -/** - * @constant {number} - Size of 1 YiB - */ -const YiB = Math.pow(1024, 8); \ No newline at end of file diff --git a/src/js/index.js b/src/js/index.js new file mode 100644 index 0000000..2876ea6 --- /dev/null +++ b/src/js/index.js @@ -0,0 +1,3 @@ +import * as App from './modules/App.js'; + +App.Init(); \ No newline at end of file diff --git a/src/js/modules/App.js b/src/js/modules/App.js new file mode 100644 index 0000000..2c6e88f --- /dev/null +++ b/src/js/modules/App.js @@ -0,0 +1,222 @@ +import { Api, Utils, Log, $ } from './Util.js'; +import { ViewManager } from './ViewManager.js'; +import { DropzoneManager } from './DropzoneManager.js'; +import { FileUpload } from './FileUpload.js'; + +let ChartJsLoaded = false; + +const Elements = { + get Dropzone() { return $('#dropzone') }, + get Uploads() { return $('#uploads') }, + get PageView() { return $('#page-view') }, + get PageUpload() { return $('#page-upload') }, + get PageFaq() { return $('#page-faq') }, + get PageStats() { return $('#page-stats') }, + get PageDonate() { return $('#page-donate') } +}; + +const Templates = { + get Upload() { return $("template[id='tmpl-upload']") } +}; + +const Browser = { + get IsEdge() { + return /Edge/.test(navigator.userAgent); + }, + + get IsChrome() { + return !Browser.IsEdge && /^Mozilla.*Chrome/.test(navigator.userAgent); + }, + + get IsFirefox() { + return !Browser.IsEdge && /^Mozilla.*Firefox/.test(navigator.userAgent); + } +}; + +/** + * Uploads the files as selected by the input form + * @param {Element} ctx + * @returns {Promise} + */ +async function UploadFiles(ctx) { + let files = ctx.files; + let proc_files = []; + + for (let x = 0; x < files.length; x++) { + let fu = new FileUpload(files[x]); + proc_files[proc_files.length] = fu.ProcessUpload(); + } + + await Promise.all(proc_files); +} + +function ResetView() { + Elements.PageView.style.display = "none"; + Elements.PageUpload.style.display = "none"; + Elements.PageFaq.style.display = "none"; + Elements.PageStats.style.display = "none"; + Elements.PageDonate.style.display = "none"; +} + +async function ShowStats() { + location.hash = "#stats"; + ResetView(); + + if (!ChartJsLoaded) { + await InsertScript("//cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js", () => { + return typeof moment !== "undefined"; + }); + await InsertScript("//cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.3/Chart.min.js", () => { + return typeof Chart !== "undefined"; + }); + } + + let api_rsp = await Api.GetTxChart(); + if (api_rsp.ok) { + let ctx = $('#weektxgraph').getContext('2d'); + new Chart(ctx, { + type: 'line', + data: api_rsp.data, + options: { + scales: { + xAxes: [{ + type: 'time', + time: { + source: 'data' + } + }], + yAxes: [{ + ticks: { + beginAtZero: true, + callback: function(label, index, labels) { + return Utils.FormatBytes(label); + } + } + }] + } + } + }); + } + Elements.PageStats.style.display = "block"; +} + +function ShowFAQ() { + location.hash = "#faq"; + ResetView(); + Elements.PageFaq.style.display = "block"; +} + +function ShowDonate() { + location.hash = "#donate"; + ResetView(); + Elements.PageDonate.style.display = "block"; +} + +/** + * Sets up the page + */ +async function Init() { + CheckBrowserSupport(); + MakePolyfills(); + + ResetView(); + + if (location.hash !== "") { + if (location.hash == "#faq") { + ShowFAQ(); + } else if (location.hash == "#stats") { + ShowStats(); + } else if (location.hash == "#donate") { + ShowDonate(); + } else { + Elements.PageView.style.display = "block"; + let vm = new ViewManager(); + vm.LoadView(); + } + window.site_info = await Api.GetSiteInfo(); + } else { + window.site_info = await Api.GetSiteInfo(); + Elements.PageUpload.style.display = "block"; + $('#dropzone').innerHTML = `Click me!
(${Utils.FormatBytes(window.site_info.data.max_upload_size)} max)`; + new DropzoneManager(Elements.Dropzone); + } + + if (window.site_info.ok) { + let elms = document.querySelectorAll("#footer-stats div span"); + elms[0].textContent = window.site_info.data.basic_stats.Files; + elms[1].textContent = Utils.FormatBytes(window.site_info.data.basic_stats.Size, 2); + elms[2].textContent = Utils.FormatBytes(window.site_info.data.basic_stats.Transfer_24h, 2); + } + + let faq_headers = document.querySelectorAll('#page-faq .faq-header'); + for (let x = 0; x < faq_headers.length; x++) { + faq_headers[x].addEventListener('click', function () { + this.nextElementSibling.classList.toggle("show"); + }.bind(faq_headers[x])); + } +} + +/** + * Adds in polyfills for this browser + */ +function MakePolyfills() { + if (typeof TextEncoder === "undefined" || typeof TextDecoder === "undefined") { + InsertScript("//unpkg.com/text-encoding@0.6.4/lib/encoding-indexes.js"); + InsertScript("//unpkg.com/text-encoding@0.6.4/lib/encoding.js"); + } +} + +/** + * Adds a script tag at the top of the header + * @param {string} src - The script src url + * @param {function} fnWait - Function to use in promise to test if the script is loaded + */ +async function InsertScript(src, fnWait) { + var before = document.head.getElementsByTagName('script')[0]; + var newlink = document.createElement('script'); + newlink.src = src; + document.head.insertBefore(newlink, before); + + if (typeof fnWait === "function") { + await new Promise((resolve, reject) => { + let timer = setInterval(() => { + if (fnWait()) { + clearInterval(timer); + resolve(); + } + }, 100); + }); + } +} + +/** + * Checks browser version + */ +function CheckBrowserSupport() { + if (!Browser.IsFirefox) { + if (Browser.IsChrome) { + AddNoticeItem("Uploads bigger then 100MiB usually crash Chrome when uploading. Please upload with Firefox. Or check GitHub for tools."); + } + if (Browser.IsEdge) { + let edge_version = /Edge\/([0-9]{1,3}\.[0-9]{1,5})/.exec(navigator.userAgent)[1]; + Log.I(`Edge version is: ${edge_version}`); + if (parseFloat(edge_version) < 18.18218) { + AddNoticeItem("Upload progress isn't reported in the version of Edge you are using, see here for more info."); + } + } + + document.querySelector('#page-notice').style.display = "block"; + } +} + +/** + * Adds a notice to the UI notice box + * @param {string} txt - Message to add to notice list + */ +function AddNoticeItem(txt) { + let ne = document.createElement('li'); + ne.innerHTML = txt; + document.querySelector('#page-notice ul').appendChild(ne); +} + +export { Init, Templates }; \ No newline at end of file diff --git a/src/js/modules/Const.js b/src/js/modules/Const.js new file mode 100644 index 0000000..3658a84 --- /dev/null +++ b/src/js/modules/Const.js @@ -0,0 +1,59 @@ +/** + * Change Log + * 1.0 - https://github.com/v0l/void.cat/commit/b0a49e5bc28d62ebd954fe344c6f604b952e905c + * 1.1 - https://github.com/v0l/void.cat/commit/e3f7f2a59e86f86a27a06d8725f4f6401af7f190 + * 1.2 - TBC + */ + +/** + * @constant {string} - Stores the current app version + */ +export const AppVersion = require('../../../package.json').version; +/** + * @constant {string} - The hashing algo to use to verify the file + */ +export const HashingAlgo = 'SHA-256'; +/** + * @constant {string} - The encryption algoritm to use for file uploads + */ +export const EncryptionAlgo = 'AES-CBC'; +/** + * @constant {object} - The 'algo' argument for importing/exporting/generating keys + */ +export const EncryptionKeyDetails = { name: EncryptionAlgo, length: 128 }; +/** + * @constant {object} - The 'algo' argument for importing/exporting/generating hmac keys + */ +export const HMACKeyDetails = { name: 'HMAC', hash: HashingAlgo }; +/** + * @constant {number} - Size of 1 kiB + */ +export const kiB = Math.pow(1024, 1); +/** + * @constant {number} - Size of 1 MiB + */ +export const MiB = Math.pow(1024, 2); +/** + * @constant {number} - Size of 1 GiB + */ +export const GiB = Math.pow(1024, 3); +/** + * @constant {number} - Size of 1 TiB + */ +export const TiB = Math.pow(1024, 4); +/** + * @constant {number} - Size of 1 PiB + */ +export const PiB = Math.pow(1024, 5); +/** + * @constant {number} - Size of 1 EiB + */ +export const EiB = Math.pow(1024, 6); +/** + * @constant {number} - Size of 1 ZiB + */ +export const ZiB = Math.pow(1024, 7); +/** + * @constant {number} - Size of 1 YiB + */ +export const YiB = Math.pow(1024, 8); \ No newline at end of file diff --git a/src/js/DropzoneManager.js b/src/js/modules/DropzoneManager.js similarity index 87% rename from src/js/DropzoneManager.js rename to src/js/modules/DropzoneManager.js index f39809e..5289b64 100644 --- a/src/js/DropzoneManager.js +++ b/src/js/modules/DropzoneManager.js @@ -1,11 +1,13 @@ +import { FileUpload } from './FileUpload.js'; + /** * @constructor Creates an instance of the DropzoneManager * @param {HTMLElement} dz - Dropzone element */ -const DropzoneManager = function (dz) { +function DropzoneManager(dz) { this.dz = dz; - this.SetUI = function() { + this.SetUI = function () { document.querySelector('#page-upload div:nth-child(1)').removeAttribute("style"); document.querySelector('#uploads').removeAttribute("style"); }; @@ -27,4 +29,6 @@ const DropzoneManager = function (dz) { }; this.dz.addEventListener('click', this.OpenFileSelect.bind(this), false); -}; \ No newline at end of file +}; + +export { DropzoneManager }; \ No newline at end of file diff --git a/src/js/FileDownloader.js b/src/js/modules/FileDownloader.js similarity index 91% rename from src/js/FileDownloader.js rename to src/js/modules/FileDownloader.js index 84ec6c1..2b118f2 100644 --- a/src/js/FileDownloader.js +++ b/src/js/modules/FileDownloader.js @@ -1,3 +1,7 @@ +import * as Const from './Const.js'; +import { VBF } from './VBF.js'; +import { XHR, Utils, Log } from './Util.js'; + /** * File download and decryption class * @class @@ -5,7 +9,7 @@ * @param {string} key - The key to use for decryption * @param {string} iv - The IV to use for decryption */ -const FileDownloader = function (fileinfo, key, iv) { +function FileDownloader(fileinfo, key, iv) { this.fileinfo = fileinfo; this.key = key; this.iv = iv; @@ -113,11 +117,11 @@ const FileDownloader = function (fileinfo, key, iv) { let iv_raw = Utils.HexToArray(this.iv); Log.I(`${this.fileinfo.FileId} decrypting with key ${this.key} and iv ${this.iv}`); - let key = await crypto.subtle.importKey("raw", key_raw, EncryptionKeyDetails, false, ['decrypt']); - let keyhmac = await crypto.subtle.importKey("raw", key_raw, HMACKeyDetails, false, ['verify']); + let key = await crypto.subtle.importKey("raw", key_raw, Const.EncryptionKeyDetails, false, ['decrypt']); + let keyhmac = await crypto.subtle.importKey("raw", key_raw, Const.HMACKeyDetails, false, ['verify']); let enc_data = VBF.GetEncryptedPart(header.version, blob); - let decrypted_file = await crypto.subtle.decrypt({ name: EncryptionAlgo, iv: iv_raw }, key, enc_data); + let decrypted_file = await crypto.subtle.decrypt({ name: Const.EncryptionAlgo, iv: iv_raw }, key, enc_data); //read the header let json_header_length = new Uint16Array(decrypted_file.slice(0, 2))[0]; @@ -126,7 +130,7 @@ const FileDownloader = function (fileinfo, key, iv) { //hash the file to verify let file_data = decrypted_file.slice(2 + json_header_length); - let hmac_verify = await crypto.subtle.verify(HMACKeyDetails, keyhmac, header.hmac, file_data); + let hmac_verify = await crypto.subtle.verify(Const.HMACKeyDetails, keyhmac, header.hmac, file_data); if (hmac_verify) { Log.I(`${this.fileinfo.FileId} HMAC verified!`); @@ -136,4 +140,6 @@ const FileDownloader = function (fileinfo, key, iv) { throw "HMAC verify failed"; } }; -}; \ No newline at end of file +}; + +export { FileDownloader }; \ No newline at end of file diff --git a/src/js/FileUpload.js b/src/js/modules/FileUpload.js similarity index 88% rename from src/js/FileUpload.js rename to src/js/modules/FileUpload.js index 1b63ddc..f439fb1 100644 --- a/src/js/FileUpload.js +++ b/src/js/modules/FileUpload.js @@ -1,10 +1,16 @@ +import * as Const from './Const.js'; +import { Templates } from './App.js'; +import { XHR, Utils, Log, $ } from './Util.js'; +import { VBF } from './VBF.js'; +import { bytes_to_base64 } from 'asmcrypto.js'; + /** * File upload handler class * @class * @param {File} file - The file handle to upload * @param {string} host - The hostname to upload to */ -const FileUpload = function (file, host) { +function FileUpload(file, host) { this.hasCrypto = typeof window.crypto.subtle === "object"; this.file = file; this.host = host; @@ -46,6 +52,23 @@ const FileUpload = function (file, host) { return `${await this.HexKey()}:${this.HexIV()}`; }; + /** + * Retruns the formatted hash fragment for this upload + * @returns {Promise} The id:key:iv concatenated and converted to base64 + */ + this.FormatUrl = async (id) => { + let id_hex = new Uint8Array(Utils.HexToArray(id)); + let key = new Uint8Array(await crypto.subtle.exportKey('raw', this.key)); + let iv = new Uint8Array(this.iv); + + let ret = new Uint8Array(id_hex.byteLength + key.byteLength + iv.byteLength); + ret.set(id_hex, 0); + ret.set(key, id_hex.byteLength); + ret.set(iv, id_hex.byteLength + key.byteLength); + + return bytes_to_base64(ret); + }; + /** * Loads the file and SHA256 hashes it * @return {Promise} @@ -64,7 +87,7 @@ const FileUpload = function (file, host) { fr.onload = function (ev) { this.HandleProgress('state-hash-start'); - crypto.subtle.sign(HMACKeyDetails, this.hmackey, ev.target.result).then(function (hash) { + crypto.subtle.sign(Const.HMACKeyDetails, this.hmackey, ev.target.result).then(function (hash) { this.HandleProgress('state-hash-end'); resolve({ hash: hash, @@ -189,7 +212,7 @@ const FileUpload = function (file, host) { * Creates a template for the upload to show progress */ this.CreateNode = function () { - let nelm = document.importNode(App.Templates.Upload.content, true); + let nelm = document.importNode(Templates.Upload.content, true); nelm.filename = nelm.querySelector('.file-info .file-info-name'); nelm.filesize = nelm.querySelector('.file-info .file-info-size'); @@ -213,8 +236,8 @@ const FileUpload = function (file, host) { * @returns {Promise} The new key */ this.GenerateKey = async function () { - this.key = await crypto.subtle.generateKey(EncryptionKeyDetails, true, ['encrypt', 'decrypt']); - this.hmackey = await crypto.subtle.importKey("raw", await crypto.subtle.exportKey('raw', this.key), HMACKeyDetails, false, ["sign"]); + this.key = await crypto.subtle.generateKey(Const.EncryptionKeyDetails, true, ['encrypt', 'decrypt']); + this.hmackey = await crypto.subtle.importKey("raw", await crypto.subtle.exportKey('raw', this.key), Const.HMACKeyDetails, false, ["sign"]); crypto.getRandomValues(this.iv); @@ -230,7 +253,7 @@ const FileUpload = function (file, host) { this.EncryptFile = async function (fileData) { this.HandleProgress('state-encrypt-start'); let encryptedData = await crypto.subtle.encrypt({ - name: EncryptionAlgo, + name: Const.EncryptionAlgo, iv: this.iv }, this.key, fileData); this.HandleProgress('state-encrypt-end'); @@ -288,7 +311,7 @@ const FileUpload = function (file, host) { Log.I(`${this.file.name} hash is: ${h256}`); //create blob for encryption - let header_data = new TextEncoder().encode(header); + let header_data = new TextEncoder('utf-8').encode(header); Log.I(`Using header: ${header} (length=${header_data.byteLength})`); let encryption_payload = new Uint8Array(2 + header_data.byteLength + hash_data.data.byteLength); @@ -315,7 +338,7 @@ const FileUpload = function (file, host) { let nl = document.createElement("a"); nl.target = "_blank"; - nl.href = `${window.location.protocol}//${window.location.host}/#${uploadResult.id}:${await this.TextKey()}`; + nl.href = `${window.location.protocol}//${window.location.host}/#${await this.FormatUrl(uploadResult.id)}`; nl.textContent = this.file.name; this.domNode.links.appendChild(nl); } else { @@ -324,3 +347,5 @@ const FileUpload = function (file, host) { } }; }; + +export { FileUpload }; \ No newline at end of file diff --git a/src/js/Util.js b/src/js/modules/Util.js similarity index 77% rename from src/js/Util.js rename to src/js/modules/Util.js index cd3749d..d6d3569 100644 --- a/src/js/Util.js +++ b/src/js/modules/Util.js @@ -1,14 +1,16 @@ +import * as Const from './Const.js'; + /** * @constant {function} - Helper function for document.querySelector * @param {string} selector - The selector to use in the query * @returns {HTMLElement} The first selected element */ -const $ = (selector) => document.querySelector(selector); +export const $ = (selector) => document.querySelector(selector); -const Log = { - I: (msg) => console.log(`[App_v ${AppVersion}][I]: ${msg}`), - W: (msg) => console.warn(`[App_v ${AppVersion}][W]: ${msg}`), - E: (msg) => console.error(`[App_v ${AppVersion}][E]: ${msg}`) +export const Log = { + I: (msg) => console.log(`[App_v ${Const.AppVersion}][I]: ${msg}`), + W: (msg) => console.warn(`[App_v ${Const.AppVersion}][W]: ${msg}`), + E: (msg) => console.error(`[App_v ${Const.AppVersion}][E]: ${msg}`) }; /** @@ -18,7 +20,7 @@ const Log = { * @param {[object]} data - Request payload (method must be post) * @returns {Promise} The completed request */ -const JsonXHR = async function (method, url, data) { +export async function JsonXHR(method, url, data) { return await XHR(method, url, JSON.stringify(data), { 'Content-Type': 'application/json' }); @@ -35,7 +37,7 @@ const JsonXHR = async function (method, url, data) { * @param {[function]} editrequest - Function that can edit the request before its sent * @returns {Promise} The completed request */ -const XHR = function (method, url, data, headers, uploadprogress, downloadprogress, editrequest) { +export function XHR(method, url, data, headers, uploadprogress, downloadprogress, editrequest) { return new Promise(function (resolve, reject) { let x = new XMLHttpRequest(); x.onreadystatechange = function (ev) { @@ -76,7 +78,7 @@ const XHR = function (method, url, data, headers, uploadprogress, downloadprogre /** * Calls api handler */ -const Api = { +export const Api = { DoRequest: async function (req) { return JSON.parse((await JsonXHR('POST', '/api', req)).response); }, @@ -118,7 +120,7 @@ const Api = { /** * Generic util functions */ -const Utils = { +export const Utils = { /** * Formats an ArrayBuffer to hex * @param {ArrayBuffer} buffer - Input data to convert to hex @@ -149,22 +151,22 @@ const Utils = { */ FormatBytes: (b, f) => { f = typeof f === 'number' ? 2 : f; - if (b >= YiB) - return (b / YiB).toFixed(f) + ' YiB'; - if (b >= ZiB) - return (b / ZiB).toFixed(f) + ' ZiB'; - if (b >= EiB) - return (b / EiB).toFixed(f) + ' EiB'; - if (b >= PiB) - return (b / PiB).toFixed(f) + ' PiB'; - if (b >= TiB) - return (b / TiB).toFixed(f) + ' TiB'; - if (b >= GiB) - return (b / GiB).toFixed(f) + ' GiB'; - if (b >= MiB) - return (b / MiB).toFixed(f) + ' MiB'; - if (b >= kiB) - return (b / kiB).toFixed(f) + ' KiB'; + if (b >= Const.YiB) + return (b / Const.YiB).toFixed(f) + ' YiB'; + if (b >= Const.ZiB) + return (b / Const.ZiB).toFixed(f) + ' ZiB'; + if (b >= Const.EiB) + return (b / Const.EiB).toFixed(f) + ' EiB'; + if (b >= Const.PiB) + return (b / Const.PiB).toFixed(f) + ' PiB'; + if (b >= Const.TiB) + return (b / Const.TiB).toFixed(f) + ' TiB'; + if (b >= Const.GiB) + return (b / Const.GiB).toFixed(f) + ' GiB'; + if (b >= Const.MiB) + return (b / Const.MiB).toFixed(f) + ' MiB'; + if (b >= Const.kiB) + return (b / Const.kiB).toFixed(f) + ' KiB'; return b.toFixed(f) + ' B' } }; \ No newline at end of file diff --git a/src/js/VBF.js b/src/js/modules/VBF.js similarity index 99% rename from src/js/VBF.js rename to src/js/modules/VBF.js index 9df0310..3012af1 100644 --- a/src/js/VBF.js +++ b/src/js/modules/VBF.js @@ -84,4 +84,6 @@ const VBF = { }; } } -}; \ No newline at end of file +}; + +export { VBF }; \ No newline at end of file diff --git a/src/js/ViewManager.js b/src/js/modules/ViewManager.js similarity index 85% rename from src/js/ViewManager.js rename to src/js/modules/ViewManager.js index f9da2b0..da3d242 100644 --- a/src/js/ViewManager.js +++ b/src/js/modules/ViewManager.js @@ -1,16 +1,27 @@ +import { Api, Utils, Log, $ } from './Util.js'; +import { FileDownloader } from './FileDownloader.js'; +import { base64_to_bytes } from 'asmcrypto.js'; + /** * @constructor Creates an instance of the ViewManager */ -const ViewManager = function () { +export function ViewManager() { this.id = null; this.key = null; this.iv = null; this.ParseUrlHash = function () { - let hs = window.location.hash.substr(1).split(':'); - this.id = hs[0]; - this.key = hs[1]; - this.iv = hs[2]; + if (window.location.hash.indexOf(':') !== -1) { + let hs = window.location.hash.substr(1).split(':'); + this.id = hs[0]; + this.key = hs[1]; + this.iv = hs[2]; + } else if (window.location.hash.length === 73) { //base64 encoded #id:key:iv + let hs = base64_to_bytes(window.location.hash.substr(1)); + this.id = Utils.ArrayToHex(hs.slice(0, 20)); + this.key = Utils.ArrayToHex(hs.slice(20, 36)); + this.iv = Utils.ArrayToHex(hs.slice(36)); + } }; this.LoadView = async function () { @@ -104,6 +115,4 @@ const ViewManager = function () { document.body.appendChild(st); } }; - - this.LoadView(); }; \ No newline at end of file diff --git a/src/js/autodownloader.js b/src/js/modules/autodownloader.js similarity index 100% rename from src/js/autodownloader.js rename to src/js/modules/autodownloader.js diff --git a/src/php/filestore.php b/src/php/filestore.php index b53c98e..26632d4 100644 --- a/src/php/filestore.php +++ b/src/php/filestore.php @@ -46,10 +46,16 @@ return "$this->DocumentRoot/$this->UploadFolder"; } + /** + * $id should be the ripemd160(hmac-sha256(file, key)) as hex + */ public function GetRelativeFilePath($id) : string { - return "$this->UploadFolder/$id"; + return $this->UploadFolder . "/" . $id; } + /** + * $id should be the ripemd160(hmac-sha256(file, key)) as hex + */ public function GetAbsoluteFilePath($id) : string { return $this->GetUploadDirAbsolute() . "/" . $id; } @@ -94,7 +100,7 @@ } public function StoreV1File($bf, $file) : ?string { - $id = gmp_strval(gmp_init("0x" . hash(Config::$Instance->public_hash_algo, $bf->Hash)), 62); + $id = hash(Config::$Instance->public_hash_algo, $bf->Hash); $input = fopen($file, "rb"); $res = $this->StoreFile($input, $id); @@ -115,7 +121,7 @@ $hash = unpack("H64hash256", fread($input_temp, 32)); fclose($input_temp); - $id = gmp_strval(gmp_init("0x" . hash(Config::$Instance->public_hash_algo, $hash["hash256"])), 62); + $id = hash(Config::$Instance->public_hash_algo, $hash["hash256"]); $file_path = $this->GetAbsoluteFilePath($id); if(!file_exists($file_path)){ rename($temp_name, $file_path); @@ -124,6 +130,9 @@ return null; } + /** + * $id should be formatted base62 filename + */ public function GetFileSize($id) : int { return filesize($this->GetAbsoluteFilePath($id)); }