<?php
require_once('common.php');
if (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on")
{
	header('HTTP/1.1 301 Moved Permanently');
	header("Location: https://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
	exit(0);
}
header('Content-Type: text/html; charset=UTF-8');

?><!DOCTYPE html><html lang="en"><head>
	<meta charset="utf-8">
	<title>up2k</title>
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=0.9">
	<style>
html {
	color: #444;
	background: #f7f7f7;
	font-family: sans-serif;
	line-height: 1.3em;
}
#head {
	margin-top: 2em;
	text-align: center;
}
a {
	color: #059;
}
#beta {
	color: #800;
	text-shadow: .03em -.15em .25em #fa0;
	transform: rotate(-10deg);
	display: inline-block;
	position: relative;
	top: -.1em;
	right: -.1em;
}
#form {
	width: 2px;
	height: 2px;
	overflow: hidden;
}
#fancy {
	color: #400;
	background: #fceabb;
	background: -moz-linear-gradient(top, #fceabb 0%, #fccd4d 50%, #f8b500 51%, #fbdf93 100%);
	background: -webkit-linear-gradient(top, #fceabb 0%,#fccd4d 50%,#f8b500 51%,#fbdf93 100%);
	background: linear-gradient(to bottom, #fceabb 0%,#fccd4d 50%,#f8b500 51%,#fbdf93 100%);
	filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fceabb', endColorstr='#fbdf93',GradientType=0 );
	text-shadow: 0 1px 1px #ff0;
	text-decoration: none;
	line-height: 1.5em;
	border: 1px solid #f0f;
	border-color: #ccc #c4c4c4 #c4c4c4 #ccc;
	border-radius: .4em;
	font-weight: bold;
	text-align: center;
	font-size: 2em;
	margin: 1em auto;
	padding: 1em 0;
	width: 15em;
	cursor: pointer;
	box-shadow: .4em .4em 0 #ddd;
}
#js {
	color: #530;
	background: #fc6;
	text-align: center;
	position: fixed;
	padding-top: 2em;
	top: 0;
	left: 0;
	right: 0;
	bottom: 0;
	width: 100%;
	height: 100%;
	z-index: 9001;
}
#js a {
	color: #059;
}
h3 {
	border-bottom: 1px solid #999;
	margin: 2em 1em 0 1em;
	font-size: 1em;
}
h6 {
	color: #777;
	background: #fafafa;
	width: 40em;
	padding: 0;
	margin: 2em auto .5em auto;
	font-size: .8em;
	font-weight: normal;
	font-style: italic;
	border: 1px solid #eee;
	border-width: 1px 0;
	box-shadow: 0 0 1em #fff;
}
#tab {
	margin: 3em auto;
	width: calc(100% - 2em);
	max-width: 100em;
}
#tab td {
	border: 1px solid #ccc;
	border-width: 0 0px 1px 0;
	padding: .1em .3em;
}
#tab td:nth-child(2) {
	width: 5em;
}
#tab td:nth-child(3) {
	width: 40%;
}
#tab tr+tr:hover td {
	background: #fea;
}
#config {
	margin: 1em auto;
	width: 30em;
}
#config * {
	text-align: center;
	line-height: 1em;
	margin: 0;
	padding: 0;
	border: none;
	outline: none;
}
#config input {
	width: 4em;
	background: #fff;
	font-size: 1.2em;
	padding: .15em 0;
	border: 1px solid #bbb;
}
#config a {
	color: #fff;
	background: #a36; #369;
	text-decoration: none;
	border-radius: .1em;
	font-size: 1.5em;
	padding: .1em 0;
	margin: 0 -.25em;
	width: 1.5em;
	height: 1em;
	display: inline-block;
	position: relative;
	line-height: 1em;
	bottom: -.08em;
}
#config input+a {
	background: #d80; #497;
}
.prog>div {
	display: inline-block;
	position: relative;
	overflow: hidden;
	margin: 0;
	padding: 0;
	height: 1.1em;
	margin-bottom: -.15em;
	box-shadow: -.1em -.1em .2em inset rgba(0,0,0,0.2);
}
.prog>div>div {
	width: 0%;
	position: absolute;
	left: 0;
	top: 0;
	bottom: 0;
	background: #0a0;
}
</style></head><body>





