Compare commits

..

3 Commits

Author SHA1 Message Date
sudocarlos
1ab44ab897 blossom: implement BUD-05 without optimizations (#45)
* blossom: implement BUD-05 without optimizations

* blossom: add redirect function, handle /media with 307 redirect to /upload

* blossome: remove duplicate /upload handle

* blossom: add content-length header to handleHasBlob()

* blossom: add content-type header to handleHasBlob()

* blossom: remove blossomRedirect() and use http.Redirect() for handleMedia() instead
2025-05-08 10:29:26 -03:00
fiatjaf
3da898cec7 blossom: implement BUD-04
closes https://github.com/fiatjaf/khatru/issues/43
2025-05-06 00:44:45 -03:00
fiatjaf
cfbe484784 docs: show fox. 2025-04-28 15:45:07 -03:00
3 changed files with 112 additions and 3 deletions

View File

@@ -227,6 +227,10 @@ func (bs BlossomServer) handleHasBlob(w http.ResponseWriter, r *http.Request) {
blossomError(w, "file not found", 404)
return
}
w.Header().Set("Content-Length", strconv.Itoa(bd.Size))
w.Header().Set("Accept-Ranges", "bytes")
w.Header().Set("Content-Type", bd.Type)
}
func (bs BlossomServer) handleList(w http.ResponseWriter, r *http.Request) {
@@ -336,8 +340,8 @@ func (bs BlossomServer) handleReport(w http.ResponseWriter, r *http.Request) {
return
}
var evt *nostr.Event
if err := json.Unmarshal(body, evt); err != nil {
var evt nostr.Event
if err := json.Unmarshal(body, &evt); err != nil {
blossomError(w, "can't parse event", 400)
return
}
@@ -353,7 +357,7 @@ func (bs BlossomServer) handleReport(w http.ResponseWriter, r *http.Request) {
}
for _, rr := range bs.ReceiveReport {
if err := rr(r.Context(), evt); err != nil {
if err := rr(r.Context(), &evt); err != nil {
blossomError(w, "failed to receive report: "+err.Error(), 500)
return
}
@@ -361,6 +365,101 @@ 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(), 404)
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 body struct {
URL string `json:"url"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
blossomError(w, "invalid request body: "+err.Error(), 400)
return
}
// download the blob
resp, err := http.Get(body.URL)
if err != nil {
blossomError(w, "failed to download blob: "+err.Error(), 400)
return
}
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
if err != nil {
blossomError(w, "failed to read blob: "+err.Error(), 400)
return
}
// calculate sha256 hash
hash := sha256.Sum256(b)
hhash := hex.EncodeToString(hash[:])
// verify hash matches x tag in auth event
if auth.Tags.FindWithValue("x", hhash) == nil {
blossomError(w, "blob hash does not match any \"x\" tag in authorization event", 403)
return
}
// get content type and extension
var ext string
contentType := resp.Header.Get("Content-Type")
if contentType != "" {
ext = getExtension(contentType)
} else {
// Try to detect from URL extension
if idx := strings.LastIndex(body.URL, "."); idx >= 0 {
ext = body.URL[idx:]
}
}
// run reject hooks
for _, ru := range bs.RejectUpload {
reject, reason, code := ru(r.Context(), auth, len(b), ext)
if reject {
blossomError(w, reason, code)
return
}
}
// create blob descriptor
bd := BlobDescriptor{
URL: bs.ServiceURL + "/" + hhash + ext,
SHA256: hhash,
Size: len(b),
Type: contentType,
Uploaded: nostr.Now(),
}
// store blob metadata
if err := bs.Store.Keep(r.Context(), bd, auth.PubKey); err != nil {
blossomError(w, "failed to save metadata: "+err.Error(), 400)
return
}
// store actual blob
for _, sb := range bs.StoreBlob {
if err := sb(r.Context(), hhash, b); err != nil {
blossomError(w, "failed to save blob: "+err.Error(), 500)
return
}
}
json.NewEncoder(w).Encode(bd)
}
func (bs BlossomServer) handleMedia(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/upload", 307)
return
}
func (bs BlossomServer) handleNegentropy(w http.ResponseWriter, r *http.Request) {

View File

@@ -43,6 +43,14 @@ func New(rl *khatru.Relay, serviceURL string) *BlossomServer {
return
}
}
if r.URL.Path == "/media" {
bs.handleMedia(w, r)
return
}
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)

View File

@@ -4,6 +4,8 @@ layout: home
hero:
name: khatru
text: a framework for making Nostr relays
image:
src: /logo.png
tagline: write your custom relay with code over configuration
actions:
- theme: brand