From 6de0c7b0360325dd7c19ef76405927da4009f539 Mon Sep 17 00:00:00 2001
From: Pascal Bleser
Date: Mon, 17 Nov 2025 12:35:00 +0100
Subject: [PATCH] groupware: fix blob uploading metadata and add 'POST /blobs'
route
---
pkg/jmap/jmap_api_blob.go | 5 ++-
pkg/jmap/jmap_http.go | 36 ++++++++++++++++---
.../pkg/groupware/groupware_api_blob.go | 2 +-
.../pkg/groupware/groupware_route.go | 1 +
4 files changed, 36 insertions(+), 8 deletions(-)
diff --git a/pkg/jmap/jmap_api_blob.go b/pkg/jmap/jmap_api_blob.go
index f3561992d..b83129fba 100644
--- a/pkg/jmap/jmap_api_blob.go
+++ b/pkg/jmap/jmap_api_blob.go
@@ -40,9 +40,8 @@ func (j *Client) GetBlobMetadata(accountId string, session *Session, ctx context
type UploadedBlob struct {
BlobId string `json:"blobId"`
- Size int `json:"size"`
- Type string `json:"type"`
- Sha512 string `json:"sha:512"`
+ Size int `json:"size,omitzero"`
+ Type string `json:"type,omitempty"`
}
func (j *Client) UploadBlobStream(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, contentType string, body io.Reader) (UploadedBlob, Language, Error) {
diff --git a/pkg/jmap/jmap_http.go b/pkg/jmap/jmap_http.go
index 9c5bb08ae..630725bcb 100644
--- a/pkg/jmap/jmap_http.go
+++ b/pkg/jmap/jmap_http.go
@@ -219,9 +219,9 @@ func (h *HttpJmapClient) Command(ctx context.Context, logger *log.Logger, sessio
}
if logger.Trace().Enabled() {
- requestBytes, err := httputil.DumpResponse(res, true)
+ responseBytes, err := httputil.DumpResponse(res, true)
if err == nil {
- logger.Trace().Str(logEndpoint, endpoint).Str("proto", "jmap").Str("type", "response").Msg(string(requestBytes))
+ logger.Trace().Str(logEndpoint, endpoint).Str("proto", "jmap").Str("type", "response").Msg(string(responseBytes))
}
}
@@ -261,10 +261,17 @@ func (h *HttpJmapClient) UploadBinary(ctx context.Context, logger *log.Logger, s
}
req.Header.Add("Content-Type", contentType)
req.Header.Add("User-Agent", h.userAgent)
- h.auth(session.Username, logger, req)
if acceptLanguage != "" {
req.Header.Add("Accept-Language", acceptLanguage)
}
+ if logger.Trace().Enabled() {
+ requestBytes, err := httputil.DumpRequestOut(req, false)
+ if err == nil {
+ logger.Trace().Str(logEndpoint, endpoint).Str("proto", "jmap").Str("type", "request").Msg(string(requestBytes))
+ }
+ }
+
+ h.auth(session.Username, logger, req)
res, err := h.client.Do(req)
if err != nil {
@@ -272,6 +279,13 @@ func (h *HttpJmapClient) UploadBinary(ctx context.Context, logger *log.Logger, s
logger.Error().Err(err).Msgf("failed to perform POST %v", uploadUrl)
return UploadedBlob{}, "", SimpleError{code: JmapErrorSendingRequest, err: err}
}
+ if logger.Trace().Enabled() {
+ responseBytes, err := httputil.DumpResponse(res, true)
+ if err == nil {
+ logger.Trace().Str(logEndpoint, endpoint).Str("proto", "jmap").Str("type", "response").Msg(string(responseBytes))
+ }
+ }
+
language := Language(res.Header.Get("Content-Language"))
if res.StatusCode < 200 || res.StatusCode > 299 {
h.listener.OnFailedRequestWithStatus(endpoint, res.StatusCode)
@@ -295,6 +309,8 @@ func (h *HttpJmapClient) UploadBinary(ctx context.Context, logger *log.Logger, s
return UploadedBlob{}, language, SimpleError{code: JmapErrorServerResponse, err: err}
}
+ logger.Trace()
+
var result UploadedBlob
err = json.Unmarshal(responseBody, &result)
if err != nil {
@@ -315,10 +331,16 @@ func (h *HttpJmapClient) DownloadBinary(ctx context.Context, logger *log.Logger,
return nil, "", SimpleError{code: JmapErrorCreatingRequest, err: err}
}
req.Header.Add("User-Agent", h.userAgent)
- h.auth(session.Username, logger, req)
if acceptLanguage != "" {
req.Header.Add("Accept-Language", acceptLanguage)
}
+ if logger.Trace().Enabled() {
+ requestBytes, err := httputil.DumpRequestOut(req, true)
+ if err == nil {
+ logger.Trace().Str(logEndpoint, endpoint).Str("proto", "jmap").Str("type", "request").Msg(string(requestBytes))
+ }
+ }
+ h.auth(session.Username, logger, req)
res, err := h.client.Do(req)
if err != nil {
@@ -326,6 +348,12 @@ func (h *HttpJmapClient) DownloadBinary(ctx context.Context, logger *log.Logger,
logger.Error().Err(err).Msgf("failed to perform GET %v", downloadUrl)
return nil, "", SimpleError{code: JmapErrorSendingRequest, err: err}
}
+ if logger.Trace().Enabled() {
+ responseBytes, err := httputil.DumpResponse(res, false)
+ if err == nil {
+ logger.Trace().Str(logEndpoint, endpoint).Str("proto", "jmap").Str("type", "response").Msg(string(responseBytes))
+ }
+ }
language := Language(res.Header.Get("Content-Language"))
if res.StatusCode == http.StatusNotFound {
return nil, language, nil
diff --git a/services/groupware/pkg/groupware/groupware_api_blob.go b/services/groupware/pkg/groupware/groupware_api_blob.go
index 4cf220fe4..1e38f57ec 100644
--- a/services/groupware/pkg/groupware/groupware_api_blob.go
+++ b/services/groupware/pkg/groupware/groupware_api_blob.go
@@ -59,7 +59,7 @@ func (g *Groupware) UploadBlob(w http.ResponseWriter, r *http.Request) {
}
logger := log.From(req.logger.With().Str(logAccountId, accountId))
- resp, lang, jerr := g.jmap.UploadBlobStream(accountId, req.session, req.ctx, logger, contentType, req.language(), body)
+ resp, lang, jerr := g.jmap.UploadBlobStream(accountId, req.session, req.ctx, logger, req.language(), contentType, body)
if jerr != nil {
return req.errorResponseFromJmap(jerr)
}
diff --git a/services/groupware/pkg/groupware/groupware_route.go b/services/groupware/pkg/groupware/groupware_route.go
index 97bc25687..d31a63f68 100644
--- a/services/groupware/pkg/groupware/groupware_route.go
+++ b/services/groupware/pkg/groupware/groupware_route.go
@@ -134,6 +134,7 @@ func (g *Groupware) Route(r chi.Router) {
r.Route("/blobs", func(r chi.Router) {
r.Get("/{blobid}", g.GetBlobMeta)
r.Get("/{blobid}/{blobname}", g.DownloadBlob) // ?type=
+ r.Post("/", g.UploadBlob)
})
r.Route("/ical", func(r chi.Router) {
r.Get("/{blobid}", g.ParseIcalBlob)