<div id="head">
	<h1>this meme is hella <span id="beta">beta</span></h1>
	<h2>if it breaks use the <a href="/up1">old uploader</a></h2>
	<h6>however if that happens mid-upload please refresh the web sight and<br />try the same files again, it will continue where it left off probably<br />( just make sure to use the same file split value)</h6>
</div>

<div id="js">
	<h1>enable javascript</h1>
	<h2>or use the <a href="/up1">old uploader</a></h2>
</div>

<form id="form" method="POST" enctype="multipart/form-data" onsubmit="return false;"></form>

<table id="config">
	<tr>
		<td>parallel uploads</td>
		<td>file split (megabyte)</td>
	</tr>
	<tr>
		<td>
			<a href="#" onclick="nthread_sub()">&ndash;</a>
			<input id="nthread" value="2" />
			<a href="#" onclick="nthread_add()">+</a>
		</td>
		<td>
			<a href="#" onclick="chunksz_sub()">&ndash;</a>
			<input id="chunksz" value="8" />
			<a href="#" onclick="chunksz_add()">+</a>
		</td>
	</tr>
</table>

<div id="fancy">
	drop files here<br />
	(or click me)
</div>

<table id="tab"><tr><td>filename</td><td>status</td><td>progress</td></tr></table>





<script type="text/javascript">
"use strict";

function o(i) {
	return document.getElementById(i);
}

o('js').innerHTML = '<h1>your browser is too old</h1><h2>try the <a href="/up1">old uploader</a></h2>';

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 chunksize_mb = cfg('chunksz');
var chunksize = chunksize_mb * 1024 * 1024;

var col_hashing   = '#0099ff'; //'#d7d7d7';
//var col_hashed    = '#e8a6df'; //'#decb7f';
//var col_hashed    = '#0099ff';
var col_hashed    = '#eeeeee';
var col_uploading = '#ffcc44';
var col_uploaded  = '#00cc00';
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 && FileReader && FileList)
	o('js').innerHTML = '<h1>your browser is too old</h1><h3>(need chrome 37+ / firefox 34+ / edge 12+ / opera 24+ / safari 7+)</h3><h2>try the <a href="/up1">old uploader</a></h2>';

if (crypto.subtle.digest)
	o('js').style.display = 'none';

function nav() {
	o('file' + fdom_ctr).click();
}
o('fancy').addEventListener('click', nav, false);

function ondrag(ev) {
	ev.stopPropagation();
	ev.preventDefault();
	ev.dataTransfer.dropEffect = 'copy';
	ev.dataTransfer.effectAllowed = 'copy';
}
o('fancy').addEventListener('dragover', ondrag, false);
o('fancy').addEventListener('dragenter', ondrag, false);

function gotfile(ev) {
	ev.stopPropagation();
	ev.preventDefault();
	
	var files = ev.dataTransfer ?
		ev.dataTransfer.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];
		var entry = {
			"n": parseInt(st.files.length.toString()),
			"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 = '<td></td><td id="f{0}t">hashing</td><td id="f{0}p" class="prog"></td>'.format(st.files.length);
		tr.getElementsByTagName('td')[0].textContent = entry.name;
		o('tab').appendChild(tr);
		
		st.files.push(entry);
		st.todo.hash.push(entry);
	}
}
o('fancy').addEventListener('drop', gotfile, false);

function more_one_file() {
	fdom_ctr++;
	var elm = document.createElement('div')
	elm.innerHTML = '<input id="file{0}" type="file" name="file{0}[]" multiple="multiple" />'.format(fdom_ctr);
	o('form').appendChild(elm);
	o('file' + fdom_ctr).addEventListener('change', gotfile, false);
}
more_one_file();

/////
////
///   actuator
//

