This repository has been archived on 2026-02-07. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
2026-01-07 09:17:03 +01:00

112 lines
2.8 KiB
Go

package main
import (
"encoding/base64"
"errors"
"io"
"log"
"net/http"
"strconv"
)
const (
maxBodySize = 10 * 1024 * 1024 // 10 MB max body size
)
// Handler handles incoming HTTP requests for data synchronization.
type Handler struct {
config *Config
storage *Storage
propagator *Propagator
}
// NewHandler creates a new Handler instance.
func NewHandler(config *Config, storage *Storage, propagator *Propagator) *Handler {
return &Handler{
config: config,
storage: storage,
propagator: propagator,
}
}
// ServeHTTP handles POST /data requests.
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// Parse headers
filename := r.Header.Get("X-Filename")
if filename == "" {
http.Error(w, "Missing X-Filename header", http.StatusBadRequest)
return
}
signatureB64 := r.Header.Get("X-Signature")
if signatureB64 == "" {
http.Error(w, "Missing X-Signature header", http.StatusBadRequest)
return
}
timestampStr := r.Header.Get("X-Timestamp")
if timestampStr == "" {
http.Error(w, "Missing X-Timestamp header", http.StatusBadRequest)
return
}
// Check if filename is configured
allowedKeys := h.config.GetAllowedKeys(filename)
if allowedKeys == nil {
http.Error(w, "Unknown filename", http.StatusNotFound)
return
}
// Decode signature
signature, err := base64.StdEncoding.DecodeString(signatureB64)
if err != nil {
http.Error(w, "Invalid X-Signature: not valid base64", http.StatusBadRequest)
return
}
// Parse timestamp
timestamp, err := strconv.ParseInt(timestampStr, 10, 64)
if err != nil {
http.Error(w, "Invalid X-Timestamp: not a valid integer", http.StatusBadRequest)
return
}
// Read body with size limit
r.Body = http.MaxBytesReader(w, r.Body, maxBodySize)
data, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read request body", http.StatusBadRequest)
return
}
// Verify signature
if !VerifySignature(data, filename, timestamp, signature, allowedKeys) {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}
// Write to storage
if err := h.storage.WriteIfNewer(filename, timestamp, data); err != nil {
if errors.Is(err, ErrTimestampNotNewer) {
http.Error(w, "Timestamp not newer than existing data", http.StatusConflict)
return
}
log.Printf("Storage error: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
log.Printf("Stored data for %q (timestamp=%d, size=%d bytes)", filename, timestamp, len(data))
// Respond success before propagation
w.WriteHeader(http.StatusOK)
// Propagate to peers in background
go h.propagator.PropagateToAllPeers(data, filename, signature, timestamp)
}