<commit_before><?php

/*
 *  FileTrader - Web based file sharing platform
 *  Copyright (C) 2011 <NAME> <<EMAIL>>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

class Files {
	private $config;
	private $dbh;

	function __construct($config) {
		$this->config = $config;
		try {
			$this->dbh = new PDO(
					"sqlite:" . getConfig($this->config, 'token_file', TRUE),
					NULL, NULL, array(PDO::ATTR_PERSISTENT => TRUE));

			/* FIXME: maybe this should be placed somewhere else, inefficient?!... */
			$this->dbh
					->query(
							'CREATE TABLE IF NOT EXISTS downloadTokens (token TINYTEXT, userName TINYTEXT, fileName TINYTEXT, PRIMARY KEY (token), UNIQUE(token))');
			$this->dbh
					->query(
							'CREATE TABLE IF NOT EXISTS   uploadTokens (token TINYTEXT, userName TINYTEXT, fileName TINYTEXT, fileSize INT, PRIMARY KEY (token), UNIQUE(token))');
		} catch (Exception $e) {
			throw new Exception("database connection failed");
		}
	}

	function __destruct() {

		$this->dbh = NULL;
	}

	function getDownloadToken() {
		/* FIXME: token should expire, based on server request? */

		if ($_SERVER['REQUEST_METHOD'] != 'POST') {
			throw new Exception("invalid request method, should be POST");
		}

		/* verify userName */
		$userName = filter_var(getRequest('userName', TRUE),
				FILTER_SANITIZE_SPECIAL_CHARS);
		if ($userName === FALSE)
			throw new Exception("invalid username");

		/* verify fileName */
		$fileName = filter_var(
				basename(getRequest('fileName', TRUE),
						FILTER_SANITIZE_SPECIAL_CHARS));
		if ($fileName === FALSE)
			throw new Exception("invalid filename");

		/* file needs to exist before getting a token is allowed */
		$fileDir = getConfig($this->config, 'file_storage_dir', TRUE)
				. DIRECTORY_SEPARATOR . base64_encode($userName);
		$filePath = $fileDir . DIRECTORY_SEPARATOR . $fileName;
		if (!file_exists($filePath)) {
			throw new Exception("file does not exist");
		}

		$token = generateToken();

		try {
			$stmt = $this->dbh
					->prepare(
							"INSERT INTO downloadTokens (token, userName, fileName) VALUES (:token, :userName, :fileName)");
			$stmt->bindParam(':token', $token);
			$stmt->bindParam(':userName', $userName);
			$stmt->bindParam(':fileName', $fileName);
			$stmt->execute();

			$downloadLocation = getProtocol() . $_SERVER['SERVER_NAME']
					. $_SERVER['PHP_SELF']
					. "?action=downloadFile&token=$token";

			return array("downloadLocation" => $downloadLocation);
		} catch (Exception $e) {
			throw new Exception("database query failed");
		}
	}

	function getUploadToken() {
		/* FIXME: token should expire, based on server request? */
		/* FIXME: what if upload size is not known in time? transcode web service for example... */
		/* FIXME: maybe transparently fix the file name on duplicate upload */

		if ($_SERVER['REQUEST_METHOD'] != 'POST') {
			throw new Exception("invalid request method, should be POST");
		}

		/* verify userName */
		$userName = filter_var(getRequest('userName', TRUE),
				FILTER_SANITIZE_SPECIAL_CHARS);
		if ($userName === FALSE)
			throw new Exception("invalid username");

		/* verify fileName */
		$fileName = filter_var(
				basename(getRequest('fileName', TRUE),
						FILTER_SANITIZE_SPECIAL_CHARS));
		if ($fileName === FALSE)
			throw new Exception("invalid filename");

		/* verify fileSize
		 * 
		 * NOTE: fileSize *is* required, but 0 is a valid file size, but also 
		 * seen as "empty" by PHP, so we work around it like this...
		 */
		$fileSize = (int) getRequest('fileSize', FALSE, 0);
		if ($fileSize < 0) {
			throw new Exception("invalid filesize");
		}

		$fileDir = getConfig($this->config, 'file_storage_dir', TRUE)
				. DIRECTORY_SEPARATOR . base64_encode($userName);
		if (file_exists($fileDir . DIRECTORY_SEPARATOR . $fileName)) {
			throw new Exception("file already exists");
		}

		$token = generateToken();

		try {
			$stmt = $this->dbh
					->prepare(
							"INSERT INTO uploadTokens (token, userName, fileName, fileSize) VALUES (:token, :userName, :fileName, :fileSize)");
			$stmt->bindParam(':token', $token);
			$stmt->bindParam(':userName', $userName);
			$stmt->bindParam(':fileName', $fileName);
			$stmt->bindParam(':fileSize', $fileSize);
			$stmt->execute();

			$uploadLocation = getProtocol() . $_SERVER['SERVER_NAME']
					. $_SERVER['PHP_SELF'] . "?action=uploadFile&token=$token";
			return array("uploadLocation" => $uploadLocation);
		} catch (Exception $e) {
			throw new Exception("database query failed");
		}
	}

	function downloadFile() {
		/* FIXME: delete token after download, but still support range requests, pickle?! */
		/* FIXME: delete token immediately on first request? */

		if ($_SERVER['REQUEST_METHOD'] != 'GET') {
			throw new Exception("invalid request method, should be GET", 405);
		}

		if (!isset($_GET['token']) || empty($_GET['token'])
				|| !ctype_xdigit($_GET['token']))
			throw new Exception("invalid token", 400);
		$token = $_GET['token'];

		$stmt = $this->dbh
				->prepare(
						"SELECT token, userName, fileName FROM downloadTokens WHERE token=:token");
		$stmt->bindParam(':token', $token);
		$stmt->execute();
		$row = $stmt->fetch();
		if (empty($row)) {
			throw new Exception("token not found", 404);
		}

		$userName = $row['userName'];
		$fileName = $row['fileName'];

		$fileDir = getConfig($this->config, 'file_storage_dir', TRUE)
				. DIRECTORY_SEPARATOR . base64_encode($userName);
		$filePath = $fileDir . DIRECTORY_SEPARATOR . $fileName;
		if (!is_file($filePath)) {
			throw new Exception("file does not exist", 500);
		}

		$finfo = new finfo(FILEINFO_MIME_TYPE);
		header("X-Sendfile: " . $filePath);
		header("Content-Type: " . $finfo->file($filePath));
		header('Content-Disposition: attachment; filename="' . $fileName . '"');
		exit(0);
	}

	function uploadFile() {
		/* FIXME: limit content-length to something reasonable, max file upload size from PHP? */

		if ($_SERVER['REQUEST_METHOD'] != 'PUT'
				&& $_SERVER['REQUEST_METHOD'] != 'OPTIONS') {
			throw new Exception("invalid request method, should be PUT", 405);
		}

		/* This is to allow "preflight" XMLHttpRequests, 
		 * see https://developer.mozilla.org/En/HTTP_access_control */
		/* FIXME: maybe move this to after the token verification? */
		/* FIXME: maybe be more restrictive in where to allow requests from? */
		if ($_SERVER['REQUEST_METHOD'] == "OPTIONS") {
			header('Access-Control-Allow-Origin: *');
			header('Access-Control-Allow-Methods: PUT, OPTIONS');
			header('Access-Control-Allow-Headers: X-File-Chunk, Content-Type');
			header('Access-Control-Max-Age: 1728000');
			header("Content-Length: 0");
			header("Content-Type: text/plain");
			exit(0);
			break;
		} else {
			header('Access-Control-Allow-Origin: *');
		}

		if (!isset($_GET['token']) || empty($_GET['token'])
				|| !ctype_xdigit($_GET['token']))
			throw new Exception("invalid token", 400);
		$token = $_GET['token'];

		$stmt = $this->dbh
				->prepare(
						"SELECT token, userName, fileName, fileSize FROM uploadTokens WHERE token=:token");
		$stmt->bindParam(':token', $token);
		$stmt->execute();
		$row = $stmt->fetch();
		if (empty($row)) {
			throw new Exception("token not found", 404);
		}

		$userName = $row['userName'];
		$fileName = $row['fileName'];
		$fileSize = $row['fileSize'];

		$fileDir = getConfig($this->config, 'file_storage_dir', TRUE)
				. DIRECTORY_SEPARATOR . base64_encode($userName);
		if (!file_exists($fileDir)) {
			if (mkdir($fileDir) === FALSE) {
				throw new Exception("unable to create directory", 500);
			}
		}
		$filePath = $fileDir . DIRECTORY_SEPARATOR . $fileName;

		/* chunk number has to be >=0 */
		$fileChunk = (int) getHeader('X-File-Chunk', FALSE, 0);
		if ($fileChunk < 0) {
			throw new Exception("invalid x-file-chunk header", 400);
		}

		/* chunk can be >0 only if file already exists */
		if ($fileChunk > 0 && !file_exists($filePath)) {
			throw new Exception(
					"file does not exist, cannot send chunk number >0", 400);
		}

		/* append to existing file if chunk >0, else create new file */
		$out = fopen($filePath, ($fileChunk == 0) ? "wb" : "ab");
		if ($out) {
			$in = fopen("php://input", "rb");
			if ($in) {
				while ($buffer = fread($in, 4096))
					fwrite($out, $buffer);
			} else {
				throw new Exception("unable to open upload stream", 500);
			}
			fclose($in);
			fclose($out);
			flush();
		} else {
			throw new Exception("unable to open output file", 500);
		}

		/* if upload complete, i.e.: final size as specified in token reached, delete token */
		if (filesize($filePath) >= $fileSize) {
			$stmt = $this->dbh
					->prepare("DELETE FROM uploadTokens WHERE token=:token");
			$stmt->bindParam(':token', $token);
			$stmt->execute();
		}
		return array("chunk" => $fileChunk);
	}

	function getFileList() {
		if ($_SERVER['REQUEST_METHOD'] != 'GET') {
			throw new Exception("invalid request method, should be GET");
		}

		/* verify userName */
		$userName = filter_var(getRequest('userName', TRUE),
				FILTER_SANITIZE_SPECIAL_CHARS);
		if ($userName === FALSE)
			throw new Exception("invalid username");

		$fileDir = getConfig($this->config, 'file_storage_dir', TRUE)
				. DIRECTORY_SEPARATOR . base64_encode($userName);

		/* is chdir only valid for this call? May break some other stuff? */
		if (chdir($fileDir) === FALSE) {
			throw new Exception("user does not have files in this store");
		}

		$fileList = array();
		foreach (glob("*") as $fileName) {
			$fileList['files'][$fileName] = array(
					"fileSize" => filesize($fileName),
					"isDirectory" => is_dir($fileName));
		}
		return $fileList;
	}

	function deleteFile() {
		if ($_SERVER['REQUEST_METHOD'] != 'POST') {
			throw new Exception("invalid request method, should be POST");
		}

		/* verify userName */
		$userName = filter_var(getRequest('userName', TRUE),
				FILTER_SANITIZE_SPECIAL_CHARS);
		if ($userName === FALSE)
			throw new Exception("invalid username");

		/* verify fileName */
		$fileName = filter_var(
				basename(getRequest('fileName', TRUE),
						FILTER_SANITIZE_SPECIAL_CHARS));
		if ($fileName === FALSE)
			throw new Exception("invalid filename");

		$fileDir = getConfig($this->config, 'file_storage_dir', TRUE)
				. DIRECTORY_SEPARATOR . base64_encode($userName);
		$filePath = $fileDir . DIRECTORY_SEPARATOR . $fileName;

		if (!is_file($filePath)) {
			throw new Exception("file does not exist");
		}

		if (unlink($filePath) === FALSE) {
			throw new Exception("unable to delete file");
		}

		return array();
	}

	function deleteDirectory() {
		if ($_SERVER['REQUEST_METHOD'] != 'POST') {
			throw new Exception("invalid request method, should be POST");
		}

		/* verify userName */
		$userName = filter_var(getRequest('userName', TRUE),
				FILTER_SANITIZE_SPECIAL_CHARS);
		if ($userName === FALSE)
			throw new Exception("invalid username");

		/* verify fileName */
		$dirName = filter_var(
				basename(getRequest('dirName', TRUE),
						FILTER_SANITIZE_SPECIAL_CHARS));
		if ($dirName === FALSE)
			throw new Exception("invalid dirname");

		$fileDir = getConfig($this->config, 'file_storage_dir', TRUE)
				. DIRECTORY_SEPARATOR . base64_encode($userName);
		$dirPath = $fileDir . DIRECTORY_SEPARATOR . $dirName;

		if (!is_dir($dirPath)) {
			throw new Exception("directory does not exist");
		}

		if (rmdir($dirPath) === FALSE) {
			throw new Exception("unable to delete directory");
		}

		return array();
	}

	function createDirectory() {
		/* FIXME: if the user directory does not exist, this can be created! */

		if ($_SERVER['REQUEST_METHOD'] != 'POST') {
			throw new Exception("invalid request method, should be POST");
		}

		/* verify userName */
		$userName = filter_var(getRequest('userName', TRUE),
				FILTER_SANITIZE_SPECIAL_CHARS);
		if ($userName === FALSE)
			throw new Exception("invalid username");

		$fileDir = getConfig($this->config, 'file_storage_dir', TRUE)
				. DIRECTORY_SEPARATOR . base64_encode($userName);

		/* verify dirName */
		$dirName = filter_var(
				basename(getRequest('dirName', TRUE),
						FILTER_SANITIZE_SPECIAL_CHARS));
		if ($dirName === FALSE)
			throw new Exception("invalid dirname");

		/* validate existence of parent of specified directory */
		$baseDir = realpath($fileDir . DIRECTORY_SEPARATOR . dirname($dirName));
		if ($baseDir === FALSE) {
			throw new Exception(
					"parent of specified directory is invalid or does not exist");
		}
		if (strpos($baseDir, $fileDir) === FALSE
				|| strpos($baseDir, $fileDir) !== 0) {
			throw new Exception("trying to escape directory structure?");
		}
		if (!is_dir($baseDir)) {
			throw new Exception(
					"parent of specified directory is not a directory?");
		}

		/* create the directory if it does not exists yet */
		$newDir = $baseDir . DIRECTORY_SEPARATOR . basename($dirName);
		if (file_exists($newDir)) {
			throw new Exception(
					"directory (or file) with that name already exists");
		}

		if (mkdir($newDir, 0775) === FALSE) {
			throw new Exception("unable to create directory");
		}

		return array();
	}

	function serverInfo() {
		if ($_SERVER['REQUEST_METHOD'] != 'GET') {
			throw new Exception("invalid request method, should be GET");
		}

		/* determine available disk space on server */
		$df = disk_free_space(getConfig($this->config, 'storage_dir', TRUE));

		/* determine IP version (IPv4, IPv6) */
		if (filter_var($_SERVER['REMOTE_ADDR'], FILTER_VALIDATE_IP,
				FILTER_FLAG_IPV6) === FALSE) {
			$ipVersion = 4;
		} else {
			$ipVersion = 6;
		}

		return array("availableSpace" => $df, 'ipVersion' => $ipVersion,
				'remoteAddr' => $_SERVER['REMOTE_ADDR'],
				'systemDate' => date("c", time()));
	}

	function pingServer() {
		if ($_SERVER['REQUEST_METHOD'] != 'GET') {
			throw new Exception("invalid request method, should be GET");
		}
		return array('message' => 'FileTrader REST API');
	}
}
?>
<commit_msg>create user dir if it does not exist yet<commit_after><?php

