Skip to content

Instantly share code, notes, and snippets.

@toriato
Last active February 21, 2023 08:56
Show Gist options
  • Save toriato/95edf3c364b2b8b2d6b8be63a482703f to your computer and use it in GitHub Desktop.
Save toriato/95edf3c364b2b8b2d6b8be63a482703f to your computer and use it in GitHub Desktop.
simple file uploader and url shortener for sharex
<?php
define('MYSQL_DSN', 'mysql:host=mariadb;dbname=aeon');
define('MYSQL_USER', 'MYUSER');
define('MYSQL_PASS', 'MYP4SS');
define('MYSQL_OPTIONS', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
define('URLS_MAX_ID_DUPLICATED_RETRIES', 10);
define('FILES_BASEDIR', 'files');
// https://gist.github.com/henriquemoody/6580488
define('HTTP_STATUS_MESSAGES', [
100 => 'Continue',
101 => 'Switching Protocols',
102 => 'Processing', // WebDAV; RFC 2518
103 => 'Early Hints', // RFC 8297
200 => 'OK',
201 => 'Created',
202 => 'Accepted',
203 => 'Non-Authoritative Information', // since HTTP/1.1
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content', // RFC 7233
207 => 'Multi-Status', // WebDAV; RFC 4918
208 => 'Already Reported', // WebDAV; RFC 5842
226 => 'IM Used', // RFC 3229
300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Found', // Previously "Moved temporarily"
303 => 'See Other', // since HTTP/1.1
304 => 'Not Modified', // RFC 7232
305 => 'Use Proxy', // since HTTP/1.1
306 => 'Switch Proxy',
307 => 'Temporary Redirect', // since HTTP/1.1
308 => 'Permanent Redirect', // RFC 7538
400 => 'Bad Request',
401 => 'Unauthorized', // RFC 7235
402 => 'Payment Required',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required', // RFC 7235
408 => 'Request Timeout',
409 => 'Conflict',
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed', // RFC 7232
413 => 'Payload Too Large', // RFC 7231
414 => 'URI Too Long', // RFC 7231
415 => 'Unsupported Media Type', // RFC 7231
416 => 'Range Not Satisfiable', // RFC 7233
417 => 'Expectation Failed',
418 => 'I\'m a teapot', // RFC 2324, RFC 7168
421 => 'Misdirected Request', // RFC 7540
422 => 'Unprocessable Entity', // WebDAV; RFC 4918
423 => 'Locked', // WebDAV; RFC 4918
424 => 'Failed Dependency', // WebDAV; RFC 4918
425 => 'Too Early', // RFC 8470
426 => 'Upgrade Required',
428 => 'Precondition Required', // RFC 6585
429 => 'Too Many Requests', // RFC 6585
431 => 'Request Header Fields Too Large', // RFC 6585
451 => 'Unavailable For Legal Reasons', // RFC 7725
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported',
506 => 'Variant Also Negotiates', // RFC 2295
507 => 'Insufficient Storage', // WebDAV; RFC 4918
508 => 'Loop Detected', // WebDAV; RFC 5842
510 => 'Not Extended', // RFC 2774
511 => 'Network Authentication Required', // RFC 6585
]);
function exitWithStatusCode(int $statusCode, string $message = null): never
{
// use default status message if exists
if (is_null($message) && in_array($statusCode, HTTP_STATUS_MESSAGES)) {
$message = HTTP_STATUS_MESSAGES[$statusCode];
}
http_response_code($statusCode);
die($message);
}
$db = new PDO(MYSQL_DSN, MYSQL_USER, MYSQL_PASS, MYSQL_OPTIONS);
switch ($_SERVER['REQUEST_METHOD']) {
case 'GET':
$id = base64_decode(str_replace(['-', '_'], ['+', '/'], trim(@$_GET['id'])));
if (empty($id)) {
header('Location: https://gall.dcinside.com/mini/owo');
die;
}
if ($id === false) {
exitWithStatusCode(404, 'Malformed ID');
}
$sth = $db->prepare('SELECT url FROM urls WHERE id = :id');
$sth->bindValue(':id', $id, PDO::PARAM_INT);
$sth->execute();
if ($sth->rowCount() < 1) {
exitWithStatusCode(404);
}
$record = $sth->fetch();
header('Location: ' . $record['url']);
break;
case 'POST':
$token = @$_POST['token'];
$sth = $db->prepare(<<<SQL
SELECT node
FROM token_permission
WHERE token_id = (
SELECT id
FROM token
WHERE token = :token
)
SQL);
$sth->bindValue(':token', $token, PDO::PARAM_STR);
$sth->execute();
$nodes = array_merge(...array_map(fn ($n) => array_values($n), $sth->fetchAll()));
if (!empty(@$_POST['url'])) {
if (!in_array('urls.create', $nodes)) {
exitWithStatusCode(403);
}
$url = $_POST['url'];
if (!filter_var($url, FILTER_VALIDATE_URL)) {
exitWithStatusCode(400, 'Malformed URL');
}
// while until id is not duplicated
$retries = 0;
while (++$retries < URLS_MAX_ID_DUPLICATED_RETRIES) {
try {
$sth = $db->prepare(<<<SQL
INSERT INTO urls (token_id, url) VALUES ((SELECT id FROM token WHERE token = :token), :url)
SQL);
$sth->bindValue(':token', $token);
$sth->bindValue(':url', $url);
$sth->execute();
$id = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($db->lastInsertId()));
exitWithStatusCode(200, 'https://vmm.pw/' . $id);
} catch (PDOException $e) {
switch ($e->errorInfo[1]) {
// duplicated entry
case 1062:
continue 2;
default:
exitWithStatusCode(500, 'Database Error');
}
}
}
} else if (isset($_FILES['file'])) {
if (!in_array('files.create', $nodes)) {
exitWithStatusCode(403);
}
$file = $_FILES['file'];
if ($file['error'] !== UPLOAD_ERR_OK) {
exitWithStatusCode(500, 'Upload Error');
}
// check mime types
$mime = mime_content_type($file['tmp_name']);
switch (true) {
case in_array('files.allowed_types.*', $nodes): // power of asterisk!
case in_array("files.allowed_types.$mime", $nodes): // specific mime type
break;
default:
exitWithStatusCode(415);
}
$path = join(DIRECTORY_SEPARATOR, [FILES_BASEDIR, floor(microtime(true) * 1000), $file['name']]);
mkdir(dirname($path), 0777, true);
move_uploaded_file($file['tmp_name'], $path);
exitWithStatusCode(200, 'https://vmm.pw/' . $path);
}
exitWithStatusCode(500);
break;
default:
exitWithStatusCode(405);
}
{
"Version": "14.0.1",
"Name": "VMM.PW",
"DestinationType": "ImageUploader, TextUploader, FileUploader, URLShortener",
"RequestMethod": "POST",
"RequestURL": "https://vmm.pw",
"Body": "MultipartFormData",
"Arguments": {
"token": "CHANGE_ME",
"url": "{input}"
},
"FileFormName": "file",
"URL": "{response}"
}
/*!40101 SET NAMES utf8 */;
/*!50503 SET NAMES utf8mb4 */;
CREATE TABLE IF NOT EXISTS `token` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`token` char(128) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `token` (`token`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `token_permission` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`token_id` int(10) unsigned NOT NULL,
`node` varchar(64) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `token_node` (`token_id`,`node`) USING BTREE,
CONSTRAINT `FK_token_permission_token` FOREIGN KEY (`token_id`) REFERENCES `token` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `urls` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`token_id` int(10) unsigned NOT NULL,
`url` varchar(2083) NOT NULL,
`created_at` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`),
KEY `FK_urls_token` (`token_id`) USING BTREE,
CONSTRAINT `FK_urls_token` FOREIGN KEY (`token_id`) REFERENCES `token` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment