<?php
require_once('common.php');
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: .5em auto;
	padding: 1.5em 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;
}
.log {
	padding: .5em 1em;
	font-family: monospace, monospace;
}
h6 {
	margin: 0;
	padding: 0;
	font-size: .8em;
	font-weight: normal;
	font-style: italic;
}
.faded {
	color: #c90;
}
</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 it chokes mid-upload please refresh the web sight and<br />try the same files again, it will continue where it left off probably</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>

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

<h3>uploader log</h3>
<div id="uploader_log" class="log">(not active)</div>

<h3>hasher log</h3>
<div id="hasher_log" class="log">(not active)</div>





<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;
		});
	};
}

var fdom_ctr = 0;
var hash_queue = [];
var upload_queue = [];
var chunksize = 2 * 1024 * 1024;

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++) {
		hash_queue.push(files[a]);
	}
}
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();

/////
////
///   hasher
//

var hasher_ndone = 0;
var hasher_idle = true;
function hasherd() {
	if (!hasher_idle)
		return setTimeout(hasherd, 100);

	if (hash_queue.length > 0) {
		hasher_idle = false;
		hash_file();
	}
	else if (hasher_idle)
	{
		var html = '{0} files hashed, nothing to do rn'.format(hasher_ndone);
		if (o('hasher_log').innerHTML != html)
			o('hasher_log').innerHTML = html;
	}
	setTimeout(hasherd, 100);
}
hasherd();

// 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;

	// Main loop deals with bytes in chunks of 3
	for (var i = 0; i < mainLength; i = i + 3) {
		// Combine the three bytes into a single integer
		chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];

		// Use bitmasks to extract 6-bit segments from the triplet
		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];
	}

	// Deal with the remaining bytes and padding
	if (byteRemainder == 1) {
		chunk = bytes[mainLength];

		a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2

		// Set the 4 least significant bits to zero
		b = (chunk & 3)   << 4; // 3   = 2^2 - 1

		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

		// Set the 2 least significant bits to zero
		c = (chunk & 15)    <<  2; // 15    = 2^4 - 1

		base64 += encodings[a] + encodings[b] + encodings[c] + '=';
	}
	
	return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}

// 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 hash_file() {
	var fobj = hash_queue.shift();
	var nchunks = Math.ceil(fobj.size / chunksize);
	var nchunk = 0;
	var sums = [];
	
	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 >= fobj.size)
			cdr = fobj.size;
		
		reader.readAsArrayBuffer(
			bobslice.call(fobj, car, cdr));
		
		var perc = Math.floor(100 * nchunk / nchunks);
		var msg = '{1} files done, {2} more files in queue, current file at {3}% ({4}/{5})<br />\n"{0}"<span class="faded">'.format(
			fobj.name, hasher_ndone, hash_queue.length, perc, nchunk, nchunks);
		
		var recent = sums.slice(-3);
		for (var a = 0; a < recent.length; a++)
			msg += "\n<br />{0} #{1}".format(
				recent[a], nchunk-(recent.length-a));
		
		o('hasher_log').innerHTML = msg;
	};
	
	var segm_load = async function(ev) {
		const hashbuf = await crypto.subtle.digest('SHA-256', ev.target.result);
		sums.push(buf2b64(hashbuf));
		
		if (++nchunk < nchunks)
			return segm_next();
		
		hasher_ndone++;
		hasher_idle = true;
		upload_queue.push({
			"name": fobj.name,
			"size": fobj.size,
			"hash": sums,
			"fobj": fobj
		});
	};
	
	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();
}

////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
////////          EVERYTHING BELOW THIS POINT IS AWFUL              ////
///////                   PLEASE STOP READING                      /////
//////
/////
////
///   uploader
//

var ups_in_progress = [];
var uploader_ndone = 0;
var uploader_idle = true;
function uploaderd() {
	if (!uploader_idle)
		return setTimeout(uploaderd, 100);

	if (upload_queue.length > 0) {
		uploader_idle = false;
		upload_file();
	}
	else if (uploader_idle)
	{
		var html = '{0} files uploaded, nothing to do rn'.format(uploader_ndone);
		if (o('uploader_log').innerHTML != html)
			o('uploader_log').innerHTML = html;
	}
	setTimeout(uploaderd, 100);
}
uploaderd();