function boss() {
	if (st.todo.hash.length > 0 &&
		st.busy.hash.length == 0)
		exec_hash();
	
	if (st.todo.handshake.length > 0 &&
		st.busy.handshake.length == 0 &&
		st.busy.upload.length < parallel_uploads)
		exec_handshake();
	
	if (st.todo.upload.length > 0 &&
		st.busy.upload.length < parallel_uploads)
		exec_upload();
	
	setTimeout(boss, 100);
}
boss();

/////
////
///   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 exec_hash() {
	var t = st.todo.hash.shift();
	st.busy.hash.push(t);
	
	var nchunks = Math.ceil(t.size / chunksize);
	var nchunk = 0;
	
	var pb_html = '';
	var pb_perc = 99.9 / nchunks;
	for (var a = 0; a < nchunks; a++)
		pb_html += '<div id="f{0}p{1}" style="width:{2}%"><div></div></div>'.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 = async function(ev) {
		const hashbuf = await crypto.subtle.digest('SHA-256', ev.target.result);
		t.hash.push(buf2b64(hashbuf));
		
		prog(t.n, nchunk, col_hashed);
		if (++nchunk < nchunks)
		{
			prog(t.n, nchunk, col_hashing);
			return segm_next();
		}
		
		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) {
			t.postlist = [];
			t.wark = xhr.response.wark;
			var missing = xhr.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 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';
			}
			o('f{0}t'.format(t.n)).innerHTML = msg;
			st.busy.handshake.splice(st.busy.handshake.indexOf(t), 1);
		}
		else
			alert("server broke (error {0}):\n\"{1}\"\n".format(
				xhr.status, (xhr.response && xhr.response.err) ||
					"no further information"));
	};
	xhr.open('POST', 'handshake.php', true);
	xhr.responseType = 'json';
	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 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)
				{
					o('f{0}t'.format(t.n)).innerHTML = 'verifying';
					st.todo.handshake.push(t);
				}
			}
			else
				alert("server broke (error {0}):\n\"{1}\"\n".format(
					xhr.status, (xhr.response && xhr.response.err) ||
						"no further information"));
		};
		xhr.open('POST', '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 = 'json';
		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 bumpchunk(dir) {
	try {
		dir.stopPropagation();
		dir.preventDefault();
	} catch(ex) { }
	
	if (st.files.length > 0)
		return alert('only possible before you start uploading\n\n(refresh and try again)')
	
	var obj = o('chunksz');
	if (dir.target) {
		obj.style.background = '#faa';
		var v = Math.floor(parseInt(obj.value));
		if (v < 1 || v > 1024 || v !== v)
			return;
		
		chunksize_mb = v;
		chunksize = chunksize_mb * 1024 * 1024;
		localStorage.setItem('chunksz', v);
		obj.style.background = '#fff';
		return;
	}
	
	chunksize_mb = Math.floor(chunksize_mb * dir);
	
	if (chunksize_mb < 1)
		chunksize_mb = 1;
	
	if (chunksize_mb > 1024)
		chunksize_mb = 1024;
	
	obj.value = chunksize_mb;
	bumpchunk({"target":1})
}

function bumpthread(dir) {
	try {
		dir.stopPropagation();
		dir.preventDefault();
	} catch(ex) { }
	
	var obj = o('nthread');
	if (dir.target) {
		obj.style.background = '#faa';
		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 = '#fff';
		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(); }
function chunksz_add() { bumpchunk(2); }
function chunksz_sub() { bumpchunk(0.5); }
function nthread_add() { bumpthread(1); }
function nthread_sub() { bumpthread(-1); }

o('chunksz').addEventListener('input', bumpchunk, false);
o('nthread').addEventListener('input', bumpthread, false);

var nodes = o('config').getElementsByTagName('a');
for (var a = nodes.length - 1; a >= 0; a--)
	nodes[a].addEventListener('touchend', nop, false);

bumpchunk({"target":1})
bumpthread({"target":1})

</script>
</body>
</html>
