From 0d99789a54e289ba6d5a96f4091a33d0006641d9 Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Tue, 6 May 2025 00:15:45 -0300 Subject: [PATCH] khatru/blossom: implement bud-04 mirror. --- khatru/blossom/handlers.go | 91 ++++++++++++++++++++++++++++++++++++++ khatru/blossom/server.go | 5 +++ 2 files changed, 96 insertions(+) diff --git a/khatru/blossom/handlers.go b/khatru/blossom/handlers.go index 8bbf3ec..cdfe937 100644 --- a/khatru/blossom/handlers.go +++ b/khatru/blossom/handlers.go @@ -7,6 +7,7 @@ import ( "io" "mime" "net/http" + "path/filepath" "strconv" "strings" "time" @@ -15,6 +16,10 @@ import ( "github.com/liamg/magic" ) +type mirrorRequest struct { + URL string `json:"url"` +} + func (bs BlossomServer) handleUploadCheck(w http.ResponseWriter, r *http.Request) { auth, err := readAuthorization(r) if err != nil { @@ -355,6 +360,92 @@ func (bs BlossomServer) handleReport(w http.ResponseWriter, r *http.Request) { } func (bs BlossomServer) handleMirror(w http.ResponseWriter, r *http.Request) { + auth, err := readAuthorization(r) + if err != nil { + blossomError(w, "invalid \"Authorization\": "+err.Error(), 400) + return + } + if auth == nil { + blossomError(w, "missing \"Authorization\" header", 401) + return + } + if auth.Tags.FindWithValue("t", "upload") == nil { + blossomError(w, "invalid \"Authorization\" event \"t\" tag", 403) + return + } + + var req mirrorRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + blossomError(w, "invalid request body: "+err.Error(), 400) + return + } + + // download the blob + resp, err := http.Get(req.URL) + if err != nil { + blossomError(w, "failed to download from url: "+err.Error(), 503) + return + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + blossomError(w, "failed to read response body: "+err.Error(), 503) + return + } + + // calculate sha256 + hash := sha256.Sum256(body) + hhash := hex.EncodeToString(hash[:]) + + // verify hash against x tag + if auth.Tags.FindWithValue("x", hhash) == nil { + blossomError(w, "blob hash does not match any \"x\" tag in authorization event", 403) + return + } + + // determine content type and extension + var ext string + contentType := resp.Header.Get("Content-Type") + if contentType != "" { + ext = getExtension(contentType) + } else { + // try to detect from url + ext = filepath.Ext(req.URL) + } + + // run reject hook if defined + if bs.RejectUpload != nil { + reject, reason, code := bs.RejectUpload(r.Context(), auth, len(body), ext) + if reject { + blossomError(w, reason, code) + return + } + } + + // keep track of the blob descriptor + bd := BlobDescriptor{ + URL: bs.ServiceURL + "/" + hhash + ext, + SHA256: hhash, + Size: len(body), + Type: contentType, + Uploaded: nostr.Now(), + } + if err := bs.Store.Keep(r.Context(), bd, auth.PubKey); err != nil { + blossomError(w, "failed to save event: "+err.Error(), 400) + return + } + + // save actual blob + if bs.StoreBlob != nil { + if err := bs.StoreBlob(r.Context(), hhash, body); err != nil { + blossomError(w, "failed to save: "+err.Error(), 500) + return + } + } + + // return response + json.NewEncoder(w).Encode(bd) } func (bs BlossomServer) handleNegentropy(w http.ResponseWriter, r *http.Request) { diff --git a/khatru/blossom/server.go b/khatru/blossom/server.go index f2b9f0e..af94cd1 100644 --- a/khatru/blossom/server.go +++ b/khatru/blossom/server.go @@ -44,6 +44,11 @@ func New(rl *khatru.Relay, serviceURL string) *BlossomServer { } } + if r.URL.Path == "/mirror" && r.Method == "PUT" { + bs.handleMirror(w, r) + return + } + if strings.HasPrefix(r.URL.Path, "/list/") && r.Method == "GET" { bs.handleList(w, r) return