Skip to content
This repository was archived by the owner on Jan 24, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 38 additions & 18 deletions blossom/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,17 @@ func (bs BlossomServer) handleUpload(w http.ResponseWriter, r *http.Request) {

hash := sha256.Sum256(b)
hhash := hex.EncodeToString(hash[:])
mimeType := mime.TypeByExtension(ext)
if mimeType == "" {
mimeType = "application/octet-stream"
}

// keep track of the blob descriptor
bd := BlobDescriptor{
URL: bs.ServiceURL + "/" + hhash + ext,
SHA256: hhash,
Size: len(b),
Type: mime.TypeByExtension(ext),
Type: mimeType,
Uploaded: nostr.Now(),
}
if err := bs.Store.Keep(r.Context(), bd, auth.PubKey); err != nil {
Expand All @@ -141,7 +145,7 @@ func (bs BlossomServer) handleUpload(w http.ResponseWriter, r *http.Request) {

// save actual blob
for _, sb := range bs.StoreBlob {
if err := sb(r.Context(), hhash, b); err != nil {
if err := sb(r.Context(), hhash, ext, b); err != nil {
blossomError(w, "failed to save: "+err.Error(), 500)
return
}
Expand Down Expand Up @@ -182,19 +186,25 @@ func (bs BlossomServer) handleGetBlob(w http.ResponseWriter, r *http.Request) {
}
}

var ext string
bd, err := bs.Store.Get(r.Context(), hhash)
if err != nil {
// can't find the BlobDescriptor, try to get the extension from the URL
if len(spl) == 2 {
ext = spl[1]
}
} else if bd != nil {
ext = getExtension(bd.Type)
}

for _, rg := range bs.RejectGet {
reject, reason, code := rg(r.Context(), auth, hhash)
reject, reason, code := rg(r.Context(), auth, hhash, ext)
if reject {
blossomError(w, reason, code)
return
}
}

var ext string
if len(spl) == 2 {
ext = spl[1]
}

if len(bs.RedirectGet) > 0 {
for _, redirect := range bs.RedirectGet {
redirectURL, code, err := redirect(r.Context(), hhash, ext)
Expand All @@ -215,7 +225,7 @@ func (bs BlossomServer) handleGetBlob(w http.ResponseWriter, r *http.Request) {
}

for _, lb := range bs.LoadBlob {
reader, _ := lb(r.Context(), hhash)
reader, _ := lb(r.Context(), hhash, ext)
if reader != nil {
// use unix epoch as the time if we can't find the descriptor
// as described in the http.ServeContent documentation
Expand Down Expand Up @@ -335,9 +345,20 @@ func (bs BlossomServer) handleDelete(w http.ResponseWriter, r *http.Request) {
return
}

var ext string
bd, err := bs.Store.Get(r.Context(), hhash)
if err != nil {
// can't find the BlobDescriptor, try to get the extension from the URL
if len(spl) == 2 {
ext = spl[1]
}
} else if bd != nil {
ext = getExtension(bd.Type)
}

// should we accept this delete?
for _, rd := range bs.RejectDelete {
reject, reason, code := rd(r.Context(), auth, hhash)
reject, reason, code := rd(r.Context(), auth, hhash, ext)
if reject {
blossomError(w, reason, code)
return
Expand All @@ -353,7 +374,7 @@ func (bs BlossomServer) handleDelete(w http.ResponseWriter, r *http.Request) {
// we will actually only delete the file if no one else owns it
if bd, err := bs.Store.Get(r.Context(), hhash); err == nil && bd == nil {
for _, del := range bs.DeleteBlob {
if err := del(r.Context(), hhash); err != nil {
if err := del(r.Context(), hhash, ext); err != nil {
blossomError(w, "failed to delete blob: "+err.Error(), 500)
return
}
Expand Down Expand Up @@ -442,13 +463,12 @@ func (bs BlossomServer) handleMirror(w http.ResponseWriter, r *http.Request) {
// get content type and extension
var ext string
contentType := resp.Header.Get("Content-Type")
if contentType != "" {
if contentType != "" { // First try to get the extension from the Content-Type header
ext = getExtension(contentType)
} else {
// Try to detect from URL extension
if idx := strings.LastIndex(body.URL, "."); idx >= 0 {
ext = body.URL[idx:]
}
} else if ft, _ := magic.Lookup(b); ft != nil { // Else try to infer extension from the file content
ext = "." + ft.Extension
} else if idx := strings.LastIndex(body.URL, "."); idx >= 0 { // Else, try to get the extension from the URL
ext = body.URL[idx:]
}

// run reject hooks
Expand Down Expand Up @@ -477,7 +497,7 @@ func (bs BlossomServer) handleMirror(w http.ResponseWriter, r *http.Request) {

// store actual blob
for _, sb := range bs.StoreBlob {
if err := sb(r.Context(), hhash, b); err != nil {
if err := sb(r.Context(), hhash, ext, b); err != nil {
blossomError(w, "failed to save blob: "+err.Error(), 500)
return
}
Expand Down
12 changes: 6 additions & 6 deletions blossom/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@ type BlossomServer struct {
ServiceURL string
Store BlobIndex

StoreBlob []func(ctx context.Context, sha256 string, body []byte) error
LoadBlob []func(ctx context.Context, sha256 string) (io.ReadSeeker, error)
DeleteBlob []func(ctx context.Context, sha256 string) error
StoreBlob []func(ctx context.Context, sha256 string, ext string, body []byte) error
LoadBlob []func(ctx context.Context, sha256 string, ext string) (io.ReadSeeker, error)
DeleteBlob []func(ctx context.Context, sha256 string, ext string) error
ReceiveReport []func(ctx context.Context, reportEvt *nostr.Event) error
RedirectGet []func(ctx context.Context, sha256 string, fileExtension string) (url string, code int, err error)
RedirectGet []func(ctx context.Context, sha256 string, ext string) (url string, code int, err error)

RejectUpload []func(ctx context.Context, auth *nostr.Event, size int, ext string) (bool, string, int)
RejectGet []func(ctx context.Context, auth *nostr.Event, sha256 string) (bool, string, int)
RejectGet []func(ctx context.Context, auth *nostr.Event, sha256 string, ext string) (bool, string, int)
RejectList []func(ctx context.Context, auth *nostr.Event, pubkey string) (bool, string, int)
RejectDelete []func(ctx context.Context, auth *nostr.Event, sha256 string) (bool, string, int)
RejectDelete []func(ctx context.Context, auth *nostr.Event, sha256 string, ext string) (bool, string, int)
}

// ServerOption represents a functional option for configuring a BlossomServer
Expand Down
6 changes: 3 additions & 3 deletions docs/core/blossom.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ func main() {
bl.Store = blossom.EventStoreBlobIndexWrapper{Store: blobdb, ServiceURL: bl.ServiceURL}

// implement the required storage functions
bl.StoreBlob = append(bl.StoreBlob, func(ctx context.Context, sha256 string, body []byte) error {
bl.StoreBlob = append(bl.StoreBlob, func(ctx context.Context, sha256 string, ext string, body []byte) error {
// store the blob data somewhere
return nil
})
bl.LoadBlob = append(bl.LoadBlob, func(ctx context.Context, sha256 string) (io.ReadSeeker, error) {
bl.LoadBlob = append(bl.LoadBlob, func(ctx context.Context, sha256 string, ext string) (io.ReadSeeker, error) {
// load and return the blob data
return nil, nil
})
bl.DeleteBlob = append(bl.DeleteBlob, func(ctx context.Context, sha256 string) error {
bl.DeleteBlob = append(bl.DeleteBlob, func(ctx context.Context, sha256 string, ext string) error {
// delete the blob data
return nil
})
Expand Down
6 changes: 3 additions & 3 deletions examples/blossom/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ func main() {
}
bl := blossom.New(relay, "http://localhost:3334")
bl.Store = blossom.EventStoreBlobIndexWrapper{Store: bdb, ServiceURL: bl.ServiceURL}
bl.StoreBlob = append(bl.StoreBlob, func(ctx context.Context, sha256 string, body []byte) error {
fmt.Println("storing", sha256, len(body))
bl.StoreBlob = append(bl.StoreBlob, func(ctx context.Context, sha256 string, ext string, body []byte) error {
fmt.Println("storing", sha256, ext, len(body))
return nil
})
bl.LoadBlob = append(bl.LoadBlob, func(ctx context.Context, sha256 string) (io.ReadSeeker, error) {
bl.LoadBlob = append(bl.LoadBlob, func(ctx context.Context, sha256 string, ext string) (io.ReadSeeker, error) {
fmt.Println("loading", sha256)
blob := strings.NewReader("aaaaa")
return blob, nil
Expand Down