scratch – Blame information for rev 141

Subversion Repositories:
Rev:
Rev Author Line No. Line
7 office 1 <?php
2  
3 ###########################################################################
4 ## Copyright (C) Wizardry and Steamworks 2017 - License: GNU GPLv3 ##
5 ###########################################################################
6  
66 office 7 require_once('php/pseudocrypt.php');
8 require_once('php/functions.php');
141 office 9 require_once('php/ip.php');
87 office 10 require_once('vendor/autoload.php');
7 office 11  
67 office 12 ### Load configuration.
13 $config = spyc_load_file('config.yaml');
14  
52 office 15 #### POST -> upload / GET -> download
16 switch ($_SERVER['REQUEST_METHOD']) {
17 case 'POST':
93 office 18 #### Script restrictions.
96 office 19 session_start();
20 if (empty($_POST['token']) || !hash_equals($_SESSION['token'], $_POST['token'])) {
93 office 21 http_response_code(403);
22 die('Forbidden.');
23 }
52 office 24 #### Retrieve uploaded file.
25 if (!empty($_FILES['file']) and
26 is_uploaded_file($_FILES['file']['tmp_name'])) {
67 office 27 if($_FILES['file']['size'] > $config['ALLOWED_ASSET_SIZE'] * 1048576) {
81 office 28 http_response_code(403);
29 die('File size exceeds '.$config['ALLOWED_ASSET_SIZE'].'MiB.');
57 office 30 }
52 office 31 # Regular multipart/form-data upload.
32 $name = $_FILES['file']['name'];
57 office 33 $data = atomized_get_contents($_FILES['file']['tmp_name']);
52 office 34 } else {
67 office 35 if((int)get_file_size("php://input") > $config['ALLOWED_ASSET_SIZE'] * 1048576) {
81 office 36 http_response_code(403);
37 die('File size exceeds '.$config['ALLOWED_ASSET_SIZE'].'MiB.');
57 office 38 }
52 office 39 # Raw POST data.
40 $name = urldecode(@$_SERVER['HTTP_X_FILE_NAME']);
57 office 41 $data = atomized_get_contents("php://input");
52 office 42 }
7 office 43  
52 office 44 #### Grab the file extension.
45 $fileExtension = pathinfo($name, PATHINFO_EXTENSION);
11 office 46  
52 office 47 #### If the extension is not allowed then change it to a text extension.
48 if (!isset($fileExtension) ||
49 !in_array(strtoupper($fileExtension),
67 office 50 array_map('strtoupper', $config['ALLOWED_FILE_EXTENSIONS']))) {
81 office 51 http_response_code(403);
52 die('File extension not allowed.');
52 office 53 }
14 office 54  
52 office 55 #### Hash filename.
56 $file = strtolower(
57 PseudoCrypt::hash(
58 preg_replace(
59 '/\D/',
60 '',
61 hash(
62 'sha512',
63 $data
64 )
65 ),
67 office 66 $config['ASSET_HASH_SIZE']
7 office 67 )
52 office 68 );
14 office 69  
52 office 70 #### Build the user path.
71 $userPath = join(
72 DIRECTORY_SEPARATOR,
73 array(
67 office 74 $config['STORE_FOLDER'],
52 office 75 $file
76 )
77 );
11 office 78  
102 office 79 #### Check for path traversals.
52 office 80 $pathPart = pathinfo($userPath.'.'.$fileExtension);
81 if (strcasecmp(
67 office 82 realpath($pathPart['dirname']), realpath($config['STORE_FOLDER'])) != 0) {
81 office 83 http_response_code(500);
84 die('Internal server error.');
52 office 85 }
7 office 86  
52 office 87 #### Store the file.
81 office 88 $timestamp = atomized_put_contents($userPath.'.'.$fileExtension, $data);
125 office 89  
140 office 90 ### Log IP address.
91 $db = new PDO('sqlite:db/scratch.sqlite3');
92 $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
93 try {
94 $db->beginTransaction();
95  
96 ## Create tags table if it does not exist.
97 $db->query('CREATE TABLE IF NOT EXISTS "uploaders" ("hash" text NOT NULL COLLATE NOCASE, "ip" text COLLATE NOCASE, UNIQUE("hash") ON CONFLICT REPLACE)');
98  
99 $q = $db->prepare('REPLACE INTO "uploaders" ("hash", "ip") VALUES(:hash, :ip)');
100 $q->bindParam(':hash', $file);
141 office 101 $q->bindParam(':ip', get_ip_address());
140 office 102 $q->execute();
103  
104 $db->commit();
105 } catch (Exception $e) {
106 error_log($e);
107 ## Rollback.
108 $db->rollback();
109 }
110  
125 office 111 ### Process any sent tags.
112 if(isset($_POST['tags'])) {
113 $tags = json_decode(
114 stripslashes(
115 $_POST['tags']
116 )
117 );
118  
119 ## If we have any tags then insert them into the database.
120 if(!empty($tags)) {
126 office 121 ## Connect or create the scratch database.
122 $db = new PDO('sqlite:db/scratch.sqlite3');
123 ## Set the error mode to exceptions.
124 $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
125 office 125 try {
126 office 126  
125 office 127 $db->beginTransaction();
126 office 128  
129 ## Create tags table if it does not exist.
128 office 130 $db->query('CREATE TABLE IF NOT EXISTS "tags" ("hash" text NOT NULL COLLATE NOCASE, "tag" text COLLATE NOCASE, UNIQUE("hash", "tag") ON CONFLICT REPLACE)');
126 office 131  
132 ## Now add all the tags.
125 office 133 foreach($tags as $tag) {
126 office 134 $q = $db->prepare('REPLACE INTO "tags" ("hash", "tag") VALUES(:hash, :tag)');
135 $q->bindParam(':hash', $file);
136 $q->bindParam(':tag', $tag);
137 $q->execute();
125 office 138 }
139 $db->commit();
140 } catch (Exception $e) {
126 office 141 error_log($e);
125 office 142 ## Rollback.
143 $db->rollback();
144 }
145 }
146 }
14 office 147  
90 office 148 ### Hook for various file extensions.
149 $opengraph = FALSE;
150 switch(strtoupper($fileExtension)) {
103 office 151 case 'MP4':
90 office 152 case 'GIF':
153 $opengraph = TRUE;
154 break;
155 }
125 office 156  
52 office 157 ### Return the URL to the file.
158 header('Content-Type: text/plain; charset=utf-8');
81 office 159 echo json_encode(
160 array(
161 "hash" => $file,
90 office 162 "timestamp" => $timestamp,
163 "opengraph" => $opengraph
81 office 164 )
165 );
52 office 166 break;
167 case 'GET':
83 office 168 ### Tell browser not to cache files.
169 header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
170 header("Cache-Control: post-check=0, pre-check=0", false);
171 header("Pragma: no-cache");
172  
52 office 173 ### If no file has been specified for download then return.
81 office 174 if (!isset($_GET['hash']) or empty($_GET['hash'])) {
175 http_response_code(404);
176 die('File not found.');
52 office 177 }
178  
53 office 179 ### Find the requested file.
52 office 180 $file = array_shift(
181 preg_grep(
81 office 182 '/'.$_GET['hash'].'/',
67 office 183 scandir($config['STORE_FOLDER'])
52 office 184 )
185 );
186  
81 office 187 if (!isset($file) or empty($file)) {
188 http_response_code(404);
189 die('File not found.');
190 }
53 office 191  
192 ### Check the path for path traversals.
193 $fileExtension = pathinfo($file, PATHINFO_EXTENSION);
52 office 194  
53 office 195 #### If the extension is not allowed then return.
196 if (!isset($fileExtension) ||
197 !in_array(strtoupper($fileExtension),
67 office 198 array_map('strtoupper', $config['ALLOWED_FILE_EXTENSIONS']))) {
81 office 199 http_response_code(403);
200 die('File extension not allowed.');
52 office 201 }
53 office 202  
203 #### Build the user path.
204 $userPath = join(
205 DIRECTORY_SEPARATOR,
206 array(
67 office 207 $config['STORE_FOLDER'],
53 office 208 $file
209 )
210 );
52 office 211  
53 office 212 #### Check for path traversals
213 $pathPart = pathinfo($userPath);
214 if (strcasecmp(
67 office 215 realpath($pathPart['dirname']), realpath($config['STORE_FOLDER'])) != 0) {
81 office 216 http_response_code(500);
217 die('Internal server error.');
53 office 218 }
219  
83 office 220 ### Hook for various file extensions.
53 office 221 switch(strtoupper($fileExtension)) {
222 case "HTML":
223 case "HTM":
224 header('Content-type: text/html');
225 break;
226 break;
83 office 227 case "URL":
91 office 228 if(preg_match(
229 "/URL=(https?:\/\/[\-_\.\+!\*'\(\),a-zA-Z0-9]+:?[0-9]{0,5}\/.*?)\n/",
230 file_get_contents($userPath), $matches)) {
83 office 231 header('Location: '.$matches[1]);
232 return;
233 }
234 break;
53 office 235 default:
236 ### Open MIME info database and send the content type.
237 $finfo = finfo_open(FILEINFO_MIME_TYPE);
238 if (!$finfo) {
81 office 239 http_response_code(500);
240 die('Internal server error.');
53 office 241 }
242 header('Content-type: '.finfo_file($finfo, $userPath));
243 finfo_close($finfo);
244 break;
245 }
246  
52 office 247 ### Send the file along with the inline content disposition.
53 office 248 header('Content-length: '.(int)get_file_size($userPath));
249 header('Content-Disposition: inline; filename="' . basename($userPath) . '"');
250 header('Content-Transfer-Encoding: binary');
251 header('X-Sendfile: '.$userPath);
52 office 252 break;
253 }