From 1246a277c17d8b600f9c9b1de7a12ae6fb6da962 Mon Sep 17 00:00:00 2001
From: steering7253
Date: Wed, 24 Jun 2026 03:22:20 -0400
Subject: init
---
.config.php.example | 7 +++
.gitignore | 1 +
index.html | 56 +++++++++++++++++
static/script.js | 178 ++++++++++++++++++++++++++++++++++++++++++++++++++++
static/style.css | 55 ++++++++++++++++
upload.php | 95 ++++++++++++++++++++++++++++
6 files changed, 392 insertions(+)
create mode 100644 .config.php.example
create mode 100644 .gitignore
create mode 100644 index.html
create mode 100644 static/script.js
create mode 100644 static/style.css
create mode 100644 upload.php
diff --git a/.config.php.example b/.config.php.example
new file mode 100644
index 0000000..dd6d3fe
--- /dev/null
+++ b/.config.php.example
@@ -0,0 +1,7 @@
+
+
+
+ hostfil.es
+
+
+
+
+
+
+
diff --git a/static/script.js b/static/script.js
new file mode 100644
index 0000000..5d48603
--- /dev/null
+++ b/static/script.js
@@ -0,0 +1,178 @@
+/*
+sources:
+MDN:
+ https://developer.mozilla.org/en-US/docs/Web/API/File_API/Using_files_from_web_applications#example_uploading_a_user-selected_file
+ https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop
+web.dev:
+ https://web.dev/patterns/clipboard/paste-files/
+note that this likely means the code is mixed-licensed under:
+ MDN: "Attributions and copyright licensing" by Mozilla Contributors, licensed under CC-BY-SA 2.5.
+ web.dev: code samples are licensed under the Apache 2.0 License. For details, see the Google Developers Site Policies
+*/
+
+addEventListener("DOMContentLoaded", function () {
+ function uploadFiles(files) {
+ for (const file of files) {
+ const li = document.createElement("li");
+ if (file.type.startsWith("image/")) {
+ const img = document.createElement("img");
+ img.src = URL.createObjectURL(file);
+ img.alt = file.name;
+ li.appendChild(img);
+ }
+ li.appendChild(document.createTextNode("Uploading " + file.name + "..."));
+ preview.appendChild(li);
+
+ new FileUpload(li, file)
+ }
+ }
+
+ /* DRAG-N-DROP */
+ const dropZone = document.getElementById("drop-zone");
+ const preview = document.getElementById("preview");
+
+ function dropHandler(ev) {
+ ev.preventDefault();
+ const files = [...ev.dataTransfer.items]
+ .map((item) => item.getAsFile())
+ .filter((file) => file);
+ uploadFiles(files);
+ }
+
+ dropZone.addEventListener("drop", dropHandler);
+
+ dropZone.addEventListener("dragover", (e) => {
+ const fileItems = [...e.dataTransfer.items].filter(
+ (item) => item.kind === "file",
+ );
+ if (fileItems.length > 0) {
+ e.preventDefault();
+ if (fileItems.some((item) => item.type.startsWith("image/"))) {
+ e.dataTransfer.dropEffect = "copy";
+ } else {
+ e.dataTransfer.dropEffect = "none";
+ }
+ }
+ });
+
+ window.addEventListener("dragover", (e) => {
+ const fileItems = [...e.dataTransfer.items].filter(
+ (item) => item.kind === "file",
+ );
+ if (fileItems.length > 0) {
+ e.preventDefault();
+ if (!dropZone.contains(e.target)) {
+ e.dataTransfer.dropEffect = "none";
+ }
+ }
+ });
+
+ const fileInput = document.getElementById("file-input");
+ fileInput.addEventListener("change", (e) => {
+ uploadFiles(e.target.files);
+ });
+
+ window.addEventListener("drop", (e) => {
+ if ([...e.dataTransfer.items].some((item) => item.kind === "file")) {
+ e.preventDefault();
+ }
+ });
+
+
+
+ /* PASTE */
+ document.addEventListener('paste', async (e) => {
+ // Prevent the default behavior, so you can code your own logic.
+ e.preventDefault();
+ if (!e.clipboardData.files.length) {
+ return;
+ }
+ // Iterate over all pasted files.
+ uploadFiles(e.clipboardData.files)
+ });
+
+
+
+ /* EXPANDER */
+ document.querySelectorAll('.expander-toggle').forEach((e) => {
+ e.addEventListener('click', toggle_expand);
+ });
+});
+
+function toggle_expand(ev) {
+ const cl = this.parentNode.querySelector('.expander').classList;
+ const icon = this.parentNode.querySelector('.expander-icon');
+ if (cl.contains('expander-hidden')) {
+ cl.replace('expander-hidden', 'expander-shown');
+ icon.innerHTML = "▼";
+ } else {
+ cl.remove('expander-shown');
+ cl.add('expander-hidden');
+ icon.innerHTML = "►";
+ }
+}
+
+function FileUpload(preview, file) {
+ this.ctrl = createThrobber(preview);
+ const xhr = new XMLHttpRequest();
+ this.xhr = xhr;
+
+ this.xhr.upload.addEventListener("progress", (e) => {
+ if (e.lengthComputable) {
+ const percentage = Math.round((e.loaded * 100) / e.total);
+ this.ctrl.update(percentage);
+ }
+ });
+
+ xhr.upload.addEventListener("load", (e) => {
+ this.ctrl.update(100);
+ preview.removeChild(this.ctrl);
+ });
+ xhr.addEventListener("load", (e) => {
+ const a = document.createElement('a');
+ const text = document.createTextNode(xhr.responseText);
+ a.href = xhr.responseText;
+ a.appendChild(text);
+ preview.appendChild(a);
+ });
+ xhr.open("POST", ascend(preview, "form").action + "/" + file.name);
+ xhr.overrideMimeType("application/octet-stream; charset=x-user-defined-binary");
+ xhr.send(file);
+}
+
+function createThrobber(element) {
+ const throbberWidth = 64;
+ const throbberHeight = 6;
+ const throbber = document.createElement("canvas");
+ throbber.classList.add("upload-progress");
+ throbber.setAttribute("width", throbberWidth);
+ throbber.setAttribute("height", throbberHeight);
+ element.appendChild(throbber);
+ throbber.ctx = throbber.getContext("2d");
+ throbber.ctx.fillStyle = "orange";
+ throbber.update = (percent) => {
+ throbber.ctx.fillRect(
+ 0,
+ 0,
+ (throbberWidth * percent) / 100,
+ throbberHeight,
+ );
+ if (percent === 100) {
+ throbber.ctx.fillStyle = "green";
+ }
+ };
+ throbber.update(0);
+ return throbber;
+}
+
+function ascend(from_element, to_tag_name) {
+ to_tag_name = to_tag_name.toLowerCase();
+ e = from_element;
+
+ while (e && e.parentNode) {
+ e = e.parentNode;
+ if (e.tagName && e.tagName.toLowerCase() == to_tag_name) {
+ return e;
+ }
+ }
+}
diff --git a/static/style.css b/static/style.css
new file mode 100644
index 0000000..b2584d7
--- /dev/null
+++ b/static/style.css
@@ -0,0 +1,55 @@
+body {
+ font-family: "Arial", sans-serif;
+}
+
+#drop-zone {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 500px;
+ max-width: 100%;
+ height: 200px;
+ padding: 1em;
+ border: 1px solid #cccccc;
+ border-radius: 4px;
+ color: slategray;
+ cursor: pointer;
+}
+
+#file-input {
+ display: none;
+}
+
+#preview {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5em;
+ list-style: none;
+ padding: 0;
+}
+
+#preview li {
+ display: flex;
+ align-items: center;
+ gap: 0.5em;
+ margin: 0;
+ width: 100%;
+ height: 100px;
+}
+
+#preview img {
+ width: 100px;
+ height: 100px;
+ object-fit: cover;
+}
+
+.upload-progress {
+ border: 1px solid black;
+}
+
+.expander-hidden {
+ display: none;
+}
+.expander-toggle {
+ cursor: pointer;
+}
diff --git a/upload.php b/upload.php
new file mode 100644
index 0000000..3c0c3a7
--- /dev/null
+++ b/upload.php
@@ -0,0 +1,95 @@
+ $filename");
+ }
+} else {
+ $in_fh = fopen('php://input', 'r');
+ $tmp_path = tempnam(UPLOAD_DIR, '.up-');
+ $out_fh = fopen($tmp_path, 'w');
+ if (FALSE === $out_fh) {
+ die_error("Couldn't open temporary file $tmp_path");
+ }
+ $hash = hash_init("sha512");
+ $size = 0;
+ while (!feof($in_fh) && FALSE !== ($data = fread($in_fh, 1024*1024))) {
+ hash_update($hash, $data);
+ $fwrite_status = fwrite($out_fh, $data);
+ if (FALSE === $fwrite_status || $fwrite_status < strlen($data)) {
+ die_error("Outputting to temporary file $tmp_path failed");
+ }
+ $size += $fwrite_status;
+ }
+ fclose($in_fh);
+ if (FALSE === fclose($out_fh)) {
+ die_error("Closing temporary file $tmp_path failed");
+ }
+ $filename = hash_final($hash) . get_extension($_SERVER['PATH_INFO']);
+ $filepath = UPLOAD_DIR . '/' . $filename;
+ if (file_exists($filepath)) {
+ unlink($tmp_path);
+ } else {
+ if (FALSE === rename($tmp_path, $filepath)) {
+ die_error("Renaming temporary file $tmp_path to $filepath failed");
+ }
+ }
+ echo UPLOAD_URL . $filename . "\n";
+ send_notification("[hostfil.es] $_SERVER[REMOTE_ADDR] uploaded $_SERVER[PATH_INFO] $c_t -> $filename");
+}
+
+
+function send_notification($note) {
+ $sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
+ socket_connect($sock, gethostbyname(NOTIFICATION_ADDRESS), NOTIFICATION_PORT);
+ socket_write($sock, $note, strlen($note));
+ socket_close($sock);
+}
+
+
+function die_error($s) {
+ global $tmp_path, $filepath, $size;
+ header('Status: 500 Server Is 💩');
+ echo $s;
+ error_log($s);
+ @$debug = json_encode(array('s'=>$_SERVER, 'f'=>$_FILES, 't'=>$tmp_path, 'p'=>$filepath, 'size' => $size));
+ send_notification("[hostfil.es] $_SERVER[REMOTE_ADDR] ! $s\n$debug");
+ exit;
+}
+
+
+function get_extension($file) {
+ $ext = strrchr($file, '.');
+ if (FALSE === $ext) {
+ return '';
+ }
+ return $ext;
+}
--
cgit v1.3.1-10-gc9f91