265 lines
7.0 KiB
Go
265 lines
7.0 KiB
Go
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{})
|
|
}
|
|
|
|
files, err := filesystem.GetFileListOfDataFolder()
|
|
if err != nil {
|
|
log.Fatalf("[Error] %v", err)
|
|
}
|
|
|
|
fileInfos := make([]FileInfo, 0, len(files))
|
|
|
|
for filename, sizeInBytes := range files {
|
|
fileInfos = append(
|
|
fileInfos,
|
|
FileInfo{
|
|
Name: filename,
|
|
Size: sizeInBytes,
|
|
},
|
|
)
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(fileInfos)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
filename := ps.ByName("filename")
|
|
|
|
files, err := filesystem.GetFileListOfDataFolder()
|
|
|
|
sizeInBytes, fileExists := files[filename]
|
|
if !fileExists {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write([]byte(`{"status":"ok"}`))
|
|
return
|
|
}
|
|
|
|
err = filesystem.DeleteFile(filename)
|
|
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")
|
|
|
|
files, err := filesystem.GetFileListOfDataFolder()
|
|
if err != nil {
|
|
log.Fatalf("[Error] %v", err)
|
|
}
|
|
|
|
sizeInBytes, fileExists := files[filename]
|
|
if !fileExists {
|
|
http.Error(w, "404 File Not Found", http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
log.Printf("| Download | %-21s | %-10s | %s\n", getClientIP(r), filesystem.GetHumanReadableSize(sizeInBytes), 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.Join(config.GetPathDataFolder(), filename))
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
_, err := filesystem.GetFileListOfDataFolder()
|
|
if err != nil {
|
|
log.Fatalf("[Error] %v", err)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
bytesWritten, err := io.Copy(uploadFile, part)
|
|
uploadFile.Close()
|
|
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"}`))
|
|
}
|