2024-01-11 22:23:10 +01:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2024-04-01 12:58:59 +02:00
|
|
|
"crypto/rand"
|
|
|
|
"encoding/base64"
|
2024-01-11 22:23:10 +01:00
|
|
|
"fmt"
|
2024-03-08 14:59:31 +01:00
|
|
|
"html/template"
|
2024-01-11 22:23:10 +01:00
|
|
|
"io"
|
2024-01-11 22:38:55 +01:00
|
|
|
"log"
|
2024-01-11 22:23:10 +01:00
|
|
|
"net/http"
|
|
|
|
"os"
|
2024-03-09 14:09:03 +01:00
|
|
|
"path"
|
|
|
|
"path/filepath"
|
2024-03-08 14:59:31 +01:00
|
|
|
"strings"
|
2024-01-11 22:38:55 +01:00
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
lastUploadTime time.Time
|
|
|
|
mu sync.Mutex
|
|
|
|
uploadInterval = 10 * time.Second
|
2024-01-11 22:23:10 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
http.HandleFunc("/", homeHandler)
|
|
|
|
http.HandleFunc("/upload", uploadHandler)
|
2024-03-08 15:32:44 +01:00
|
|
|
http.HandleFunc("/image/", imageHandler)
|
2024-03-08 15:01:49 +01:00
|
|
|
http.HandleFunc("/view/", viewHandler)
|
2024-04-01 12:58:59 +02:00
|
|
|
// Statischen Dateipfad setzen
|
|
|
|
fs := http.FileServer(http.Dir("static"))
|
|
|
|
http.Handle("/static/", http.StripPrefix("/static/", fs))
|
2024-01-11 22:23:10 +01:00
|
|
|
|
|
|
|
fmt.Println("Server listening on :8080")
|
|
|
|
http.ListenAndServe(":8080", nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
func homeHandler(w http.ResponseWriter, r *http.Request) {
|
2024-03-08 15:01:49 +01:00
|
|
|
// Setzen der Content Security Policy
|
|
|
|
w.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self'; object-src 'none';")
|
2024-03-08 14:59:31 +01:00
|
|
|
|
|
|
|
// Verwenden von html/template zur sicheren Ausgabe von HTML
|
2024-03-08 15:01:49 +01:00
|
|
|
tmpl, err := template.ParseFiles("templates/homeTemplate.html")
|
2024-03-08 14:59:31 +01:00
|
|
|
if err != nil {
|
2024-03-08 15:01:49 +01:00
|
|
|
http.Error(w, "Fehler beim Laden des Templates", http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
data := struct {
|
|
|
|
Title string
|
|
|
|
}{
|
|
|
|
Title: "Bildupload",
|
|
|
|
}
|
|
|
|
|
|
|
|
err = tmpl.Execute(w, data)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Fehler beim Rendern des Templates", http.StatusInternalServerError)
|
2024-03-08 14:59:31 +01:00
|
|
|
}
|
2024-01-11 22:23:10 +01:00
|
|
|
}
|
|
|
|
|
2024-04-01 12:58:36 +02:00
|
|
|
func generateNonce() (string, error) {
|
2024-04-01 12:58:59 +02:00
|
|
|
nonceBytes := make([]byte, 16) // 16 Bytes generieren eine ausreichend lange Zeichenfolge für den Nonce
|
|
|
|
if _, err := rand.Read(nonceBytes); err != nil {
|
|
|
|
return "", err // Im Fehlerfall, geben Sie den Fehler zurück
|
|
|
|
}
|
|
|
|
return base64.StdEncoding.EncodeToString(nonceBytes), nil
|
2024-04-01 12:58:36 +02:00
|
|
|
}
|
|
|
|
|
2024-01-11 22:23:10 +01:00
|
|
|
func uploadHandler(w http.ResponseWriter, r *http.Request) {
|
2024-04-01 12:58:59 +02:00
|
|
|
nonce, err := generateNonce()
|
|
|
|
if err != nil {
|
|
|
|
// Fehlerbehandlung, z.B. Senden eines Serverfehlers
|
|
|
|
http.Error(w, "Serverfehler", http.StatusInternalServerError)
|
|
|
|
log.Printf("Fehler beim Generieren des Nonce: %v", err)
|
|
|
|
return
|
|
|
|
}
|
2024-04-01 12:58:36 +02:00
|
|
|
|
2024-04-01 11:26:45 +02:00
|
|
|
// Setzen der Content Security Policy
|
2024-04-01 12:58:59 +02:00
|
|
|
w.Header().Set("Content-Security-Policy", fmt.Sprintf("script-src 'self' 'nonce-%s';", nonce))
|
2024-04-01 11:26:45 +02:00
|
|
|
|
|
|
|
mu.Lock()
|
|
|
|
defer mu.Unlock()
|
|
|
|
|
|
|
|
if time.Since(lastUploadTime) < uploadInterval {
|
|
|
|
http.Error(w, "Nur alle 10 Sekunden erlaubt", http.StatusTooManyRequests)
|
|
|
|
log.Printf("Bildupload zu häufig. Nur alle 10 Sekunden erlaubt.")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.Method == http.MethodPost {
|
|
|
|
file, handler, err := r.FormFile("image")
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Fehler beim Lesen der Datei", http.StatusInternalServerError)
|
|
|
|
log.Printf("Fehler beim Lesen der Datei: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
// Überprüfen Sie den MIME-Typ der Datei
|
|
|
|
buffer := make([]byte, 512) // Genug für die Erkennung des MIME-Typs
|
|
|
|
_, err = file.Read(buffer)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Fehler beim Lesen der Datei", http.StatusInternalServerError)
|
|
|
|
log.Printf("Fehler beim Lesen der Datei für MIME-Typ-Erkennung: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
mimeType := http.DetectContentType(buffer)
|
|
|
|
if !strings.HasPrefix(mimeType, "image/") && !strings.HasPrefix(mimeType, "text/xml") && !strings.HasPrefix(mimeType, "image/svg+xml") {
|
|
|
|
http.Error(w, "Nur Bild-Uploads sind erlaubt", http.StatusBadRequest)
|
|
|
|
log.Printf("Versuch, eine Nicht-Bild-Datei hochzuladen: %v", mimeType)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = file.Seek(0, io.SeekStart)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Fehler beim Zurücksetzen des Dateizeigers", http.StatusInternalServerError)
|
|
|
|
log.Printf("Fehler beim Zurücksetzen des Dateizeigers: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ermitteln, ob der ursprüngliche Dateiname erzwungen werden soll
|
|
|
|
//forceName := r.Header.Get("Force-Name")
|
|
|
|
forceName := r.FormValue("force_name")
|
|
|
|
var filename string
|
|
|
|
if forceName == "true" {
|
|
|
|
filename = handler.Filename
|
|
|
|
} else {
|
|
|
|
// Zeitstempel zum Dateinamen hinzufügen
|
|
|
|
timestamp := time.Now().Format("20060102-150405")
|
|
|
|
filename = fmt.Sprintf("%s-%s", timestamp, handler.Filename)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Datei speichern
|
|
|
|
uploadPath := "./uploads/" + filename
|
|
|
|
f, err := os.Create(uploadPath)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Fehler beim Erstellen der Datei", http.StatusInternalServerError)
|
|
|
|
log.Printf("Fehler beim Erstellen der Datei: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
_, copyErr := io.Copy(f, file)
|
|
|
|
if copyErr != nil {
|
|
|
|
http.Error(w, "Fehler beim Kopieren der Datei", http.StatusInternalServerError)
|
|
|
|
log.Printf("Fehler beim Kopieren der Datei: %v", copyErr)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
lastUploadTime = time.Now() // Setzen Sie die Zeit des letzten Uploads
|
|
|
|
|
|
|
|
tmpl, err := template.ParseFiles("templates/uploadSuccess.html")
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Fehler beim Laden des Templates", http.StatusInternalServerError)
|
|
|
|
log.Printf("Fehler beim Laden des Templates: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
data := struct {
|
|
|
|
Message string
|
|
|
|
Filename string
|
2024-04-01 12:58:59 +02:00
|
|
|
Nonce string
|
2024-04-01 11:26:45 +02:00
|
|
|
}{
|
|
|
|
Message: "Bild erfolgreich hochgeladen.",
|
|
|
|
Filename: filename, // Geändert, um den möglicherweise modifizierten Dateinamen anzuzeigen
|
2024-04-01 12:58:59 +02:00
|
|
|
Nonce: nonce,
|
2024-04-01 11:26:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
err = tmpl.Execute(w, data)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Fehler beim Rendern des Templates", http.StatusInternalServerError)
|
|
|
|
log.Printf("Fehler beim Rendern des Templates: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
tmpl, err := template.ParseFiles("templates/uploadForm.html")
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Fehler beim Laden des Templates", http.StatusInternalServerError)
|
|
|
|
log.Printf("Fehler beim Laden des Templates: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
err = tmpl.Execute(w, nil)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Fehler beim Rendern des Templates", http.StatusInternalServerError)
|
|
|
|
log.Printf("Fehler beim Rendern des Templates: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2024-01-11 22:23:10 +01:00
|
|
|
}
|
|
|
|
|
2024-03-19 21:44:33 +01:00
|
|
|
// Funktion zur Ermittlung des MIME-Types basierend auf der Dateiendung
|
|
|
|
func getMimeType(filePath string) string {
|
|
|
|
switch filepath.Ext(filePath) {
|
|
|
|
case ".jpg", ".jpeg":
|
|
|
|
return "image/jpeg"
|
|
|
|
case ".png":
|
|
|
|
return "image/png"
|
|
|
|
case ".svg":
|
|
|
|
return "image/svg+xml"
|
|
|
|
default:
|
|
|
|
return "application/octet-stream"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-08 15:32:44 +01:00
|
|
|
func imageHandler(w http.ResponseWriter, r *http.Request) {
|
2024-03-08 15:49:19 +01:00
|
|
|
// Setzen der Content Security Policy
|
|
|
|
w.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self'; object-src 'none';")
|
|
|
|
|
2024-03-08 15:37:05 +01:00
|
|
|
// Extrahieren des Bildnamens aus dem URL-Pfad
|
2024-03-09 14:09:03 +01:00
|
|
|
imagePath := r.URL.Path[len("/image/"):]
|
|
|
|
|
|
|
|
// Reinigen des Pfades, um Directory Traversal zu verhindern
|
|
|
|
cleanedPath := path.Clean("/uploads/" + imagePath)
|
|
|
|
|
|
|
|
// Generieren des absoluten Pfads zum uploads-Verzeichnis
|
|
|
|
uploadsDir, err := filepath.Abs("./uploads")
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Interner Serverfehler", http.StatusInternalServerError)
|
|
|
|
log.Printf("Fehler beim Ermitteln des absoluten Pfads des uploads-Verzeichnisses: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generieren des absoluten Pfads zur angeforderten Datei
|
|
|
|
absImagePath, err := filepath.Abs(cleanedPath)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Interner Serverfehler", http.StatusInternalServerError)
|
|
|
|
log.Printf("Fehler beim Ermitteln des absoluten Pfads des Bildes: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sicherstellen, dass das Bild im uploads-Verzeichnis liegt
|
|
|
|
if !strings.HasPrefix(absImagePath, uploadsDir) {
|
|
|
|
http.Error(w, "Zugriff verweigert", http.StatusForbidden)
|
|
|
|
log.Printf("Versuch, auf Datei außerhalb des uploads-Verzeichnisses zuzugreifen: %v", absImagePath)
|
|
|
|
return
|
|
|
|
}
|
2024-03-08 15:37:05 +01:00
|
|
|
|
|
|
|
// Stellen Sie sicher, dass das Bild existiert
|
2024-03-09 14:09:03 +01:00
|
|
|
if _, err := os.Stat(absImagePath); os.IsNotExist(err) {
|
2024-03-08 15:37:05 +01:00
|
|
|
http.Error(w, "Bild nicht gefunden", http.StatusNotFound)
|
|
|
|
log.Printf("Bild nicht gefunden: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Setzen der korrekten MIME-Type basierend auf der Dateiendung
|
2024-03-19 21:44:33 +01:00
|
|
|
mimeType := getMimeType(imagePath)
|
2024-03-08 15:37:05 +01:00
|
|
|
w.Header().Set("Content-Type", mimeType)
|
|
|
|
|
|
|
|
// Ausliefern des Bildes
|
2024-03-09 14:09:03 +01:00
|
|
|
http.ServeFile(w, r, absImagePath)
|
2024-01-11 22:23:10 +01:00
|
|
|
}
|
2024-03-08 15:32:44 +01:00
|
|
|
|
|
|
|
func viewHandler(w http.ResponseWriter, r *http.Request) {
|
2024-03-08 15:49:19 +01:00
|
|
|
// Setzen der Content Security Policy
|
|
|
|
w.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self'; object-src 'none';")
|
|
|
|
|
2024-03-08 15:32:44 +01:00
|
|
|
filePath := r.URL.Path[len("/view/"):]
|
|
|
|
imagePath := "./uploads/" + filePath
|
|
|
|
|
|
|
|
// Überprüfen, ob die Bilddatei existiert
|
|
|
|
if _, err := os.Stat(imagePath); os.IsNotExist(err) {
|
|
|
|
http.Error(w, "Bild nicht gefunden", http.StatusNotFound)
|
|
|
|
log.Printf("Bild nicht gefunden: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verwenden von html/template zur sicheren Ausgabe von HTML
|
|
|
|
tmpl, err := template.ParseFiles("templates/viewImage.html")
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Fehler beim Laden des Templates", http.StatusInternalServerError)
|
|
|
|
log.Printf("Fehler beim Laden des Templates: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
data := struct {
|
|
|
|
Filename string
|
|
|
|
}{
|
|
|
|
Filename: filePath,
|
|
|
|
}
|
|
|
|
|
|
|
|
err = tmpl.Execute(w, data)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Fehler beim Rendern des Templates", http.StatusInternalServerError)
|
|
|
|
log.Printf("Fehler beim Rendern des Templates: %v", err)
|
|
|
|
}
|
|
|
|
}
|