upload / GET -> download switch ($_SERVER['REQUEST_METHOD']) { case 'POST': #### Script restrictions. session_start(); if (empty($_POST['token']) || !hash_equals($_SESSION['token'], $_POST['token'])) { http_response_code(403); die('Forbidden.'); } #### Retrieve uploaded file. if (!empty($_FILES['file']) and is_uploaded_file($_FILES['file']['tmp_name'])) { if($_FILES['file']['size'] > $config['ALLOWED_ASSET_SIZE'] * 1048576) { http_response_code(403); die('File size exceeds '.$config['ALLOWED_ASSET_SIZE'].'MiB.'); } # Regular multipart/form-data upload. $name = $_FILES['file']['name']; $data = atomized_get_contents($_FILES['file']['tmp_name']); } else { if((int)get_file_size("php://input") > $config['ALLOWED_ASSET_SIZE'] * 1048576) { http_response_code(403); die('File size exceeds '.$config['ALLOWED_ASSET_SIZE'].'MiB.'); } # Raw POST data. $name = urldecode(@$_SERVER['HTTP_X_FILE_NAME']); $data = atomized_get_contents("php://input"); } #### Grab the file extension. $fileExtension = pathinfo($name, PATHINFO_EXTENSION); #### If the extension is not allowed then change it to a text extension. if (!isset($fileExtension) || !in_array(strtoupper($fileExtension), array_map('strtoupper', $config['ALLOWED_FILE_EXTENSIONS']))) { http_response_code(403); die('File extension not allowed.'); } #### Hash filename. $file = strtolower( PseudoCrypt::hash( preg_replace( '/\D/', '', hash( 'sha512', $data ) ), $config['ASSET_HASH_SIZE'] ) ); #### Build the user path. $userPath = join( DIRECTORY_SEPARATOR, array( $config['STORE_FOLDER'], $file ) ); #### Check for path traversals. $pathPart = pathinfo($userPath.'.'.$fileExtension); if (strcasecmp( realpath($pathPart['dirname']), realpath($config['STORE_FOLDER'])) != 0) { http_response_code(500); die('Internal server error.'); } #### Store the file. $timestamp = atomized_put_contents($userPath.'.'.$fileExtension, $data); ### Log IP address. $db = new PDO('sqlite:db/scratch.sqlite3'); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); try { $db->beginTransaction(); ## Create tags table if it does not exist. $db->query('CREATE TABLE IF NOT EXISTS "uploaders" ("hash" text NOT NULL COLLATE NOCASE, "ip" text COLLATE NOCASE, UNIQUE("hash") ON CONFLICT REPLACE)'); $q = $db->prepare('REPLACE INTO "uploaders" ("hash", "ip") VALUES(:hash, :ip)'); $q->bindParam(':hash', $file); $q->bindParam(':ip', get_ip_address()); $q->execute(); $db->commit(); } catch (Exception $e) { error_log($e); ## Rollback. $db->rollback(); } ### Process any sent tags. if(isset($_POST['tags'])) { $tags = json_decode( stripslashes( $_POST['tags'] ) ); ## If we have any tags then insert them into the database. if(!empty($tags)) { ## Connect or create the scratch database. $db = new PDO('sqlite:db/scratch.sqlite3'); ## Set the error mode to exceptions. $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); try { $db->beginTransaction(); ## Create tags table if it does not exist. $db->query('CREATE TABLE IF NOT EXISTS "tags" ("hash" text NOT NULL COLLATE NOCASE, "tag" text COLLATE NOCASE, UNIQUE("hash", "tag") ON CONFLICT REPLACE)'); ## Now add all the tags. foreach($tags as $tag) { $q = $db->prepare('REPLACE INTO "tags" ("hash", "tag") VALUES(:hash, :tag)'); $q->bindParam(':hash', $file); $q->bindParam(':tag', $tag); $q->execute(); } $db->commit(); } catch (Exception $e) { error_log($e); ## Rollback. $db->rollback(); } } } ### Hook for various file extensions. $opengraph = FALSE; switch(strtoupper($fileExtension)) { case 'MP4': case 'GIF': $opengraph = TRUE; break; } ### Return the URL to the file. header('Content-Type: text/plain; charset=utf-8'); echo json_encode( array( "hash" => $file, "timestamp" => $timestamp, "opengraph" => $opengraph ) ); break; case 'GET': ### Tell browser not to cache files. header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0"); header("Cache-Control: post-check=0, pre-check=0", false); header("Pragma: no-cache"); ### If no file has been specified for download then return. if (!isset($_GET['hash']) or empty($_GET['hash'])) { http_response_code(404); die('File not found.'); } ### Find the requested file. $file = array_shift( preg_grep( '/'.$_GET['hash'].'/', scandir($config['STORE_FOLDER']) ) ); if (!isset($file) or empty($file)) { http_response_code(404); die('File not found.'); } ### Check the path for path traversals. $fileExtension = pathinfo($file, PATHINFO_EXTENSION); #### If the extension is not allowed then return. if (!isset($fileExtension) || !in_array(strtoupper($fileExtension), array_map('strtoupper', $config['ALLOWED_FILE_EXTENSIONS']))) { http_response_code(403); die('File extension not allowed.'); } #### Build the user path. $userPath = join( DIRECTORY_SEPARATOR, array( $config['STORE_FOLDER'], $file ) ); #### Check for path traversals $pathPart = pathinfo($userPath); if (strcasecmp( realpath($pathPart['dirname']), realpath($config['STORE_FOLDER'])) != 0) { http_response_code(500); die('Internal server error.'); } ### Hook for various file extensions. switch(strtoupper($fileExtension)) { case "HTML": case "HTM": header('Content-type: text/html'); break; break; case "URL": if(preg_match( "/URL=(https?:\/\/[\-_\.\+!\*'\(\),a-zA-Z0-9]+:?[0-9]{0,5}\/.*?)\n/", file_get_contents($userPath), $matches)) { header('Location: '.$matches[1]); return; } break; default: ### Open MIME info database and send the content type. $finfo = finfo_open(FILEINFO_MIME_TYPE); if (!$finfo) { http_response_code(500); die('Internal server error.'); } header('Content-type: '.finfo_file($finfo, $userPath)); finfo_close($finfo); break; } ### Send the file along with the inline content disposition. header('Content-length: '.(int)get_file_size($userPath)); header('Content-Disposition: inline; filename="' . basename($userPath) . '"'); header('Content-Transfer-Encoding: binary'); header('X-Sendfile: '.$userPath); break; }