"use strict"; // error handler for mobile devices function hcroak(msg) { document.body.innerHTML = msg; window.onerror = undefined; throw 'fatal_err'; } function croak(msg) { document.body.textContent = msg; window.onerror = undefined; throw msg; } function esc(txt) { return txt.replace(/[&"<>]/g, function (c) { return { '&': '&', '"': '"', '<': '<', '>': '>' }[c]; }); } window.onerror = function (msg, url, lineNo, columnNo, error) { window.onerror = undefined; var html = ['

you hit a bug!

please screenshot this error and send me a copy arigathanks gozaimuch (ed/irc.rizon.net or ed#2644)

', esc(String(msg)), '

', esc(url + ' @' + lineNo + ':' + columnNo), '

']; if (error) { var find = ['desc', 'stack', 'trace']; for (var a = 0; a < find.length; a++) if (String(error[find[a]]) !== 'undefined') html.push('

' + find[a] + '

' + esc(String(error[find[a]])).replace(/\n/g, '
\n')); } document.body.style.fontSize = '0.8em'; hcroak(html.join('\n')); }; // https://stackoverflow.com/a/950146 function import_js(url, cb) { var head = document.head || document.getElementsByTagName('head')[0]; var script = document.createElement('script'); script.type = 'text/javascript'; script.src = url; script.onreadystatechange = cb; script.onload = cb; head.appendChild(script); } function o(id) { return document.getElementById(id); } (function () { // chrome requires https to use crypto.subtle, // usually it's undefined but some chromes throw on invoke try { crypto.subtle.digest( 'SHA-512', new Uint8Array(1) ).then( function (x) { up2k_init(true) }, function (x) { up2k_init(false) } ); } catch (ex) { up2k_init(false); } })(); function up2k_init(have_crypto) { //have_crypto = false; // show modal message function showmodal(msg) { o('u2notbtn').innerHTML = msg; o('u2btn').style.display = 'none'; o('u2notbtn').style.display = 'block'; o('u2conf').style.opacity = '0.5'; } // hide modal message function unmodal() { o('u2notbtn').style.display = 'none'; o('u2btn').style.display = 'block'; o('u2conf').style.opacity = '1'; o('u2notbtn').innerHTML = ''; } var post_url = o('bup').getElementsByTagName('form')[0].getAttribute('action'); var shame = 'your browser disables sha512 unless you use https' var is_https = (window.location + '').indexOf('https:') === 0; if (is_https) // chrome<37 firefox<34 edge<12 ie<11 opera<24 safari<10.1 shame = 'your browser is impressively ancient' // upload ui hidden by default, clicking the header shows it o('u2tgl').onclick = function (e) { e.preventDefault(); o('u2tgl').style.display = 'none'; o('u2body').style.display = 'block'; if (!have_crypto && !window.asmCrypto) { showmodal('

loading sha512.js

since ' + shame + '

thanks chrome

'); import_js('/.cpr/deps/sha512.js', unmodal); if (is_https) o('u2foot').innerHTML = shame + ' so this uploader will do like 500kB/s at best'; else o('u2foot').innerHTML = 'seems like ' + shame + ' so do that if you want more performance'; } }; // shows or clears an error message in the basic uploader ui function setmsg(msg) { if (msg !== undefined) { o('u2err').setAttribute('class', 'err'); o('u2err').innerHTML = msg; } else { o('u2err').setAttribute('class', ''); o('u2err').innerHTML = ''; } } // switches to the basic uploader with msg as error message function un2k(msg) { o('up2k').style.display = 'none'; o('bup').style.display = 'block'; setmsg(msg); } // handle user intent to use the basic uploader instead o('u2nope').onclick = function (e) { e.preventDefault(); un2k(); }; if (!String.prototype.format) { String.prototype.format = function () { var args = arguments; return this.replace(/{(\d+)}/g, function (match, number) { return typeof args[number] != 'undefined' ? args[number] : match; }); }; } function cfg(name) { var val = localStorage.getItem(name); if (val === null) return parseInt(o(name).value); o(name).value = val return val; } var parallel_uploads = cfg('nthread'); var col_hashing = '#0099ff'; var col_hashed = '#004466'; var col_uploading = '#ffcc44'; var col_uploaded = '#00bb00'; var fdom_ctr = 0; var st = { "files": [], "todo": { "hash": [], "handshake": [], "upload": [] }, "busy": { "hash": [], "handshake": [], "upload": [] } }; var bobslice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice; if (!bobslice || !window.FileReader || !window.FileList) return un2k("this is the basic uploader; up2k needs at least
chrome 21 // firefox 13 // edge 12 // opera 12 // safari 5.1"); // probably safe now o('up2k').style.display = 'block'; o('bup').style.display = 'none'; function nav() { o('file' + fdom_ctr).click(); } o('u2btn').addEventListener('click', nav, false); function ondrag(ev) { ev.stopPropagation(); ev.preventDefault(); ev.dataTransfer.dropEffect = 'copy'; ev.dataTransfer.effectAllowed = 'copy'; } o('u2btn').addEventListener('dragover', ondrag, false); o('u2btn').addEventListener('dragenter', ondrag, false); function gotfile(ev) { ev.stopPropagation(); ev.preventDefault(); var files; var is_itemlist = false; if (ev.dataTransfer) { if (ev.dataTransfer.items) { files = ev.dataTransfer.items; // DataTransferItemList is_itemlist = true; } else files = ev.dataTransfer.files; // FileList } else files = ev.target.files; if (files.length == 0) return alert('no files selected??'); more_one_file(); for (var a = 0; a < files.length; a++) { var fobj = files[a]; if (is_itemlist) { if (fobj.kind !== 'file') continue; fobj = fobj.getAsFile(); } try { if (fobj.size < 1) throw 1; } catch (ex) { return alert( 'Due to a browser bug, Firefox-Android can only select one file at a time. ' + 'This works in "Firefox Preview" (new Firefox, currently in beta).\n\n' + 'Google firefox bug 1456557 for more info'); } var entry = { "n": parseInt(st.files.length.toString()), "t0": new Date().getTime(), // TODO remove probably "fobj": fobj, "name": fobj.name, "size": fobj.size, "hash": [] }; var skip = false; for (var b = 0; b < st.files.length; b++) if (entry.name == st.files[b].name && entry.size == st.files[b].size) skip = true; if (skip) continue; var tr = document.createElement('tr'); tr.innerHTML = 'hashing'.format(st.files.length); tr.getElementsByTagName('td')[0].textContent = entry.name; o('u2tab').appendChild(tr); st.files.push(entry); st.todo.hash.push(entry); } } o('u2btn').addEventListener('drop', gotfile, false); function more_one_file() { fdom_ctr++; var elm = document.createElement('div') elm.innerHTML = ''.format(fdom_ctr); o('u2form').appendChild(elm); o('file' + fdom_ctr).addEventListener('change', gotfile, false); } more_one_file(); ///// //// /// actuator // var tasker = (function () { var mutex = false; function taskerd() { if (mutex) return; mutex = true; while (true) { var mou_ikkai = false; if (st.todo.hash.length > 0 && st.busy.hash.length == 0) { exec_hash(); mou_ikkai = true; } if (st.todo.handshake.length > 0 && st.busy.handshake.length == 0 && st.busy.upload.length < parallel_uploads) { exec_handshake(); mou_ikkai = true; } if (st.todo.upload.length > 0 && st.busy.upload.length < parallel_uploads) { exec_upload(); mou_ikkai = true; } if (!mou_ikkai) { setTimeout(taskerd, 100); mutex = false; return; } } } taskerd(); return taskerd; })(); ///// //// /// hashing // // https://gist.github.com/jonleighton/958841 function buf2b64_maybe_fucky(buffer) { var ret = ''; var view = new DataView(buffer); for (var i = 0; i < view.byteLength; i++) { ret += String.fromCharCode(view.getUint8(i)); } return window.btoa(ret).replace( /\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); } // https://gist.github.com/jonleighton/958841 function buf2b64(arrayBuffer) { var base64 = ''; var encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; var bytes = new Uint8Array(arrayBuffer); var byteLength = bytes.byteLength; var byteRemainder = byteLength % 3; var mainLength = byteLength - byteRemainder; var a, b, c, d; var chunk; for (var i = 0; i < mainLength; i = i + 3) { chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2]; // create 8*3=24bit segment then split into 6bit segments a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18 b = (chunk & 258048) >> 12; // 258048 = (2^6 - 1) << 12 c = (chunk & 4032) >> 6; // 4032 = (2^6 - 1) << 6 d = chunk & 63; // 63 = 2^6 - 1 // Convert the raw binary segments to the appropriate ASCII encoding base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d]; } if (byteRemainder == 1) { chunk = bytes[mainLength]; a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2 b = (chunk & 3) << 4; // 3 = 2^2 - 1 (zero 4 LSB) base64 += encodings[a] + encodings[b];//+ '=='; } else if (byteRemainder == 2) { chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1]; a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10 b = (chunk & 1008) >> 4; // 1008 = (2^6 - 1) << 4 c = (chunk & 15) << 2; // 15 = 2^4 - 1 (zero 2 LSB) base64 += encodings[a] + encodings[b] + encodings[c];//+ '='; } return base64; } // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest function buf2hex(buffer) { var hexCodes = []; var view = new DataView(buffer); for (var i = 0; i < view.byteLength; i += 4) { var value = view.getUint32(i) // 4 bytes per iter var stringValue = value.toString(16) // doesn't pad var padding = '00000000' var paddedValue = (padding + stringValue).slice(-padding.length) hexCodes.push(paddedValue); } return hexCodes.join(""); } function get_chunksize(filesize) { var chunksize = 1024 * 1024; var stepsize = 512 * 1024; while (true) { for (var mul = 1; mul <= 2; mul++) { var nchunks = Math.ceil(filesize / chunksize); if (nchunks <= 256) return chunksize; chunksize += stepsize; stepsize *= mul; } } } function exec_hash() { var t = st.todo.hash.shift(); st.busy.hash.push(t); var nchunk = 0; var chunksize = get_chunksize(t.size); var nchunks = Math.ceil(t.size / chunksize); var pb_html = ''; var pb_perc = 99.9 / nchunks; for (var a = 0; a < nchunks; a++) pb_html += '
'.format( t.n, a, pb_perc); o('f{0}p'.format(t.n)).innerHTML = pb_html; var segm_next = function () { var reader = new FileReader(); reader.onload = segm_load; reader.onerror = segm_err; var car = nchunk * chunksize; var cdr = car + chunksize; if (cdr >= t.size) cdr = t.size; reader.readAsArrayBuffer( bobslice.call(t.fobj, car, cdr)); prog(t.n, nchunk, col_hashing); }; var segm_load = function (ev) { var filebuf = ev.target.result; var hashbuf; if (have_crypto) crypto.subtle.digest('SHA-512', filebuf).then(hash_done); else { var ofs = 0; var eof = filebuf.byteLength; var hasher = new asmCrypto.Sha512(); //hasher.process(new Uint8Array(filebuf)); while (ofs < eof) { // saves memory, doesn't affect perf var ofs2 = Math.min(eof, ofs + 1024 * 1024); hasher.process(new Uint8Array(filebuf.slice(ofs, ofs2))); ofs = ofs2; } hasher.finish(); hash_done(hasher.result); } }; var hash_done = function (hashbuf) { t.hash.push(buf2b64(hashbuf.slice(0, 32)).replace(/=$/, '')); prog(t.n, nchunk, col_hashed); if (++nchunk < nchunks) { prog(t.n, nchunk, col_hashing); return segm_next(); } t.t1 = new Date().getTime(); if (t.n == 0) { // TODO remove var spd = (t.size / ((t.t1 - t.t0) / 1000.)) / (1024 * 1024.); alert('{0} ms, {1} MB/s\n'.format(t.t1 - t.t0, spd.toFixed(3)) + t.hash.join('\n')); } o('f{0}t'.format(t.n)).innerHTML = 'connecting'; st.busy.hash.splice(st.busy.hash.indexOf(t), 1); st.todo.handshake.push(t); }; var segm_err = function () { alert('y o u b r o k e i t\n\n(was that a folder? just files please)'); }; segm_next(); } ///// //// /// handshake // function exec_handshake() { var t = st.todo.handshake.shift(); st.busy.handshake.push(t); var xhr = new XMLHttpRequest(); xhr.onload = function (ev) { if (xhr.status == 200) { var response = JSON.parse(xhr.responseText); t.postlist = []; t.wark = response.wark; var missing = response.hash; for (var a = 0; a < missing.length; a++) { var idx = t.hash.indexOf(missing[a]); if (idx < 0) return alert('wtf negative index for hash "{0}" in task:\n{1}'.format( missing[a], JSON.stringify(t))); t.postlist.push(idx); } for (var a = 0; a < t.hash.length; a++) prog(t.n, a, (t.postlist.indexOf(a) == -1) ? col_uploaded : col_hashed); var done = true; var msg = 'completed'; if (t.postlist.length > 0) { for (var a = 0; a < t.postlist.length; a++) st.todo.upload.push({ 'nfile': t.n, 'npart': t.postlist[a] }); msg = 'uploading'; done = false; } o('f{0}t'.format(t.n)).innerHTML = msg; st.busy.handshake.splice(st.busy.handshake.indexOf(t), 1); if (done) { var spd1 = (t.size / ((t.t1 - t.t0) / 1000.)) / (1024 * 1024.); var spd2 = (t.size / ((t.t2 - t.t1) / 1000.)) / (1024 * 1024.); o('f{0}p'.format(t.n)).innerHTML = '🎷🐛 (hash {0} MB/s | upload {1} MB/s)'.format( spd1.toFixed(2), spd2.toFixed(2)); } } else alert("server broke (error {0}):\n\"{1}\"\n".format( xhr.status, (xhr.response && xhr.response.err) || (xhr.responseText && xhr.responseText) || "no further information")); }; xhr.open('POST', post_url + '/handshake.php', true); xhr.responseType = 'text'; xhr.send(JSON.stringify({ "name": t.name, "size": t.size, "hash": t.hash })); } ///// //// /// upload // function exec_upload() { var upt = st.todo.upload.shift(); st.busy.upload.push(upt); var npart = upt.npart; var t = st.files[upt.nfile]; prog(t.n, npart, col_uploading); var chunksize = get_chunksize(t.size); var car = npart * chunksize; var cdr = car + chunksize; if (cdr >= t.size) cdr = t.size; var reader = new FileReader(); reader.onerror = function () { alert('y o u b r o k e i t\n\n(was that a folder? just files please)'); }; reader.onload = function (ev) { var xhr = new XMLHttpRequest(); xhr.upload.onprogress = function (xev) { var perc = xev.loaded / (cdr - car) * 100; prog(t.n, npart, '', perc); }; xhr.onload = function (xev) { if (xhr.status == 200) { prog(t.n, npart, col_uploaded); st.busy.upload.splice(st.busy.upload.indexOf(upt), 1); t.postlist.splice(t.postlist.indexOf(npart), 1); if (t.postlist.length == 0) { t.t2 = new Date().getTime(); o('f{0}t'.format(t.n)).innerHTML = 'verifying'; st.todo.handshake.push(t); } tasker(); } else alert("server broke (error {0}):\n\"{1}\"\n".format( xhr.status, (xhr.response && xhr.response.err) || (xhr.responseText && xhr.responseText) || "no further information")); }; xhr.open('POST', post_url + '/chunkpit.php', true); //xhr.setRequestHeader("X-Up2k-Hash", t.hash[npart].substr(1) + "x"); xhr.setRequestHeader("X-Up2k-Hash", t.hash[npart]); xhr.setRequestHeader("X-Up2k-Wark", t.wark); xhr.setRequestHeader('Content-Type', 'application/octet-stream'); xhr.overrideMimeType('Content-Type', 'application/octet-stream'); xhr.responseType = 'text'; xhr.send(ev.target.result); }; reader.readAsArrayBuffer(bobslice.call(t.fobj, car, cdr)); } ///// //// /// progress bar // function prog(nfile, nchunk, color, percent) { var n1 = o('f{0}p{1}'.format(nfile, nchunk)); var n2 = n1.getElementsByTagName('div')[0]; if (percent === undefined) { n1.style.background = color; n2.style.display = 'none'; } else { n2.style.width = percent + '%'; n2.style.display = 'block'; } } ///// //// /// config ui // function bumpthread(dir) { try { dir.stopPropagation(); dir.preventDefault(); } catch (ex) { } var obj = o('nthread'); if (dir.target) { obj.style.background = '#922'; var v = Math.floor(parseInt(obj.value)); if (v < 1 || v > 8 || v !== v) return; parallel_uploads = v; localStorage.setItem('nthread', v); obj.style.background = '#444'; return; } parallel_uploads += dir; if (parallel_uploads < 1) parallel_uploads = 1; if (parallel_uploads > 8) parallel_uploads = 8; obj.value = parallel_uploads; bumpthread({ "target": 1 }) } function nop(ev) { ev.preventDefault(); this.click(); } o('nthread_add').onclick = function (ev) { ev.preventDefault(); bumpthread(1); }; o('nthread_sub').onclick = function (ev) { ev.preventDefault(); bumpthread(-1); }; o('nthread').addEventListener('input', bumpthread, false); var nodes = o('u2conf').getElementsByTagName('a'); for (var a = nodes.length - 1; a >= 0; a--) nodes[a].addEventListener('touchend', nop, false); bumpthread({ "target": 1 }) }