Archived
112 lines
2.8 KiB
Go
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)
|
|
}
|