2024-08-19 12:48:30 -03:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2024-10-18 11:18:33 -03:00
|
|
|
"embed"
|
2024-08-19 12:48:30 -03:00
|
|
|
"fmt"
|
2024-09-19 15:38:43 -03:00
|
|
|
"html/template"
|
2024-08-19 12:48:30 -03:00
|
|
|
"io"
|
2024-10-18 11:18:33 -03:00
|
|
|
"io/fs"
|
2024-09-19 10:28:23 -03:00
|
|
|
"log/slog"
|
2024-08-19 12:48:30 -03:00
|
|
|
"net/http"
|
|
|
|
"os"
|
2024-08-19 16:11:25 -03:00
|
|
|
"path/filepath"
|
2024-10-15 14:18:55 -03:00
|
|
|
"strings"
|
2024-10-17 10:52:33 -03:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/golang-jwt/jwt/v5"
|
2024-08-19 12:48:30 -03:00
|
|
|
)
|
|
|
|
|
2024-08-25 23:15:11 -03:00
|
|
|
type Application struct {
|
2024-08-20 08:38:56 -03:00
|
|
|
auth struct {
|
|
|
|
username string
|
|
|
|
password string
|
|
|
|
}
|
2024-09-18 10:03:53 -03:00
|
|
|
url string
|
|
|
|
key string
|
|
|
|
filesDir string
|
|
|
|
port string
|
2024-10-17 15:26:13 -03:00
|
|
|
authUpload string
|
2024-09-18 10:03:53 -03:00
|
|
|
lastUploadedFile string
|
2024-08-20 08:38:56 -03:00
|
|
|
}
|
2024-08-19 15:50:17 -03:00
|
|
|
|
2024-10-18 11:18:33 -03:00
|
|
|
//go:embed static/**
|
|
|
|
var static embed.FS
|
|
|
|
|
|
|
|
//go:embed templates/dirlist.html
|
|
|
|
var treeTemplate embed.FS
|
|
|
|
|
2024-09-19 10:28:23 -03:00
|
|
|
func (app *Application) fileListingHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
dir := app.filesDir + r.URL.Path
|
|
|
|
|
|
|
|
files, err := os.ReadDir(dir)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var fileInfos []FileInfo
|
|
|
|
for _, file := range files {
|
|
|
|
filePath := filepath.Join(dir, file.Name())
|
|
|
|
info, err := os.Stat(filePath)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
fileInfos = append(fileInfos, FileInfo{
|
|
|
|
Name: file.Name(),
|
|
|
|
Path: filepath.Join(r.URL.Path, file.Name()),
|
|
|
|
Size: info.Size(),
|
2024-10-03 21:19:14 -03:00
|
|
|
FormattedSize: FormatFileSize(info.Size()),
|
2024-10-16 22:19:47 -03:00
|
|
|
TimeUploaded: info.ModTime().
|
|
|
|
UTC().
|
|
|
|
Format("2006-01-02 15:04:05 UTC"),
|
2024-09-19 10:28:23 -03:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-10-18 11:18:33 -03:00
|
|
|
var tmpl *template.Template
|
|
|
|
|
|
|
|
if _, err := os.Stat("./templates/dirlist.html"); err == nil {
|
|
|
|
tmpl = template.Must(template.ParseFiles("templates/dirlist.html"))
|
|
|
|
} else {
|
|
|
|
tmpl = template.Must(template.ParseFS(treeTemplate, "templates/dirlist.html"))
|
|
|
|
}
|
2024-09-19 10:28:23 -03:00
|
|
|
templateData := TemplateData{
|
|
|
|
Files: fileInfos,
|
|
|
|
URL: app.url,
|
|
|
|
}
|
|
|
|
if err := tmpl.Execute(w, templateData); err != nil {
|
2024-10-15 15:08:27 -03:00
|
|
|
slog.Warn(err.Error())
|
2024-09-19 10:28:23 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-16 21:38:20 -03:00
|
|
|
func (app *Application) fileHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
path := fmt.Sprintf(".%s", filepath.Clean(r.URL.Path))
|
|
|
|
|
|
|
|
if !filepath.IsLocal(path) {
|
|
|
|
http.Error(w, "Wrong url", http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if fileInfo, err := os.Stat(path); err == nil && !fileInfo.IsDir() {
|
|
|
|
http.ServeFile(w, r, path)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-18 09:55:26 -03:00
|
|
|
func (app *Application) indexHandler(w http.ResponseWriter, r *http.Request) {
|
2024-10-18 11:18:33 -03:00
|
|
|
if _, err := os.Stat(app.filesDir); err != nil {
|
|
|
|
if err := os.Mkdir(app.filesDir, 0750); err != nil {
|
|
|
|
http.Error(w, "Error creating storage directory", http.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-25 23:15:11 -03:00
|
|
|
if r.Method == http.MethodPost {
|
2024-10-15 18:27:24 -03:00
|
|
|
app.uploadHandler(w, r)
|
2024-08-25 23:15:11 -03:00
|
|
|
return
|
2024-08-20 08:38:56 -03:00
|
|
|
}
|
|
|
|
|
2024-08-19 20:00:07 -03:00
|
|
|
name := filepath.Clean(r.URL.Path)
|
2024-09-06 16:34:20 -03:00
|
|
|
path := filepath.Join(app.filesDir, name)
|
2024-08-19 20:00:07 -03:00
|
|
|
|
|
|
|
if !filepath.IsLocal(path) {
|
|
|
|
http.Error(w, "Wrong url", http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
2024-08-19 16:11:25 -03:00
|
|
|
|
|
|
|
if fileInfo, err := os.Stat(path); err == nil && !fileInfo.IsDir() {
|
2024-10-16 21:38:20 -03:00
|
|
|
DisplayFile(app, "/"+path, w)
|
2024-09-18 13:58:05 -03:00
|
|
|
return
|
2024-08-19 16:11:25 -03:00
|
|
|
}
|
2024-09-18 13:58:05 -03:00
|
|
|
|
2024-10-18 11:18:33 -03:00
|
|
|
if _, err := os.Stat("./static"); err == nil {
|
|
|
|
http.StripPrefix("/", http.FileServer(http.Dir("./static"))).ServeHTTP(w, r)
|
|
|
|
} else {
|
|
|
|
fs, _ := fs.Sub(static, "static")
|
|
|
|
http.StripPrefix("/", http.FileServer(http.FS(fs))).ServeHTTP(w, r)
|
|
|
|
}
|
2024-08-19 16:11:25 -03:00
|
|
|
}
|
|
|
|
|
2024-10-15 14:18:55 -03:00
|
|
|
func (app *Application) lastUploadedHandler(w http.ResponseWriter, r *http.Request) {
|
2024-09-18 10:03:53 -03:00
|
|
|
if app.lastUploadedFile == "" {
|
|
|
|
http.Error(w, "No new files uploaded yet", http.StatusNotFound)
|
|
|
|
return
|
|
|
|
}
|
2024-10-16 23:20:14 -03:00
|
|
|
DisplayFile(app, "/"+app.lastUploadedFile, w)
|
2024-09-18 10:03:53 -03:00
|
|
|
}
|
|
|
|
|
2024-10-15 14:18:55 -03:00
|
|
|
func (app *Application) uploadHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if contentType := r.Header.Get("Content-Type"); contentType == "application/x-www-form-urlencoded" {
|
|
|
|
app.formHandler(w, r)
|
|
|
|
} else if strings.Split(contentType, ";")[0] == "multipart/form-data" {
|
|
|
|
app.curlHandler(w, r)
|
|
|
|
} else {
|
|
|
|
http.Error(w, "Method not allowed", http.StatusUnauthorized)
|
2024-10-15 13:33:18 -03:00
|
|
|
}
|
2024-10-15 14:18:55 -03:00
|
|
|
}
|
2024-10-15 13:33:18 -03:00
|
|
|
|
2024-10-15 14:18:55 -03:00
|
|
|
func (app *Application) formHandler(w http.ResponseWriter, r *http.Request) {
|
2024-10-15 15:08:27 -03:00
|
|
|
content := r.FormValue("content")
|
|
|
|
|
|
|
|
if err := os.WriteFile("/tmp/file.txt", []byte(content), 0666); err != nil {
|
|
|
|
http.Error(w, "Couldn't parse content body", http.StatusNoContent)
|
|
|
|
}
|
|
|
|
|
|
|
|
file, err := os.Open("/tmp/file.txt")
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Couldn't find file", http.StatusNotFound)
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
2024-10-29 19:20:52 -03:00
|
|
|
full := true
|
2024-10-29 17:51:27 -03:00
|
|
|
if len(r.Form["secret"]) == 0 {
|
2024-10-29 19:20:52 -03:00
|
|
|
full = false
|
2024-10-29 17:51:27 -03:00
|
|
|
}
|
2024-10-29 19:20:52 -03:00
|
|
|
filename := app.publicURL(file, ".txt", full)
|
2024-10-29 17:51:27 -03:00
|
|
|
|
2024-10-15 15:08:27 -03:00
|
|
|
// reopening file because hash consumes it
|
|
|
|
file, err = os.Open("/tmp/file.txt")
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Couldn't find file", http.StatusNotFound)
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
err = SaveFile(app.lastUploadedFile, file)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(w, "Error parsing file: %s", err.Error())
|
|
|
|
}
|
|
|
|
|
2024-10-21 12:01:32 -03:00
|
|
|
ResponseURLHandler(w, app.url, filename)
|
2024-10-15 13:33:18 -03:00
|
|
|
}
|
|
|
|
|
2024-10-15 14:18:55 -03:00
|
|
|
func (app *Application) curlHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if r.URL.Path != "/" {
|
|
|
|
http.Error(w, "Method not allowed", http.StatusUnauthorized)
|
|
|
|
return
|
|
|
|
}
|
2024-10-15 13:33:18 -03:00
|
|
|
|
2024-10-15 14:18:55 -03:00
|
|
|
if !CheckAuth(r, app.key) {
|
|
|
|
http.Error(w, "You're not authorized.", http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
2024-10-15 13:33:18 -03:00
|
|
|
|
2024-08-20 09:00:44 -03:00
|
|
|
file, handler, err := r.FormFile("file")
|
2024-08-19 12:48:30 -03:00
|
|
|
if err != nil {
|
2024-08-20 08:38:56 -03:00
|
|
|
http.Error(w, "Error retrieving the file", http.StatusBadRequest)
|
2024-10-15 15:08:27 -03:00
|
|
|
slog.Warn(err.Error())
|
2024-08-19 12:48:30 -03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
2024-10-29 19:20:52 -03:00
|
|
|
full := true
|
2024-10-29 17:51:27 -03:00
|
|
|
if len(r.Form["secret"]) == 0 {
|
2024-10-29 19:20:52 -03:00
|
|
|
full = false
|
2024-10-29 17:51:27 -03:00
|
|
|
}
|
2024-10-29 19:20:52 -03:00
|
|
|
filename := app.publicURL(file, filepath.Ext(handler.Filename), full)
|
2024-10-29 17:51:27 -03:00
|
|
|
|
2024-09-18 13:48:04 -03:00
|
|
|
// reopen the file for copying, as the hash process consumed the file reader
|
2024-10-15 14:38:01 -03:00
|
|
|
file, _, err = r.FormFile("file")
|
2024-09-18 13:48:04 -03:00
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Error retrieving the file", http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer file.Close()
|
2024-08-20 09:00:44 -03:00
|
|
|
|
2024-10-29 17:51:27 -03:00
|
|
|
if err = SaveFile(app.lastUploadedFile, file); err != nil {
|
2024-10-15 15:08:27 -03:00
|
|
|
fmt.Fprintf(w, "Error parsing file: %s", err.Error())
|
2024-08-19 12:48:30 -03:00
|
|
|
}
|
|
|
|
|
2024-10-21 12:01:32 -03:00
|
|
|
ResponseURLHandler(w, app.url, filename)
|
2024-10-15 14:38:01 -03:00
|
|
|
}
|
|
|
|
|
2024-10-29 19:20:52 -03:00
|
|
|
func (app *Application) publicURL(file io.Reader, extension string, full bool) string {
|
|
|
|
filename, _ := HashFile(file, extension, full)
|
2024-10-15 14:38:01 -03:00
|
|
|
|
|
|
|
filepath := filepath.Join(app.filesDir, filename)
|
|
|
|
|
2024-09-18 10:03:53 -03:00
|
|
|
app.lastUploadedFile = filepath
|
|
|
|
|
2024-10-15 15:14:22 -03:00
|
|
|
return filename
|
2024-08-19 12:48:30 -03:00
|
|
|
}
|
2024-10-17 10:52:33 -03:00
|
|
|
|
|
|
|
func (app *Application) createTokenHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
|
|
|
"exp": time.Now().Add(time.Hour * 2).Unix(),
|
|
|
|
})
|
|
|
|
|
|
|
|
tokenString, err := token.SignedString([]byte(app.key))
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Error generating token", http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Fprintf(w, "%s", tokenString)
|
|
|
|
}
|