/*
 *  FileTrader - Web based file sharing platform
 *  Copyright (C) 2011 <NAME> <<EMAIL>>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

class Files {
	private $config;
	private $dbh;

	function __construct($config) {
		$this->config = $config;
		try {
			$this->dbh = new PDO(
					"sqlite:" . getConfig($this->config, 'token_file', TRUE),
					NULL, NULL, array(PDO::ATTR_PERSISTENT => TRUE));

			/* FIXME: maybe this should be placed somewhere else, inefficient?!... */
			$this->dbh
					->query(
							'CREATE TABLE IF NOT EXISTS downloadTokens (token TINYTEXT, userName TINYTEXT, fileName TINYTEXT, PRIMARY KEY (token), UNIQUE(token))');
			$this->dbh
					->query(
							'CREATE TABLE IF NOT EXISTS   uploadTokens (token TINYTEXT, userName TINYTEXT, fileName TINYTEXT, fileSize INT, PRIMARY KEY (token), UNIQUE(token))');
		} catch (Exception $e) {
			throw new Exception("database connection failed");
		}
	}

	function __destruct() {

		$this->dbh = NULL;
	}

	function getDownloadToken() {
		/* FIXME: token should expire, based on server request? */

		if ($_SERVER['REQUEST_METHOD'] != 'POST') {
			throw new Exception("invalid request method, should be POST");
		}

		/* verify userName */
		$userName = filter_var(getRequest('userName', TRUE),
				FILTER_SANITIZE_SPECIAL_CHARS);
		if ($userName === FALSE)
			throw new Exception("invalid username");

		/* verify fileName */
		$fileName = filter_var(
				basename(getRequest('fileName', TRUE),
						FILTER_SANITIZE_SPECIAL_CHARS));
		if ($fileName === FALSE)
			throw new Exception("invalid filename");

		/* file needs to exist before getting a token is allowed */
		$fileDir = getConfig($this->config, 'file_storage_dir', TRUE)
				. DIRECTORY_SEPARATOR . base64_encode($userName);
		$filePath = $fileDir . DIRECTORY_SEPARATOR . $fileName;
		if (!file_exists($filePath)) {
			throw new Exception("file does not exist");
		}

		$token = generateToken();

		try {
			$stmt = $this->dbh
					->prepare(
							"INSERT INTO downloadTokens (token, userName, fileName) VALUES (:token, :userName, :fileName)");
			$stmt->bindParam(':token', $token);
			$stmt->bindParam(':userName', $userName);
			$stmt->bindParam(':fileName', $fileName);
			$stmt->execute();

			$downloadLocation = getProtocol() . $_SERVER['SERVER_NAME']
					. $_SERVER['PHP_SELF']
					. "?action=downloadFile&token=$token";

			return array("downloadLocation" => $downloadLocation);
		} catch (Exception $e) {
			throw new Exception("database query failed");
		}
	}

	function getUploadToken() {
		/* FIXME: token should expire, based on server request? */
		/* FIXME: what if upload size is not known in time? transcode web service for example... */
		/* FIXME: maybe transparently fix the file name on duplicate upload */

		if ($_SERVER['REQUEST_METHOD'] != 'POST') {
			throw new Exception("invalid request method, should be POST");
		}

		/* verify userName */
		$userName = filter_var(getRequest('userName', TRUE),
				FILTER_SANITIZE_SPECIAL_CHARS);
		if ($userName === FALSE)
			throw new Exception("invalid username");

		/* verify fileName */
		$fileName = filter_var(
				basename(getRequest('fileName', TRUE),
						FILTER_SANITIZE_SPECIAL_CHARS));
		if ($fileName === FALSE)
			throw new Exception("invalid filename");

		/* verify fileSize
		 * 
		 * NOTE: fileSize *is* required, but 0 is a valid file size, but also 
		 * seen as "empty" by PHP, so we work around it like this...
		 */
		$fileSize = (int) getRequest('fileSize', FALSE, 0);
		if ($fileSize < 0) {
			throw new Exception("invalid filesize");
		}

		$fileDir = getConfig($this->config, 'file_storage_dir', TRUE)
				. DIRECTORY_SEPARATOR . base64_encode($userName);
		if (file_exists($fileDir . DIRECTORY_SEPARATOR . $fileName)) {
			throw new Exception("file already exists");
		}

		$token = generateToken();

		try {
			$stmt = $this->dbh
					->prepare(
							"INSERT INTO uploadTokens (token, userName, fileName, fileSize) VALUES (:token, :userName, :fileName, :fileSize)");
			$stmt->bindParam(':token', $token);
			$stmt->bindParam(':userName', $userName);
			$stmt->bindParam(':fileName', $fileName);
			$stmt->bindParam(':fileSize', $fileSize);
			$stmt->execute();

			$uploadLocation = getProtocol() . $_SERVER['SERVER_NAME']
					. $_SERVER['PHP_SELF'] . "?action=uploadFile&token=$token";
			return array("uploadLocation" => $uploadLocation);
		} catch (Exception $e) {
			throw new Exception("database query failed");
		}
	}

	function downloadFile() {
		/* FIXME: delete token after download, but still support range requests, pickle?! */
		/* FIXME: delete token immediately on first request? */

		if ($_SERVER['REQUEST_METHOD'] != 'GET') {
			throw new Exception("invalid request method, should be GET", 405);
		}

		if (!isset($_GET['token']) || empty($_GET['token'])
				|| !ctype_xdigit($_GET['token']))
			throw new Exception("invalid token", 400);
		$token = $_GET['token'];

		$stmt = $this->dbh
				->prepare(
						"SELECT token, userName, fileName FROM downloadTokens WHERE token=:token");
		$stmt->bindParam(':token', $token);
		$stmt->execute();
		$row = $stmt->fetch();
		if (empty($row)) {
			throw new Exception("token not found", 404);
		}

		$userName = $row['userName'];
		$fileName = $row['fileName'];

		$fileDir = getConfig($this->config, 'file_storage_dir', TRUE)
				. DIRECTORY_SEPARATOR . base64_encode($userName);
		$filePath = $fileDir . DIRECTORY_SEPARATOR . $fileName;
		if (!is_file($filePath)) {
			throw new Exception("file does not exist", 500);
		}

		$finfo = new finfo(FILEINFO_MIME_TYPE);
		header("X-Sendfile: " . $filePath);
		header("Content-Type: " . $finfo->file($filePath));
		header('Content-Disposition: attachment; filename="' . $fileName . '"');
		exit(0);
	}

	function uploadFile() {
		/* FIXME: limit content-length to something reasonable, max file upload size from PHP? */

		if ($_SERVER['REQUEST_METHOD'] != 'PUT'
				&& $_SERVER['REQUEST_METHOD'] != 'OPTIONS') {
			throw new Exception("invalid request method, should be PUT", 405);
		}

		/* This is to allow "preflight" XMLHttpRequests, 
		 * see https://developer.mozilla.org/En/HTTP_access_control */
		/* FIXME: maybe move this to after the token verification? */
		/* FIXME: maybe be more restrictive in where to allow requests from? */
		if ($_SERVER['REQUEST_METHOD'] == "OPTIONS") {
			header('Access-Control-Allow-Origin: *');
			header('Access-Control-Allow-Methods: PUT, OPTIONS');
			header('Access-Control-Allow-Headers: X-File-Chunk, Content-Type');
			header('Access-Control-Max-Age: 1728000');
			header("Content-Length: 0");
			header("Content-Type: text/plain");
			exit(0);
			break;
		} else {
			header('Access-Control-Allow-Origin: *');
		}

		if (!isset($_GET['token']) || empty($_GET['token'])
				|| !ctype_xdigit($_GET['token']))
			throw new Exception("invalid token", 400);
		$token = $_GET['token'];

		$stmt = $this->dbh
				->prepare(
						"SELECT token, userName, fileName, fileSize FROM uploadTokens WHERE token=:token");
		$stmt->bindParam(':token', $token);
		$stmt->execute();
		$row = $stmt->fetch();
		if (empty($row)) {
			throw new Exception("token not found", 404);
		}

		$userName = $row['userName'];
		$fileName = $row['fileName'];
		$fileSize = $row['fileSize'];

		$fileDir = getConfig($this->config, 'file_storage_dir', TRUE)
				. DIRECTORY_SEPARATOR . base64_encode($userName);
		if (!file_exists($fileDir)) {
			if (mkdir($fileDir) === FALSE) {
				throw new Exception("unable to create directory", 500);
			}
		}
		$filePath = $fileDir . DIRECTORY_SEPARATOR . $fileName;

		/* chunk number has to be >=0 */
		$fileChunk = (int) getHeader('X-File-Chunk', FALSE, 0);
		if ($fileChunk < 0) {
			throw new Exception("invalid x-file-chunk header", 400);
		}

		/* chunk can be >0 only if file already exists */
		if ($fileChunk > 0 && !file_exists($filePath)) {
			throw new Exception(
					"file does not exist, cannot send chunk number >0", 400);
		}

		/* append to existing file if chunk >0, else create new file */
		$out = fopen($filePath, ($fileChunk == 0) ? "wb" : "ab");
		if ($out) {
			$in = fopen("php://input", "rb");
			if ($in) {
				while ($buffer = fread($in, 4096))
					fwrite($out, $buffer);
			} else {
				throw new Exception("unable to open upload stream", 500);
			}
			fclose($in);
			fclose($out);
			flush();
		} else {
			throw new Exception("unable to open output file", 500);
		}

		/* if upload complete, i.e.: final size as specified in token reached, delete token */
		if (filesize($filePath) >= $fileSize) {
			$stmt = $this->dbh
					->prepare("DELETE FROM uploadTokens WHERE token=:token");
			$stmt->bindParam(':token', $token);
			$stmt->execute();
		}
		return array("chunk" => $fileChunk);
	}

	function getFileList() {
		if ($_SERVER['REQUEST_METHOD'] != 'GET') {
			throw new Exception("invalid request method, should be GET");
		}

		/* verify userName */
		$userName = filter_var(getRequest('userName', TRUE),
				FILTER_SANITIZE_SPECIAL_CHARS);
		if ($userName === FALSE)
			throw new Exception("invalid username");

		$fileDir = getConfig($this->config, 'file_storage_dir', TRUE)
				. DIRECTORY_SEPARATOR . base64_encode($userName);

		/* is chdir only valid for this call? May break some other stuff? */
		if (chdir($fileDir) === FALSE) {
			throw new Exception("user does not have files in this store");
		}

		$fileList = array();
		foreach (glob("*") as $fileName) {
			$fileList['files'][$fileName] = array(
					"fileSize" => filesize($fileName),
					"isDirectory" => is_dir($fileName));
		}
		return $fileList;
	}

	function deleteFile() {
		if ($_SERVER['REQUEST_METHOD'] != 'POST') {
			throw new Exception("invalid request method, should be POST");
		}

		/* verify userName */
		$userName = filter_var(getRequest('userName', TRUE),
				FILTER_SANITIZE_SPECIAL_CHARS);
		if ($userName === FALSE)
			throw new Exception("invalid username");

		/* verify fileName */
		$fileName = filter_var(
				basename(getRequest('fileName', TRUE),
						FILTER_SANITIZE_SPECIAL_CHARS));
		if ($fileName === FALSE)
			throw new Exception("invalid filename");

		$fileDir = getConfig($this->config, 'file_storage_dir', TRUE)
				. DIRECTORY_SEPARATOR . base64_encode($userName);
		$filePath = $fileDir . DIRECTORY_SEPARATOR . $fileName;

		if (!is_file($filePath)) {
			throw new Exception("file does not exist");
		}

		if (unlink($filePath) === FALSE) {
			throw new Exception("unable to delete file");
		}

		return array();
	}

	function deleteDirectory() {
		if ($_SERVER['REQUEST_METHOD'] != 'POST') {
			throw new Exception("invalid request method, should be POST");
		}

		/* verify userName */
		$userName = filter_var(getRequest('userName', TRUE),
				FILTER_SANITIZE_SPECIAL_CHARS);
		if ($userName === FALSE)
			throw new Exception("invalid username");

		/* verify fileName */
		$dirName = filter_var(
				basename(getRequest('dirName', TRUE),
						FILTER_SANITIZE_SPECIAL_CHARS));
		if ($dirName === FALSE)
			throw new Exception("invalid dirname");

		$fileDir = getConfig($this->config, 'file_storage_dir', TRUE)
				. DIRECTORY_SEPARATOR . base64_encode($userName);
		$dirPath = $fileDir . DIRECTORY_SEPARATOR . $dirName;

		if (!is_dir($dirPath)) {
			throw new Exception("directory does not exist");
		}

		if (rmdir($dirPath) === FALSE) {
			throw new Exception("unable to delete directory");
		}

		return array();
	}

	function createDirectory() {
		if ($_SERVER['REQUEST_METHOD'] != 'POST') {
			throw new Exception("invalid request method, should be POST");
		}

		/* verify userName */
		$userName = filter_var(getRequest('userName', TRUE),
				FILTER_SANITIZE_SPECIAL_CHARS);
		if ($userName === FALSE)
			throw new Exception("invalid username");

		$fileDir = getConfig($this->config, 'file_storage_dir', TRUE)
				. DIRECTORY_SEPARATOR . base64_encode($userName);

                if (!file_exists($fileDir)) {
                        if (mkdir($fileDir) === FALSE) {
                                throw new Exception("unable to create directory", 500);
                        }
                }
		
		/* verify dirName */
		$dirName = filter_var(
				basename(getRequest('dirName', TRUE),
						FILTER_SANITIZE_SPECIAL_CHARS));
		if ($dirName === FALSE)
			throw new Exception("invalid dirname");

		/* validate existence of parent of specified directory */
		$baseDir = realpath($fileDir . DIRECTORY_SEPARATOR . dirname($dirName));
		if ($baseDir === FALSE) {
			throw new Exception(
					"parent of specified directory is invalid or does not exist");
		}
		if (strpos($baseDir, $fileDir) === FALSE
				|| strpos($baseDir, $fileDir) !== 0) {
			throw new Exception("trying to escape directory structure?");
		}
		if (!is_dir($baseDir)) {
			throw new Exception(
					"parent of specified directory is not a directory?");
		}

		/* create the directory if it does not exists yet */
		$newDir = $baseDir . DIRECTORY_SEPARATOR . basename($dirName);
		if (file_exists($newDir)) {
			throw new Exception(
					"directory (or file) with that name already exists");
		}

		if (mkdir($newDir, 0775) === FALSE) {
			throw new Exception("unable to create directory");
		}

		return array();
	}

	function serverInfo() {
		if ($_SERVER['REQUEST_METHOD'] != 'GET') {
			throw new Exception("invalid request method, should be GET");
		}

		/* determine available disk space on server */
		$df = disk_free_space(getConfig($this->config, 'storage_dir', TRUE));

		/* determine IP version (IPv4, IPv6) */
		if (filter_var($_SERVER['REMOTE_ADDR'], FILTER_VALIDATE_IP,
				FILTER_FLAG_IPV6) === FALSE) {
			$ipVersion = 4;
		} else {
			$ipVersion = 6;
		}

		return array("availableSpace" => $df, 'ipVersion' => $ipVersion,
				'remoteAddr' => $_SERVER['REMOTE_ADDR'],
				'systemDate' => date("c", time()));
	}

	function pingServer() {
		if ($_SERVER['REQUEST_METHOD'] != 'GET') {
			throw new Exception("invalid request method, should be GET");
		}
		return array('message' => 'FileTrader REST API');
	}
}
?>

{'fraction_non_alphanumeric': 0.15362169584620666, 'fraction_numerical': 0.00641949879848953, 'mean_word_length': 3.863272120200334, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 137, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 20, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3718639', 'n_tokens_mistral': 10574, 'n_tokens_neox': 9076, 'n_words': 2556}