first commit
This commit is contained in:
278
app/http.go
Normal file
278
app/http.go
Normal file
@@ -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"}`))
|
||||
}
|
Reference in New Issue
Block a user