From 4478d70d151197f4734faa91a9c12781ccf3e639 Mon Sep 17 00:00:00 2001 From: Andreas Schulte <0x0001f346@pm.me> Date: Tue, 26 Aug 2025 21:54:30 +0200 Subject: [PATCH] first commit --- .gitignore | 2 + LICENSE | 18 +++ README.md | 82 ++++++++++ app/app.go | 122 +++++++++++++++ app/assets/favicon.svg | 65 ++++++++ app/assets/index.html | 40 +++++ app/assets/script.js | 243 +++++++++++++++++++++++++++++ app/assets/style.css | 130 ++++++++++++++++ app/auth.go | 15 ++ app/http.go | 278 ++++++++++++++++++++++++++++++++++ build/.gitkeep | 0 config/certs.go | 97 ++++++++++++ config/config.go | 68 +++++++++ config/filesystem.go | 22 +++ config/flags.go | 165 ++++++++++++++++++++ filesystem/filesystem.go | 137 +++++++++++++++++ filesystem/filesystem_test.go | 113 ++++++++++++++ go.mod | 5 + go.sum | 2 + main.go | 13 ++ screenshot.png | Bin 0 -> 56472 bytes 21 files changed, 1617 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 app/app.go create mode 100644 app/assets/favicon.svg create mode 100644 app/assets/index.html create mode 100644 app/assets/script.js create mode 100644 app/assets/style.css create mode 100644 app/auth.go create mode 100644 app/http.go create mode 100644 build/.gitkeep create mode 100644 config/certs.go create mode 100644 config/config.go create mode 100644 config/filesystem.go create mode 100644 config/flags.go create mode 100644 filesystem/filesystem.go create mode 100644 filesystem/filesystem_test.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 screenshot.png diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1464c71 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/build/* +!/build/.gitkeep \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..379ec1c --- /dev/null +++ b/LICENSE @@ -0,0 +1,18 @@ +MIT License + +Copyright (c) 2025 Andreas Schulte + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..805f233 --- /dev/null +++ b/README.md @@ -0,0 +1,82 @@ +# ablage + +**A secure, minimal file exchange web application with optional authentication and HTTPS support.** + +![Screenshot of ablage](./screenshot.png) + +## Features + +- Drag & drop file upload with real time progress +- Download and delete uploaded files directly from the web interface +- Fully responsive web UI for desktop and mobile +- HTTPS support with self-signed or user-provided certificates +- Sinkhole mode to hide existing files +- Optional password protection +- HTTP mode for local, unencrypted usage +- No external dependencies on runtime +- No bullshit + +## Installation + +1. Clone the repository: + +```bash +git clone https://git.0x0001f346.de/andreas/ablage.git +cd ablage +``` + +2. Build and run: + +```bash +go build -o build/ . && build/ablage [flags] +``` + +## Usage & Flags + +| Flag | Description | +| ------------ | ------------------------------------------------------------------------------------------- | +| `--auth` | Enable Basic Authentication. | +| `--cert` | Path to a custom TLS certificate file (PEM format). | +| `--http` | Enable HTTP mode. Nothing will be encrypted. | +| `--key` | Path to a custom TLS private key file (PEM format). | +| `--password` | Set password for Basic Authentication (or let ablage generate a random one). | +| `--path` | Set path to the data folder (default is `data` in the same directory as the ablage binary). | +| `--port` | Set port to listen on (default is `13692`). | +| `--readonly` | Enable readonly mode. No files can be uploaded or deleted. | +| `--sinkhole` | Enable sinkhole mode. Existing files in the storage folder won't be visible. | + +## Accessing the Web UI + +- Open your browser and navigate to `https://localhost:13692` (or `http://localhost:13692` if using `--http`) +- If `--auth` is enabled, use the username `ablage` and the auto-generated password or provide your own with `--password` + +## File Storage + +- Uploaded files are stored in a `data` folder in the same directory as the binary by default (can be changed via `--path`) +- Sinkhole mode hides these files from the web UI but they remain on disk + +## TLS Certificates + +- By default, ablage uses an ephemeral, self-signed certificate generated on each start +- To use your own certificate, pass the paths to your key and certificate with `--key` and `--cert` + +### Generating a test certificate + +To generate a **test key/certificate pair** for local testing with elliptic curve cryptography (P-256 curve), use: + +```bash +openssl req -x509 \ + -newkey ec \ + -pkeyopt ec_paramgen_curve:P-256 \ + -nodes \ + -keyout /tmp/test.key \ + -out /tmp/test.crt \ + -days 365 \ + -subj "/CN=localhost" +``` + +Then start **ablage** like this: + +```bash +./ablage --cert /tmp/test.crt --key /tmp/test.key +``` diff --git a/app/app.go b/app/app.go new file mode 100644 index 0000000..1e924df --- /dev/null +++ b/app/app.go @@ -0,0 +1,122 @@ +package app + +import ( + "crypto/tls" + _ "embed" + "fmt" + "io" + "log" + "net/http" + "os" + + "git.0x0001f346.de/andreas/ablage/config" + "github.com/julienschmidt/httprouter" +) + +//go:embed assets/index.html +var assetIndexHTML []byte + +//go:embed assets/favicon.svg +var assetFaviconSVG []byte + +//go:embed assets/script.js +var assetScriptJS []byte + +//go:embed assets/style.css +var assetStyleCSS []byte + +func Init() { + router := httprouter.New() + + router.NotFound = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, "/", http.StatusSeeOther) + }) + + router.MethodNotAllowed = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, "/", http.StatusSeeOther) + }) + + router.GET(httpPathRoot, httpGetRoot) + router.GET(httpPathConfig, httpGetConfig) + router.GET(httpPathFaviconICO, httpGetFaviconICO) + router.GET(httpPathFaviconSVG, httpGetFaviconSVG) + router.GET(httpPathFiles, httpGetFiles) + router.GET(httpPathFilesDeleteFilename, httpGetFilesDeleteFilename) + router.GET(httpPathFilesGetFilename, httpGetFilesGetFilename) + router.GET(httpPathScriptJS, httpGetScriptJS) + router.GET(httpPathStyleCSS, httpGetStyleCSS) + router.POST(httpPathUpload, httpPostUpload) + + var handler http.Handler = router + + if config.GetBasicAuthMode() { + handler = basicAuthMiddleware(handler, config.GetBasicAuthUsername(), config.GetBasicAuthPassword()) + } + + if config.GetHttpMode() { + config.PrintStartupBanner() + err := http.ListenAndServe(fmt.Sprintf(":%d", config.GetPortToListenOn()), handler) + if err != nil { + fmt.Fprintf(os.Stderr, "Ablage exited with error:\n%v\n", err) + os.Exit(1) + } + return + } + + tlsCert, err := tls.X509KeyPair(config.GetTLSCertificate(), config.GetTLSKey()) + if err != nil { + fmt.Fprintf(os.Stderr, "Faild to parse PEM encoded public/private key pair:\n%v\n", err) + os.Exit(1) + } + + server := &http.Server{ + Addr: fmt.Sprintf(":%d", config.GetPortToListenOn()), + ErrorLog: log.New(io.Discard, "", 0), + Handler: handler, + TLSConfig: &tls.Config{ + Certificates: []tls.Certificate{tlsCert}, + }, + TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)), + } + + config.PrintStartupBanner() + + err = server.ListenAndServeTLS("", "") + if err != nil { + fmt.Fprintf(os.Stderr, "Ablage exited with error:\n%v\n", err) + os.Exit(1) + } +} + +func getClientIP(r *http.Request) string { + if r.Header.Get("X-Forwarded-For") != "" { + return r.Header.Get("X-Forwarded-For") + } + + return r.RemoteAddr +} + +func isBrowserDisplayableFileType(extension string) bool { + browserDisplayableFileTypes := map[string]struct{}{ + // audio + ".mp3": {}, ".ogg": {}, ".wav": {}, + // pictures + ".bmp": {}, ".gif": {}, ".ico": {}, ".jpg": {}, ".jpeg": {}, + ".png": {}, ".svg": {}, ".webp": {}, + // programming + ".bat": {}, ".cmd": {}, ".c": {}, ".cpp": {}, ".go": {}, + ".h": {}, ".hpp": {}, ".java": {}, ".kt": {}, ".lua": {}, + ".php": {}, ".pl": {}, ".ps1": {}, ".py": {}, ".rb": {}, + ".rs": {}, ".sh": {}, ".swift": {}, ".ts": {}, ".tsx": {}, + // text + ".csv": {}, ".log": {}, ".md": {}, ".pdf": {}, ".txt": {}, + // video + ".mp4": {}, ".webm": {}, + // web + ".css": {}, ".js": {}, ".html": {}, + } + + _, isBrowserDisplayableFileType := browserDisplayableFileTypes[extension] + + return isBrowserDisplayableFileType +} diff --git a/app/assets/favicon.svg b/app/assets/favicon.svg new file mode 100644 index 0000000..231db7e --- /dev/null +++ b/app/assets/favicon.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/assets/index.html b/app/assets/index.html new file mode 100644 index 0000000..e8cbe9a --- /dev/null +++ b/app/assets/index.html @@ -0,0 +1,40 @@ + + + + + + Ablage + + + + + + + + + + + + + + + + + diff --git a/app/assets/script.js b/app/assets/script.js new file mode 100644 index 0000000..aa09ed7 --- /dev/null +++ b/app/assets/script.js @@ -0,0 +1,243 @@ +(() => { + "use strict"; + + let AppConfig = null; + let UI = {}; + + async function appLoop() { + if (AppConfig === null) { + return; + } + + updateUI(); + fetchFiles(); + } + + function updateUI() { + if (AppConfig.Modes.Readonly) { + UI.dropzone.style.display = "none"; + } else { + UI.dropzone.style.display = "block"; + } + + if (AppConfig.Modes.Sinkhole) { + UI.fileList.style.display = "none"; + UI.sinkholeModeInfo.style.display = "block"; + } else { + UI.fileList.style.display = "block"; + UI.sinkholeModeInfo.style.display = "none"; + } + } + + async function initApp() { + UI.currentFileName = document.getElementById("currentFileName"); + UI.dropzone = document.getElementById("dropzone"); + UI.fileInput = document.getElementById("fileInput"); + UI.fileList = document.getElementById("file-list"); + UI.overallProgress = document.getElementById("overallProgress"); + UI.overallStatus = document.getElementById("overallStatus"); + UI.overallProgressContainer = document.getElementById( + "overallProgressContainer" + ); + UI.sinkholeModeInfo = document.getElementById("sinkholeModeInfo"); + + UI.dropzone.addEventListener("click", () => UI.fileInput.click()); + UI.fileInput.addEventListener("change", () => { + if (UI.fileInput.files.length > 0) uploadFiles(UI.fileInput.files); + }); + UI.dropzone.addEventListener("dragover", (e) => { + e.preventDefault(); + UI.dropzone.style.borderColor = "#0fff50"; + }); + UI.dropzone.addEventListener("dragleave", () => { + UI.dropzone.style.borderColor = "#888"; + }); + UI.dropzone.addEventListener("drop", (e) => { + e.preventDefault(); + UI.dropzone.style.borderColor = "#888"; + if (e.dataTransfer.files.length > 0) uploadFiles(e.dataTransfer.files); + }); + + await loadAppConfig(); + appLoop(); + + setInterval(appLoop, 5 * 1000); + setInterval(loadAppConfig, 60 * 1000); + } + + async function loadAppConfig() { + try { + const res = await fetch("/config/", { cache: "no-store" }); + if (!res.ok) { + console.error("HTTP error:", res.status); + } + AppConfig = await res.json(); + } catch (err) { + console.error("Failed to load config:", err); + AppConfig = null; + } + } + + async function fetchFiles() { + if (AppConfig.Modes.Sinkhole) { + UI.fileList.innerHTML = ""; + return; + } + + try { + const res = await fetch(AppConfig.Endpoints.Files, { cache: "no-store" }); + if (!res.ok) throw new Error("HTTP " + res.status); + const files = await res.json(); + + if (!UI.fileList) return; + UI.fileList.innerHTML = ""; + files.forEach((file) => { + const size = humanReadableSize(file.Size); + + const li = document.createElement("li"); + + const downloadLink = document.createElement("a"); + downloadLink.className = "download-link"; + downloadLink.href = AppConfig.Endpoints.FilesGet.replace( + ":filename", + encodeURIComponent(file.Name) + ); + downloadLink.textContent = `${file.Name} (${size})`; + + li.appendChild(downloadLink); + + if (!AppConfig.Modes.Readonly) { + const deleteLink = document.createElement("a"); + deleteLink.className = "delete-link"; + deleteLink.href = "#"; + deleteLink.textContent = " [Delete]"; + deleteLink.title = "Delete file"; + deleteLink.addEventListener("click", async (e) => { + e.preventDefault(); + if (!confirm(`Do you really want to delete "${file.Name}"?`)) + return; + try { + const r = await fetch( + AppConfig.Endpoints.FilesDelete.replace( + ":filename", + encodeURIComponent(file.Name) + ), + { method: "GET" } + ); + if (!r.ok) throw new Error("Delete failed " + r.status); + fetchFiles(); + } catch (err) { + console.error(err); + } + }); + + li.appendChild(deleteLink); + } + + UI.fileList.appendChild(li); + }); + } catch (err) { + console.error("fetchFiles failed:", err); + } + } + + function humanReadableSize(bytes) { + const units = ["B", "KB", "MB", "GB", "TB"]; + let i = 0; + while (bytes >= 1024 && i < units.length - 1) { + bytes /= 1024; + i++; + } + return `${bytes.toFixed(1)} ${units[i]}`; + } + + function humanReadableSpeed(bytesPerSec) { + if (!isFinite(bytesPerSec) || bytesPerSec <= 0) return "—"; + if (bytesPerSec < 1024) return bytesPerSec.toFixed(0) + " B/s"; + if (bytesPerSec < 1024 * 1024) + return (bytesPerSec / 1024).toFixed(1) + " KB/s"; + return (bytesPerSec / (1024 * 1024)).toFixed(2) + " MB/s"; + } + + function uploadFiles(fileListLike) { + const files = Array.from(fileListLike); + if (files.length === 0) return; + + UI.overallProgressContainer.style.display = "block"; + UI.overallProgress.value = 0; + UI.overallStatus.textContent = ""; + UI.currentFileName.textContent = ""; + + const totalSize = files.reduce((sum, f) => sum + f.size, 0); + let uploadedBytes = 0; + const t0 = Date.now(); + let idx = 0; + + const uploadNext = () => { + if (idx >= files.length) { + UI.overallProgressContainer.style.display = "none"; + UI.overallProgress.value = 0; + UI.overallStatus.textContent = ""; + UI.currentFileName.textContent = ""; + fetchFiles(); + return; + } + + const file = files[idx]; + UI.currentFileName.textContent = file.name; + + const xhr = new XMLHttpRequest(); + const form = new FormData(); + form.append("uploadfile", file); + + xhr.upload.addEventListener("progress", (e) => { + if (!e.lengthComputable) return; + + const totalUploaded = uploadedBytes + e.loaded; + const percent = (totalUploaded / totalSize) * 100; + UI.overallProgress.value = percent; + + const elapsed = (Date.now() - t0) / 1000; + const speed = totalUploaded / elapsed; + const speedStr = humanReadableSpeed(speed); + + const remainingBytes = totalSize - totalUploaded; + const etaSec = speed > 0 ? remainingBytes / speed : Infinity; + const min = Math.floor(etaSec / 60); + const sec = Math.floor(etaSec % 60); + + UI.overallStatus.textContent = + `${percent.toFixed(1)}% (${(totalSize / 1024 / 1024).toFixed( + 1 + )} MB total) — ` + + `Speed: ${speedStr}, Est. time left: ${ + isFinite(etaSec) ? `${min}m ${sec}s` : "calculating…" + }`; + }); + + xhr.addEventListener("load", () => { + if (xhr.status === 200) { + uploadedBytes += file.size; + } else { + console.error("Upload failed with status", xhr.status); + } + idx++; + uploadNext(); + }); + + xhr.addEventListener("error", () => { + console.error("Network/server error during upload."); + idx++; + uploadNext(); + }); + + xhr.open("POST", AppConfig.Endpoints.Upload); + xhr.send(form); + }; + + fetchFiles(); + uploadNext(); + } + + document.addEventListener("DOMContentLoaded", initApp); +})(); diff --git a/app/assets/style.css b/app/assets/style.css new file mode 100644 index 0000000..69ac15e --- /dev/null +++ b/app/assets/style.css @@ -0,0 +1,130 @@ +/* Base */ +body { + background-color: #0d1117; + color: #fefefe; + font-family: monospace, monospace; + margin: 20px auto; + max-width: 800px; + padding: 0 10px; +} + +/* Dropzone */ +#dropzone { + border: 2px dashed #888; + border-radius: 10px; + color: #fefefe; + cursor: pointer; + margin-bottom: 20px; + padding: 30px; + text-align: center; + transition: all 0.3s ease; +} + +#dropzone:hover { + color: #0fff50; +} + +/* File list */ +#file-list { + list-style: none; + margin-top: 20px; + padding-left: 0; +} + +#file-list li { + align-items: center; + display: flex; + flex-wrap: wrap; + margin-bottom: 8px; +} + +.sinkholeModeInfo { + color: #888; + text-align: center; +} + +/* Links */ +.delete-link { + color: #fefefe; + font-size: 14px; + margin-left: 8px; + text-decoration: none; +} + +.delete-link:hover { + color: #0fff50; +} + +.download-link { + color: #fefefe; + text-decoration: none; + word-break: break-word; +} + +.download-link:hover { + color: #0fff50; +} + +.logo { + color: #0fff50; + text-decoration: none; + text-align: center; +} + +/* Progress */ +#currentFileName { + font-weight: bold; + margin-bottom: 5px; + word-break: break-word; +} + +#overallProgress { + accent-color: #0fff50; + height: 20px; + width: 100%; +} + +#overallProgress::-moz-progress-bar { + background-color: #0fff50; +} + +#overallProgress::-webkit-progress-bar { + background-color: #333; + border-radius: 5px; +} + +#overallProgress::-webkit-progress-value { + background-color: #0fff50; + border-radius: 5px; +} + +#overallProgressContainer { + margin-bottom: 20px; +} + +.status { + color: #fefefe; + font-size: 14px; + margin-top: 4px; +} + +/* Responsive */ +@media (max-width: 600px) { + body { + padding: 0 15px; + } + + #dropzone { + font-size: 14px; + padding: 20px; + } + + .delete-link { + font-size: 12px; + margin-left: 5px; + } + + .download-link { + font-size: 14px; + } +} diff --git a/app/auth.go b/app/auth.go new file mode 100644 index 0000000..b3b65ba --- /dev/null +++ b/app/auth.go @@ -0,0 +1,15 @@ +package app + +import "net/http" + +func basicAuthMiddleware(handler http.Handler, username, password string) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + user, pass, ok := r.BasicAuth() + if !ok || user != username || pass != password { + w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + handler.ServeHTTP(w, r) + }) +} diff --git a/app/http.go b/app/http.go new file mode 100644 index 0000000..dd7d7a4 --- /dev/null +++ b/app/http.go @@ -0,0 +1,278 @@ +package app + +import ( + "encoding/json" + "fmt" + "io" + "log" + "mime" + "net/http" + "os" + "path/filepath" + "strings" + + "git.0x0001f346.de/andreas/ablage/config" + "git.0x0001f346.de/andreas/ablage/filesystem" + "github.com/julienschmidt/httprouter" +) + +const httpPathRoot string = "/" +const httpPathConfig string = "/config/" +const httpPathFaviconICO string = "/favicon.ico" +const httpPathFaviconSVG string = "/favicon.svg" +const httpPathFiles string = "/files/" +const httpPathFilesDeleteFilename string = "/files/delete/:filename" +const httpPathFilesGetFilename string = "/files/get/:filename" +const httpPathScriptJS string = "/script.js" +const httpPathStyleCSS string = "/style.css" +const httpPathUpload string = "/upload/" + +func httpGetConfig(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + type Endpoints struct { + Files string `json:"Files"` + FilesDelete string `json:"FilesDelete"` + FilesGet string `json:"FilesGet"` + Upload string `json:"Upload"` + } + + type Modes struct { + Readonly bool `json:"Readonly"` + Sinkhole bool `json:"Sinkhole"` + } + + type Config struct { + Endpoints Endpoints `json:"Endpoints"` + Modes Modes `json:"Modes"` + } + + var config Config = Config{ + Endpoints: Endpoints{ + Files: httpPathFiles, + FilesDelete: httpPathFilesDeleteFilename, + FilesGet: httpPathFilesGetFilename, + Upload: httpPathUpload, + }, + Modes: Modes{ + Readonly: config.GetReadonlyMode(), + Sinkhole: config.GetSinkholeMode(), + }, + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(config) +} + +func httpGetFaviconICO(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + http.Redirect(w, r, "/favicon.svg", http.StatusSeeOther) +} + +func httpGetFaviconSVG(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + w.Header().Set("Content-Type", "image/svg+xml") + w.Write(assetFaviconSVG) +} + +func httpGetFiles(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + type FileInfo struct { + Name string `json:"Name"` + Size int64 `json:"Size"` + } + + if config.GetSinkholeMode() { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode([]FileInfo{}) + } + + entries, err := os.ReadDir(config.GetPathDataFolder()) + if err != nil { + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + + files := make([]FileInfo, 0, len(entries)) + + for _, entry := range entries { + if entry.IsDir() { + continue + } + + info, err := entry.Info() + if err != nil { + continue + } + + files = append(files, FileInfo{ + Name: info.Name(), + Size: info.Size(), + }) + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(files) +} + +func httpGetFilesDeleteFilename(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + if config.GetReadonlyMode() { + http.Error(w, "403 Forbidden", http.StatusForbidden) + return + } + + if config.GetSinkholeMode() { + http.Error(w, "404 File Not Found", http.StatusNotFound) + return + } + + entries, err := os.ReadDir(config.GetPathDataFolder()) + if err != nil { + http.Error(w, "500 Internal Server Error", http.StatusInternalServerError) + return + } + + files := map[string]int64{} + for _, entry := range entries { + if entry.IsDir() { + continue + } + info, err := entry.Info() + if err != nil { + continue + } + files[info.Name()] = info.Size() + } + + filename := ps.ByName("filename") + sizeInBytes, fileExists := files[filename] + if !fileExists { + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`{"status":"ok"}`)) + return + } + + fullPath := filepath.Join(config.GetPathDataFolder(), filename) + err = os.Remove(fullPath) + if err != nil { + http.Error(w, "500 Internal Server Error", http.StatusInternalServerError) + return + } + + log.Printf("| Delete | %-21s | %-10s | %s\n", getClientIP(r), filesystem.GetHumanReadableSize(sizeInBytes), filename) + + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`{"status":"ok"}`)) +} + +func httpGetFilesGetFilename(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + if config.GetSinkholeMode() { + http.Error(w, "404 File Not Found", http.StatusNotFound) + return + } + + filename := ps.ByName("filename") + filePath := filepath.Join(config.GetPathDataFolder(), filename) + + info, err := os.Stat(filePath) + if err != nil || info.IsDir() { + http.Error(w, "404 File Not Found", http.StatusNotFound) + return + } + + log.Printf("| Download | %-21s | %-10s | %s\n", getClientIP(r), filesystem.GetHumanReadableSize(info.Size()), filename) + + extension := strings.ToLower(filepath.Ext(filename)) + mimeType := mime.TypeByExtension(extension) + if mimeType == "" { + mimeType = "application/octet-stream" + } + w.Header().Set("Content-Type", mimeType) + + if isBrowserDisplayableFileType(extension) { + w.Header().Set("Content-Disposition", "inline; filename=\""+filename+"\"") + } else { + w.Header().Set("Content-Disposition", "attachment; filename=\""+filename+"\"") + } + + http.ServeFile(w, r, filePath) +} + +func httpGetRoot(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.Write(assetIndexHTML) +} + +func httpGetScriptJS(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + w.Header().Set("Content-Type", "text/javascript") + w.Write(assetScriptJS) +} + +func httpGetStyleCSS(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + w.Header().Set("Content-Type", "text/css") + w.Write(assetStyleCSS) +} + +func httpPostUpload(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + if config.GetReadonlyMode() { + http.Error(w, "403 Forbidden", http.StatusForbidden) + return + } + + reader, err := r.MultipartReader() + if err != nil { + http.Error(w, fmt.Sprintf("Could not get multipart reader: %v", err), http.StatusBadRequest) + return + } + + for { + part, err := reader.NextPart() + if err == io.EOF { + break + } + if err != nil { + http.Error(w, fmt.Sprintf("Error reading part: %v", err), http.StatusInternalServerError) + return + } + defer part.Close() + + if part.FileName() == "" { + continue + } + + safeFilename := filesystem.SanitizeFilename(part.FileName()) + pathToFileInDataFolder := filepath.Join(config.GetPathDataFolder(), safeFilename) + pathToFileInUploadFolder := filepath.Join(config.GetPathUploadFolder(), safeFilename) + + if _, err = os.Stat(pathToFileInDataFolder); err == nil { + http.Error(w, "File already exists", http.StatusConflict) + return + } + if _, err = os.Stat(pathToFileInUploadFolder); err == nil { + http.Error(w, "File already exists", http.StatusConflict) + return + } + + uploadFile, err := os.Create(pathToFileInUploadFolder) + if err != nil { + http.Error(w, "500 Internal Server Error", http.StatusInternalServerError) + return + } + defer uploadFile.Close() + + bytesWritten, err := io.Copy(uploadFile, part) + if err != nil { + _ = os.Remove(pathToFileInUploadFolder) + http.Error(w, "500 Internal Server Error", http.StatusInternalServerError) + return + } + + if err = os.Rename(pathToFileInUploadFolder, pathToFileInDataFolder); err != nil { + _ = os.Remove(pathToFileInDataFolder) + _ = os.Remove(pathToFileInUploadFolder) + http.Error(w, "500 Internal Server Error", http.StatusInternalServerError) + return + } + + log.Printf("| Upload | %-21s | %-10s | %s\n", + getClientIP(r), filesystem.GetHumanReadableSize(bytesWritten), safeFilename) + } + + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`{"status":"ok"}`)) +} diff --git a/build/.gitkeep b/build/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/config/certs.go b/config/certs.go new file mode 100644 index 0000000..1fedc06 --- /dev/null +++ b/config/certs.go @@ -0,0 +1,97 @@ +package config + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "math/big" + "os" + "time" +) + +var selfSignedTLSCertificate []byte = []byte{} +var selfSignedTLSKey []byte = []byte{} + +func GetTLSCertificate() []byte { + return selfSignedTLSCertificate +} + +func GetTLSKey() []byte { + return selfSignedTLSKey +} + +func generateSelfSignedTLSCertificate() error { + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return err + } + + serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) + if err != nil { + return err + } + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + CommonName: "ablage", + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(365 * 24 * time.Hour), + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) + if err != nil { + return err + } + + cert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + key, err := x509.MarshalECPrivateKey(privateKey) + if err != nil { + return err + } + keyPEM := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: key}) + + selfSignedTLSCertificate = cert + selfSignedTLSKey = keyPEM + + return nil +} + +func loadOrGenerateTLSCertificate() error { + if GetHttpMode() { + return nil + } + + if pathTLSCertFile == "" || pathTLSKeyFile == "" { + return generateSelfSignedTLSCertificate() + } + + _, err := tls.LoadX509KeyPair(pathTLSCertFile, pathTLSKeyFile) + if err != nil { + return fmt.Errorf("Error: Failed to load TLS certificate or key: %w", err) + } + + certData, err := os.ReadFile(pathTLSCertFile) + if err != nil { + return fmt.Errorf("Error: Failed to read TLS certificate file: %w", err) + } + + keyData, err := os.ReadFile(pathTLSKeyFile) + if err != nil { + return fmt.Errorf("Error: Failed to read TLS key file: %w", err) + } + + selfSignedTLSCertificate = certData + selfSignedTLSKey = keyData + + return nil +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..b563600 --- /dev/null +++ b/config/config.go @@ -0,0 +1,68 @@ +package config + +import ( + "fmt" + "os" +) + +const DefaultBasicAuthUsername string = "ablage" +const DefaultNameDataFolder string = "data" +const DefaultNameUploadFolder string = "upload" +const DefaultPortToListenOn int = 13692 +const LengthOfRandomBasicAuthPassword int = 16 +const VersionString string = "1.0" + +var randomBasicAuthPassword string = generateRandomPassword() + +func Init() { + err := gatherDefaultPaths() + if err != nil { + panic(err) + } + + parseFlags() + + if GetReadonlyMode() && GetSinkholeMode() { + fmt.Println("Error: Cannot enable both readonly and sinkhole modes at the same time.") + os.Exit(1) + } + + err = loadOrGenerateTLSCertificate() + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } +} + +func PrintStartupBanner() { + fmt.Println("****************************************") + fmt.Println("* Ablage *") + fmt.Println("****************************************") + fmt.Printf("Version : %s\n", VersionString) + fmt.Printf("Basic Auth mode: %v\n", GetBasicAuthMode()) + fmt.Printf("HTTP mode : %v\n", GetHttpMode()) + fmt.Printf("Readonly mode : %v\n", GetReadonlyMode()) + fmt.Printf("Sinkhole mode : %v\n", GetSinkholeMode()) + fmt.Printf("Path : %s\n", GetPathDataFolder()) + + if GetBasicAuthMode() { + fmt.Printf("Username : %s\n", GetBasicAuthUsername()) + fmt.Printf("Password : %s\n", GetBasicAuthPassword()) + } + + if GetHttpMode() { + fmt.Printf("Listening on : http://0.0.0.0:%d\n", GetPortToListenOn()) + } else { + if pathTLSCertFile == "" || pathTLSKeyFile == "" { + fmt.Printf("TLS cert : self-signed\n") + fmt.Printf("TLS key : self-signed\n") + } else { + fmt.Printf("TLS cert : %s\n", pathTLSCertFile) + fmt.Printf("TLS key : %s\n", pathTLSKeyFile) + } + + fmt.Printf("Listening on : https://0.0.0.0:%d\n", GetPortToListenOn()) + } + + fmt.Println("") +} diff --git a/config/filesystem.go b/config/filesystem.go new file mode 100644 index 0000000..20c60af --- /dev/null +++ b/config/filesystem.go @@ -0,0 +1,22 @@ +package config + +import ( + "fmt" + "os" + "path/filepath" +) + +var defaultPathDataFolder string = "" +var defaultPathUploadFolder string = "" + +func gatherDefaultPaths() error { + execPath, err := os.Executable() + if err != nil { + return fmt.Errorf("[Error] Could not determine binary path: %v", err) + } + + defaultPathDataFolder = filepath.Join(filepath.Dir(execPath), DefaultNameDataFolder) + defaultPathUploadFolder = filepath.Join(defaultPathDataFolder, DefaultNameUploadFolder) + + return nil +} diff --git a/config/flags.go b/config/flags.go new file mode 100644 index 0000000..2952e71 --- /dev/null +++ b/config/flags.go @@ -0,0 +1,165 @@ +package config + +import ( + "crypto/rand" + "encoding/base64" + "flag" + "fmt" + "os" + "path/filepath" +) + +var basicAuthMode bool = false +var basicAuthPassword string = "" +var httpMode bool = false +var pathDataFolder string = "" +var pathTLSCertFile string = "" +var pathTLSKeyFile string = "" +var pathUploadFolder string = "" +var portToListenOn int = DefaultPortToListenOn +var readonlyMode bool = false +var sinkholeMode bool = false + +func GetBasicAuthMode() bool { + return basicAuthMode +} + +func GetBasicAuthPassword() string { + return basicAuthPassword +} + +func GetBasicAuthUsername() string { + return DefaultBasicAuthUsername +} + +func GetHttpMode() bool { + return httpMode +} + +func GetPathDataFolder() string { + return pathDataFolder +} + +func GetPathTLSCertFile() string { + return pathTLSCertFile +} + +func GetPathTLSKeyFile() string { + return pathTLSKeyFile +} + +func GetPathUploadFolder() string { + return pathUploadFolder +} + +func GetPortToListenOn() int { + return portToListenOn +} + +func GetReadonlyMode() bool { + return readonlyMode +} + +func GetSinkholeMode() bool { + return sinkholeMode +} + +func generateRandomPassword() string { + b := make([]byte, LengthOfRandomBasicAuthPassword) + _, err := rand.Read(b) + if err != nil { + panic(err) + } + return base64.RawURLEncoding.EncodeToString(b)[:LengthOfRandomBasicAuthPassword] +} + +func parseFlags() { + flag.BoolVar(&basicAuthMode, "auth", false, "Enable basic authentication.") + flag.BoolVar(&httpMode, "http", false, "Enable http mode. Nothing will be encrypted.") + flag.BoolVar(&readonlyMode, "readonly", false, "Enable readonly mode. No files can be uploaded or deleted.") + flag.BoolVar(&sinkholeMode, "sinkhole", false, "Enable sinkhole mode. Existing files won't be visible.") + flag.IntVar(&portToListenOn, "port", DefaultPortToListenOn, "Set Port to listen on.") + flag.StringVar(&basicAuthPassword, "password", "", "Set password for basic authentication (or let ablage generate a random one).") + flag.StringVar(&pathDataFolder, "path", "", "Set path to data folder (default is 'data' in the same directory as ablage).") + flag.StringVar(&pathTLSCertFile, "cert", "", "TLS cert file") + flag.StringVar(&pathTLSKeyFile, "key", "", "TLS key file") + + flag.Parse() + + parseFlagValueBasicAuthPassword() + parseFlagValuePortToListenOn() + parseFlagValuePathDataFolder() + parseFlagValuePathTLSCertFile() + parseFlagValuePathTLSKeyFile() +} + +func parseFlagValueBasicAuthPassword() { + if len(basicAuthPassword) < 1 || len(basicAuthPassword) > 128 { + basicAuthPassword = generateRandomPassword() + } +} + +func parseFlagValuePathDataFolder() { + if pathDataFolder == "" { + pathDataFolder = defaultPathDataFolder + pathUploadFolder = defaultPathUploadFolder + return + } + + info, err := os.Stat(pathDataFolder) + if err != nil { + pathDataFolder = defaultPathDataFolder + pathUploadFolder = defaultPathUploadFolder + return + } + + if !info.IsDir() { + pathDataFolder = defaultPathDataFolder + pathUploadFolder = defaultPathUploadFolder + return + } + + pathUploadFolder = filepath.Join(pathDataFolder, DefaultNameUploadFolder) +} + +func parseFlagValuePortToListenOn() { + if portToListenOn < 1 || portToListenOn > 65535 { + portToListenOn = DefaultPortToListenOn + } +} + +func parseFlagValuePathTLSCertFile() { + if pathTLSCertFile == "" { + pathTLSKeyFile = "" + return + } + + info, err := os.Stat(pathTLSCertFile) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: Failed to read cert: %v\n", err) + os.Exit(1) + } + + if info.IsDir() { + fmt.Fprintf(os.Stderr, "Error: Cert must be a file\n") + os.Exit(1) + } +} + +func parseFlagValuePathTLSKeyFile() { + if pathTLSKeyFile == "" { + pathTLSCertFile = "" + return + } + + info, err := os.Stat(pathTLSKeyFile) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: Failed to read key: %v\n", err) + os.Exit(1) + } + + if info.IsDir() { + fmt.Fprintf(os.Stderr, "Error: Key must be a file\n") + os.Exit(1) + } +} diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go new file mode 100644 index 0000000..4dee12c --- /dev/null +++ b/filesystem/filesystem.go @@ -0,0 +1,137 @@ +package filesystem + +import ( + "fmt" + "os" + "path/filepath" + "regexp" + "strings" + + "git.0x0001f346.de/andreas/ablage/config" +) + +func Init() { + err := prepareDataFolder() + if err != nil { + fmt.Println("err") + os.Exit(1) + } + + err = prepareUploadDir() + if err != nil { + fmt.Println("err") + os.Exit(1) + } +} + +func GetHumanReadableSize(bytes int64) string { + const unit int64 = 1024 + + if bytes < unit { + return fmt.Sprintf("%d Bytes", bytes) + } + + div, exp := int64(unit), 0 + + for n := bytes / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + + return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp]) +} + +func SanitizeFilename(dirtyFilename string) string { + if dirtyFilename == "" { + return "upload.bin" + } + + filenameWithoutPath := filepath.Base(dirtyFilename) + + extension := filepath.Ext(filenameWithoutPath) + filenameWithoutPathAndExtension := filenameWithoutPath[:len(filenameWithoutPath)-len(extension)] + + cleanedFilename := strings.ReplaceAll(filenameWithoutPathAndExtension, " ", "_") + cleanedFilename = strings.ReplaceAll(cleanedFilename, "Ä", "Ae") + cleanedFilename = strings.ReplaceAll(cleanedFilename, "ä", "äe") + cleanedFilename = strings.ReplaceAll(cleanedFilename, "Ö", "Oe") + cleanedFilename = strings.ReplaceAll(cleanedFilename, "ö", "oe") + cleanedFilename = strings.ReplaceAll(cleanedFilename, "Ü", "Ue") + cleanedFilename = strings.ReplaceAll(cleanedFilename, "ü", "ue") + cleanedFilename = strings.ReplaceAll(cleanedFilename, "ß", "ss") + + var safeNameRegex = regexp.MustCompile(`[^a-zA-Z0-9._-]+`) + cleanedFilename = safeNameRegex.ReplaceAllString(cleanedFilename, "_") + + for strings.Contains(cleanedFilename, "__") { + cleanedFilename = strings.ReplaceAll(cleanedFilename, "__", "_") + } + + cleanedFilename = strings.Trim(cleanedFilename, "_") + + const maxLenFilename int = 128 + if len(cleanedFilename) > maxLenFilename { + cleanedFilename = cleanedFilename[:maxLenFilename] + } + + return cleanedFilename + extension +} + +func prepareDataFolder() error { + info, err := os.Stat(config.GetPathDataFolder()) + if os.IsNotExist(err) { + if err := os.Mkdir(config.GetPathDataFolder(), 0755); err != nil { + return fmt.Errorf("Error: Could not create folder '%s': %v", config.GetPathDataFolder(), err) + } + } else if err != nil { + return fmt.Errorf("Error: Could not access '%s': %v", config.GetPathDataFolder(), err) + } else if !info.IsDir() { + return fmt.Errorf("Error: '%s' exists but is not a directory", config.GetPathDataFolder()) + } + + pathTestFile := filepath.Join(config.GetPathDataFolder(), ".write_test") + err = os.WriteFile(pathTestFile, []byte("test"), 0644) + if err != nil { + return fmt.Errorf("Error: Could not create test file '%s': %v", pathTestFile, err) + } + + err = os.Remove(pathTestFile) + if err != nil { + return fmt.Errorf("Error: Could not delete test file '%s': %v", pathTestFile, err) + } + + return nil +} + +func prepareUploadDir() error { + info, err := os.Stat(config.GetPathUploadFolder()) + if err == nil { + if !info.IsDir() { + return fmt.Errorf("%s exists, but is not a folder", config.GetPathUploadFolder()) + } + + err = os.RemoveAll(config.GetPathUploadFolder()) + if err != nil { + return fmt.Errorf("Error: Could not delete upload folder '%s': %v", config.GetPathUploadFolder(), err) + } + } else if !os.IsNotExist(err) { + return fmt.Errorf("Error: '%s' exists but is somewhat broken", config.GetPathUploadFolder()) + } + + if err := os.MkdirAll(config.GetPathUploadFolder(), 0755); err != nil { + return fmt.Errorf("Error: Could not create upload folder '%s': %v", config.GetPathUploadFolder(), err) + } + + pathTestFile := filepath.Join(config.GetPathUploadFolder(), ".write_test") + err = os.WriteFile(pathTestFile, []byte("test"), 0644) + if err != nil { + return fmt.Errorf("Error: Could not create test file '%s': %v", pathTestFile, err) + } + + err = os.Remove(pathTestFile) + if err != nil { + return fmt.Errorf("Error: Could not delete test file '%s': %v", pathTestFile, err) + } + + return nil +} diff --git a/filesystem/filesystem_test.go b/filesystem/filesystem_test.go new file mode 100644 index 0000000..c07175f --- /dev/null +++ b/filesystem/filesystem_test.go @@ -0,0 +1,113 @@ +package filesystem + +import ( + "math" + "testing" +) + +func Test_sanitizeFilename(t *testing.T) { + tests := []struct { + name string + input string + want string + }{ + { + name: "1", + input: "test.png", + want: "test.png", + }, + { + name: "2", + input: "/tmp/test.png", + want: "test.png", + }, + { + name: "3", + input: "../../etc/passwd", + want: "passwd", + }, + { + name: "4", + input: "", + want: "upload.bin", + }, + { + name: "5", + input: "übergrößé.png", + want: "uebergroess.png", + }, + { + name: "6", + input: "my cool file!!.txt", + want: "my_cool_file.txt", + }, + { + name: "7", + input: "so many spaces.txt", + want: "so_many_spaces.txt", + }, + { + name: "8", + input: "/tmp/abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz.txt", + want: "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx.txt", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := SanitizeFilename(tt.input); got != tt.want { + t.Errorf("\nsanitizeFilename()\nname: %v\nwant: %v\ngot: %v", tt.name, tt.want, got) + } + }) + } +} + +func Test_getHumanReadableSize(t *testing.T) { + tests := []struct { + name string + input int64 + want string + }{ + { + name: "1", + input: 7, + want: "7 Bytes", + }, + { + name: "2", + input: 7 * int64(math.Pow(10, 3)), + want: "6.8 KB", + }, + { + name: "3", + input: 7 * int64(math.Pow(10, 6)), + want: "6.7 MB", + }, + { + name: "4", + input: 7 * int64(math.Pow(10, 9)), + want: "6.5 GB", + }, + { + name: "5", + input: 7 * int64(math.Pow(10, 12)), + want: "6.4 TB", + }, + { + name: "6", + input: 7 * int64(math.Pow(10, 15)), + want: "6.2 PB", + }, + { + name: "7", + input: 7 * int64(math.Pow(10, 18)), + want: "6.1 EB", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetHumanReadableSize(tt.input); got != tt.want { + t.Errorf("\ngetHumanReadableSize()\nname: %v\nwant: %v\ngot: %v", tt.name, tt.want, got) + } + }) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..7430fe2 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module git.0x0001f346.de/andreas/ablage + +go 1.24.6 + +require github.com/julienschmidt/httprouter v1.3.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..096c54e --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..4e9967d --- /dev/null +++ b/main.go @@ -0,0 +1,13 @@ +package main + +import ( + "git.0x0001f346.de/andreas/ablage/app" + "git.0x0001f346.de/andreas/ablage/config" + "git.0x0001f346.de/andreas/ablage/filesystem" +) + +func main() { + config.Init() + filesystem.Init() + app.Init() +} diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..9a6aa16d6fc56e4d0f021bef7a605e279519701d GIT binary patch literal 56472 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 yu?(QWGH`f70}2*yBQtE#5)U*Gpo!