var chunkpusher_params = {
	"next": -1
};
function upload_file() {
	var qitem = upload_queue.shift();
	o('uploader_log').innerHTML = '{0} files uploaded, {1} in queue, initiating {2} chunk upload of:<br />\n"{3}"'.format(
		uploader_ndone, upload_queue.length + 1, qitem.hash.length, qitem.name);
	
	var fobj = qitem.fobj;
	delete qitem.fobj;
	
	var xhr = new XMLHttpRequest();
	xhr.onload = function(ev) {
		if (xhr.status == 200) {
			chunkpusher_params = {
				"fobj": fobj,
				"task": xhr.response,
				"qitem": qitem,
				"next": 0
			};
		}
		else
			alert("server broke (error {0}):\n\"{1}\"\n".format(
				xhr.status, xhr.statusText));
	};
	xhr.open('POST', 'handshake.php', true);
	xhr.responseType = 'json';
	xhr.send(JSON.stringify(qitem));
}

function chunkpusherd() {
	if (!chunkpusher_params ||
		chunkpusher_params.next == -1)
		return setTimeout(chunkpusherd, 100);
	
	var fobj = chunkpusher_params.fobj;
	var task = chunkpusher_params.task;
	var qitem = chunkpusher_params.qitem;
	
	var msg = '{0} files uploaded, {1} more in queue, uploading {2} chunks of:<br />\n"{3}"<span class="faded">'.format(
		uploader_ndone, upload_queue.length, task.hash.length, task.name);
	
	for (var a = 0; a < ups_in_progress.length; a++)
		msg += "\n<br />{0}".format(ups_in_progress[a]);
	
	o('uploader_log').innerHTML = msg;
	
	if (ups_in_progress.length > 3)
		return setTimeout(chunkpusherd, 100);
	
	var next = chunkpusher_params.next++;
	if (chunkpusher_params.next >= task.hash.length)
		chunkpusher_params.next = -1;
	
	var hash = task.hash[next];
	ups_in_progress.push(hash)
	
	post_segm(fobj, qitem, hash, task.wark);
	
	return setTimeout(chunkpusherd, 100);
}
chunkpusherd();

function post_segm(fobj, qitem, tgt_hash, wark) {
	var qitem_idx = -1;
	for (var a = 0; a < qitem.hash.length; a++) {
		if (qitem.hash[a] == tgt_hash) {
			qitem_idx = a;
			break;
		}
	}
	
	if (qitem_idx == -1) {
		alert('could not find hash {0} in qitem:\n{1}'.format(
			tgt_hash, JSON.stringify(qitem)));
		
		throw 42;
	}
	
	var car = qitem_idx * chunksize;
	var cdr = car + chunksize;
	if (cdr >= fobj.size)
		cdr = fobj.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.onload = function(xev) {
			if (xhr.status == 200) {
				ups_in_progress.splice(
					ups_in_progress.indexOf(tgt_hash), 1);
				
				if (ups_in_progress.length == 0) {
					uploader_ndone++;
					
					if (upload_queue.length == 0)
						uploader_idle = true;
					else
						upload_file();
				}
			}
			else
				alert("server broke (error {0}):\n\"{1}\"\n".format(
					xhr.status, xhr.statusText));
		};
		xhr.open('POST', 'chunkpit.php', true);
		xhr.setRequestHeader("X-Up2k-Hash", tgt_hash);
		xhr.setRequestHeader("X-Up2k-Wark", wark);
		xhr.setRequestHeader('Content-Type', 'application/octet-stream');
		xhr.overrideMimeType('Content-Type', 'application/octet-stream');
		xhr.responseType = 'json';
		xhr.send(ev.target.result);
		//var formdata = new FormData();
		//formdata.append("f", ev.target.result);
		//xhr.send(formdata);
	};
	
	reader.readAsArrayBuffer(bobslice.call(fobj, car, cdr));
}

/*
FileReader.readAsBinaryString() is deprecated; OK:
void abort();
void readAsArrayBuffer(Blob blob);
void readAsText(Blob blob, optional DOMString encoding);
void readAsDataURL(Blob blob);

XMLHttpRequest.send() has the following invocations options:
void send();
void send(ArrayBuffer data);
void send(Blob data);
void send(Document data);
void send(DOMString? data);
void send(FormData data);

recommendation:
send() the FileReader.result of FileReader.readAsArrayBuffer(fileObject)
*/

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