Compare commits
No commits in common. "main" and "master" have entirely different histories.
15 changed files with 575 additions and 10 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
uploads/
|
||||||
|
README-SECRET.md
|
9
LICENSE
9
LICENSE
|
@ -1,9 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2024 simono41
|
|
||||||
|
|
||||||
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.
|
|
77
README.md
77
README.md
|
@ -1,2 +1,77 @@
|
||||||
# picture-uploader
|
# Picture Uploader
|
||||||
|
|
||||||
|
This project is a simple web application written in Go for uploading and viewing images.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
To run this application, follow the steps below:
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- [Docker](https://www.docker.com/)
|
||||||
|
- [docker-compose](https://docs.docker.com/compose/)
|
||||||
|
|
||||||
|
### Instructions
|
||||||
|
|
||||||
|
1. Clone the repository:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone <repository-url>
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Navigate to the project directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd <project-directory>
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Create a folder named `uploads`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir uploads
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Set permissions for the `uploads` folder:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod 777 uploads
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Run the application using Docker Compose:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
The application should be accessible at [http://localhost:8080](http://localhost:8080).
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Modify the `docker-compose.yml` file to adjust environment variables, ports, or any other configurations as needed.
|
||||||
|
|
||||||
|
## Upload via Terminal
|
||||||
|
|
||||||
|
curl -X POST -F „image=@/tmp/network-home.svg“ -F „force_name=true“ https://pick.brothertec.eu/upload
|
||||||
|
|
||||||
|
Ersetzen Sie /pfad/zur/datei/bild.jpg durch den tatsächlichen Pfad zu Ihrer Datei und http://localhost:8080/upload durch die URL Ihres Servers und den Endpunkt für den Dateiupload.
|
||||||
|
|
||||||
|
Hier ist eine Erläuterung der Optionen, die in der Curl-Anfrage verwendet werden:
|
||||||
|
|
||||||
|
-X POST: Legt die HTTP-Methode auf POST fest, was in diesem Fall verwendet wird, um die Datei hochzuladen.
|
||||||
|
-F "image=@/pfad/zur/datei/bild.jpg": Teilt Curl mit, dass es sich um ein Formular-Upload handelt (-F), und gibt den Namen des Formularfelds (“image”) sowie den Dateipfad (@/pfad/zur/datei/bild.jpg) an.
|
||||||
|
http://localhost:8080/upload: Die URL des Servers und des Endpunkts, an den die Datei hochgeladen werden soll.
|
||||||
|
|
||||||
|
Führen Sie diese Curl-Anfrage in einem Terminal aus, und die Datei wird an den angegebenen Server hochgeladen.
|
||||||
|
|
||||||
|
## Additional Information
|
||||||
|
|
||||||
|
- This project uses NGINX as a reverse proxy. Ensure that the required networks (`nginx-proxy` and `edge`) are set up externally or adjust the `docker-compose.yml` accordingly.
|
||||||
|
- If you encounter issues with image uploads, verify the permissions on the `uploads` folder.
|
||||||
|
|
||||||
|
### Support and Issues
|
||||||
|
|
||||||
|
For support or to report issues, please [open an issue](<repository-url>/issues).
|
||||||
|
|
||||||
|
### License
|
||||||
|
|
||||||
|
This project is licensed under the [MIT License](LICENSE).
|
||||||
|
|
43
docker-compose.yml
Executable file
43
docker-compose.yml
Executable file
|
@ -0,0 +1,43 @@
|
||||||
|
version: "3.9"
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
# Go application service
|
||||||
|
go-app:
|
||||||
|
build:
|
||||||
|
context: go/.
|
||||||
|
args:
|
||||||
|
- GO111MODULE=on
|
||||||
|
#ports:
|
||||||
|
# - "8080:8080"
|
||||||
|
environment:
|
||||||
|
- VIRTUAL_HOST=pick.brothertec.eu
|
||||||
|
- VIRTUAL_PORT=8080
|
||||||
|
- LETSENCRYPT_HOST=pick.brothertec.eu
|
||||||
|
- LETSENCRYPT_EMAIL=admin@brothertec.eu
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- ./uploads:/uploads
|
||||||
|
- ./templates:/templates
|
||||||
|
- ./static:/static
|
||||||
|
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
labels:
|
||||||
|
- flame.type=application
|
||||||
|
- flame.name=Picture Upload
|
||||||
|
- flame.url=https://pick.brothertec.eu
|
||||||
|
- flame.icon=image
|
||||||
|
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
proxy:
|
||||||
|
edge-tier:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
proxy:
|
||||||
|
name: nginx-proxy
|
||||||
|
external: true
|
||||||
|
edge-tier:
|
||||||
|
name: edge
|
||||||
|
external: true
|
28
go/Dockerfile
Executable file
28
go/Dockerfile
Executable file
|
@ -0,0 +1,28 @@
|
||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
|
# Build the application from source
|
||||||
|
FROM golang:1.20.10 AS build-stage
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY * ./
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build -o /main
|
||||||
|
|
||||||
|
# Run the tests in the container
|
||||||
|
FROM build-stage AS run-test-stage
|
||||||
|
RUN go test -v ./...
|
||||||
|
|
||||||
|
# Deploy the application binary into a lean image
|
||||||
|
FROM gcr.io/distroless/base-debian11 AS build-release-stage
|
||||||
|
|
||||||
|
WORKDIR /
|
||||||
|
|
||||||
|
COPY --from=build-stage /main /main
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
USER nonroot:nonroot
|
||||||
|
|
||||||
|
ENTRYPOINT ["/main"]
|
22
go/Dockerfile.old
Executable file
22
go/Dockerfile.old
Executable file
|
@ -0,0 +1,22 @@
|
||||||
|
# Use an official Golang runtime as a parent image
|
||||||
|
FROM golang:1.21.4
|
||||||
|
|
||||||
|
# Set the working directory in the container
|
||||||
|
WORKDIR /go/src/app
|
||||||
|
|
||||||
|
# Copy the local package files to the container's workspace
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Download and install any required third-party dependencies into the container.
|
||||||
|
#RUN go get -u github.com/gorilla/mux
|
||||||
|
RUN go get -u github.com/go-sql-driver/mysql
|
||||||
|
RUN go get -u github.com/sirupsen/logrus
|
||||||
|
|
||||||
|
# Build the Go application
|
||||||
|
RUN go build -o main .
|
||||||
|
|
||||||
|
# Expose port 8080 to the outside world
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
# Command to run the application with environment variables
|
||||||
|
CMD ["./main"]
|
3
go/go.mod
Normal file
3
go/go.mod
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module picture-uploader
|
||||||
|
|
||||||
|
go 1.20
|
324
go/main.go
Normal file
324
go/main.go
Normal file
|
@ -0,0 +1,324 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
lastUploadTime time.Time
|
||||||
|
mu sync.Mutex
|
||||||
|
uploadInterval = 10 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", homeHandler)
|
||||||
|
http.HandleFunc("/upload", uploadHandler)
|
||||||
|
http.HandleFunc("/image/", imageHandler)
|
||||||
|
http.HandleFunc("/view/", viewHandler)
|
||||||
|
// Statischen Dateipfad setzen
|
||||||
|
fs := http.FileServer(http.Dir("static"))
|
||||||
|
http.Handle("/static/", http.StripPrefix("/static/", fs))
|
||||||
|
|
||||||
|
fmt.Println("Server listening on :8080")
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func homeHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Setzen der Content Security Policy
|
||||||
|
w.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self'; object-src 'none';")
|
||||||
|
|
||||||
|
// Verwenden von html/template zur sicheren Ausgabe von HTML
|
||||||
|
tmpl, err := template.ParseFiles("templates/homeTemplate.html")
|
||||||
|
if err != nil {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateNonce() (string, error) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func uploadHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
nonce, err := generateNonce()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Serverfehler", http.StatusInternalServerError)
|
||||||
|
log.Printf("Fehler beim Generieren des Nonce: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Security-Policy", fmt.Sprintf("script-src 'self' 'nonce-%s';", nonce))
|
||||||
|
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()
|
||||||
|
|
||||||
|
buffer := make([]byte, 512)
|
||||||
|
_, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
forceUpload := r.FormValue("force_upload")
|
||||||
|
if forceUpload != "true" {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
forceName := r.FormValue("force_name")
|
||||||
|
|
||||||
|
var filename string
|
||||||
|
if forceName == "true" {
|
||||||
|
filename = handler.Filename
|
||||||
|
} else {
|
||||||
|
fileExtension := filepath.Ext(handler.Filename)
|
||||||
|
timestamp := time.Now().Format("20060102-150405")
|
||||||
|
filename = fmt.Sprintf("%s%s", timestamp, fileExtension)
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadPath := "./uploads/" + filename
|
||||||
|
|
||||||
|
if _, err := os.Stat(uploadPath); err == nil && forceUpload != "true" {
|
||||||
|
http.Error(w, "Datei existiert bereits. Überschreiben nicht erlaubt.", http.StatusConflict)
|
||||||
|
log.Printf("Versuch, bestehende Datei ohne force_upload zu überschreiben: %v", filename)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
responseType := r.URL.Query().Get("responseType")
|
||||||
|
if responseType == "json" {
|
||||||
|
jsonResponse(w, nonce, filename)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
renderTemplate(w, nonce, filename)
|
||||||
|
} 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func jsonResponse(w http.ResponseWriter, nonce string, filename string) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
response := struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
Nonce string `json:"nonce"`
|
||||||
|
}{
|
||||||
|
Message: "Bild erfolgreich hochgeladen.",
|
||||||
|
Filename: filename,
|
||||||
|
Nonce: nonce,
|
||||||
|
}
|
||||||
|
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderTemplate(w http.ResponseWriter, nonce string, filename string) {
|
||||||
|
// Implementierung des Template-Renderings
|
||||||
|
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
|
||||||
|
Nonce string
|
||||||
|
}{
|
||||||
|
Message: "Bild erfolgreich hochgeladen.",
|
||||||
|
Filename: filename, // Geändert, um den möglicherweise modifizierten Dateinamen anzuzeigen
|
||||||
|
Nonce: nonce,
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func imageHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Setzen der Content Security Policy
|
||||||
|
w.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self'; object-src 'none';")
|
||||||
|
|
||||||
|
// Überprüfen, ob der Pfad mit einem "/" endet (was auf ein Verzeichnis hinweisen könnte)
|
||||||
|
// und ob es eine Dateiendung gibt (was darauf hindeutet, dass es eine spezifische Datei ist).
|
||||||
|
if strings.HasSuffix(r.URL.Path, "/") && !strings.Contains(r.URL.Path, ".") {
|
||||||
|
http.Error(w, "Zugriff verweigert", http.StatusForbidden)
|
||||||
|
log.Printf("Versuch, auf Ordner außerhalb des uploads-Verzeichnisses zuzugreifen")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extrahieren des Bildnamens aus dem URL-Pfad
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stellen Sie sicher, dass das Bild existiert
|
||||||
|
if _, err := os.Stat(absImagePath); os.IsNotExist(err) {
|
||||||
|
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
|
||||||
|
mimeType := getMimeType(imagePath)
|
||||||
|
w.Header().Set("Content-Type", mimeType)
|
||||||
|
|
||||||
|
// Ausliefern des Bildes
|
||||||
|
http.ServeFile(w, r, absImagePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func viewHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Setzen der Content Security Policy
|
||||||
|
w.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self'; object-src 'none';")
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
BIN
shortcuts/Bild hochladen (ohne json).shortcut
Normal file
BIN
shortcuts/Bild hochladen (ohne json).shortcut
Normal file
Binary file not shown.
BIN
shortcuts/Bild hochladen.shortcut
Normal file
BIN
shortcuts/Bild hochladen.shortcut
Normal file
Binary file not shown.
15
static/js/script.js
Normal file
15
static/js/script.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
document.addEventListener('DOMContentLoaded', (event) => {
|
||||||
|
document.getElementById('copyButton').addEventListener('click', kopiereURL);
|
||||||
|
});
|
||||||
|
|
||||||
|
function kopiereURL() {
|
||||||
|
var copyText = document.getElementById("imageURL");
|
||||||
|
copyText.select();
|
||||||
|
copyText.setSelectionRange(0, 99999); // Für mobile Geräte
|
||||||
|
|
||||||
|
navigator.clipboard.writeText(copyText.value).then(function() {
|
||||||
|
console.log('Kopieren in die Zwischenablage erfolgreich.');
|
||||||
|
}, function(err) {
|
||||||
|
console.error('Fehler beim Kopieren in die Zwischenablage: ', err);
|
||||||
|
});
|
||||||
|
}
|
11
templates/homeTemplate.html
Normal file
11
templates/homeTemplate.html
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>{{.Title}}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>{{.Title}}</h1>
|
||||||
|
<p>Besuchen Sie /upload, um Bilder hochzuladen.</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
16
templates/uploadForm.html
Normal file
16
templates/uploadForm.html
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Bild hochladen</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<form action="/upload" method="post" enctype="multipart/form-data">
|
||||||
|
<input type="file" name="image" required>
|
||||||
|
<br>
|
||||||
|
<input type="checkbox" name="force_name" value="true">
|
||||||
|
<label for="force_name">Originalnamen beibehalten (Force Name)</label>
|
||||||
|
<br>
|
||||||
|
<input type="submit" value="Hochladen">
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
23
templates/uploadSuccess.html
Normal file
23
templates/uploadSuccess.html
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Upload Erfolgreich</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>{{.Message}}</p>
|
||||||
|
<!-- Anzeigen des Links zum Bild, um es zu betrachten -->
|
||||||
|
<p><a href="/view/{{.Filename}}" target="_blank">Ihr Bild anzeigen</a></p>
|
||||||
|
<!-- Eingabefeld mit der URL des Bildes -->
|
||||||
|
<input type="text" value="https://pick.brothertec.eu/view/{{.Filename}}" id="imageURL" readonly>
|
||||||
|
<!-- Button, um die URL zu kopieren -->
|
||||||
|
<button id="copyButton">URL kopieren</button>
|
||||||
|
<p><a href="/upload">Zurück zum Upload</a></p>
|
||||||
|
|
||||||
|
<script src="/static/js/script.js" nonce="{{.Nonce}}"></script>
|
||||||
|
|
||||||
|
<script nonce="{{.Nonce}}">
|
||||||
|
// Rufen Sie hier Ihre Funktion auf
|
||||||
|
kopiereURL();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
12
templates/viewImage.html
Normal file
12
templates/viewImage.html
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Bild anzeigen</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Bildanzeige</h1>
|
||||||
|
<!-- Verwenden des neuen Handlers für die Bild-URL -->
|
||||||
|
<img src="/image/{{.Filename}}" alt="Hochgeladenes Bild">
|
||||||
|
<p><a href="/upload">Zurück zum Upload</a></p>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in a new issue