Files
opencloud/services/groupware/pkg/groupware/api_blob.go
Pascal Bleser a115679f3d groupware: refactoring using function templates
* adds creating addressbooks, calendars, mailboxes

 * adds deleting mailbox, event, identity

 * adds modifying an email

 * introduce template functions for the Groupware API in templates.go,
   and use those in route function implementations whenever possible

 * add capability checking for mail, quota, blobs

 * adds Changes interface

 * adds JmapResponse interface
2026-06-16 16:51:37 +02:00

116 lines
3.1 KiB
Go

package groupware
import (
"io"
"net/http"
"strconv"
"github.com/opencloud-eu/opencloud/pkg/jmap"
"github.com/opencloud-eu/opencloud/pkg/log"
)
const (
DefaultBlobDownloadType = "application/octet-stream"
)
func (g *Groupware) GetBlobMeta(w http.ResponseWriter, r *http.Request) {
get(Blob, w, r, g, g.jmap.GetBlobMetadata)
}
func (g *Groupware) UploadBlob(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
contentType := r.Header.Get("Content-Type")
body := r.Body
if body != nil {
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
req.logger.Error().Err(err).Msg("failed to close response body")
}
}(body)
}
accountId, err := req.GetAccountIdForBlob()
if err != nil {
return req.error(accountId, err)
}
logger := log.From(req.logger.With().Str(logAccountId, accountId))
ctx := req.ctx.WithLogger(logger)
resp, lang, jerr := g.jmap.UploadBlobStream(accountId, contentType, body, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, req.session.State, lang)
}
return req.respondWithoutStatus(accountId, resp)
})
}
// Download a BLOB by its identifier.
func (g *Groupware) DownloadBlob(w http.ResponseWriter, r *http.Request) {
g.stream(w, r, func(req Request, w http.ResponseWriter) *Error {
blobId, err := req.PathParam(UriParamBlobId) // the unique identifier of the blob to download
if err != nil {
return err
}
name, err := req.PathParam(UriParamBlobName) // the filename of the blob to download, which is then used in the response and may be arbitrary if unknown
if err != nil {
return err
}
typ, _ := req.getStringParam(QueryParamBlobType, "") // optionally, the Content-Type of the blob, which is then used in the response
accountId, gwerr := req.GetAccountIdForBlob()
if gwerr != nil {
return gwerr
}
logger := log.From(req.logger.With().Str(logAccountId, accountId).Str(UriParamBlobId, blobId))
ctx := req.ctx.WithLogger(logger)
return req.serveBlob(blobId, name, typ, ctx, accountId, w)
})
}
func (r *Request) serveBlob(blobId string, name string, typ string, ctx jmap.Context, accountId string, w http.ResponseWriter) *Error {
if typ == "" {
typ = DefaultBlobDownloadType
}
blob, lang, jerr := r.g.jmap.DownloadBlobStream(accountId, blobId, name, typ, ctx)
if blob != nil && blob.Body != nil {
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
ctx.Logger.Error().Err(err).Msg("failed to close response body")
}
}(blob.Body)
}
if jerr != nil {
return r.apiErrorFromJmap(jerr)
}
if blob == nil {
w.WriteHeader(http.StatusNotFound)
return nil
}
if blob.Type != "" {
w.Header().Add("Content-Type", blob.Type)
}
if blob.CacheControl != "" {
w.Header().Add("Cache-Control", blob.CacheControl)
}
if blob.ContentDisposition != "" {
w.Header().Add("Content-Disposition", blob.ContentDisposition)
}
if blob.Size >= 0 {
w.Header().Add("Content-Size", strconv.Itoa(blob.Size))
}
if lang != "" {
w.Header().Add("Content-Language", string(lang))
}
_, err := io.Copy(w, blob.Body)
if err != nil {
return r.observedParameterError(ErrorStreamingResponse)
}
return nil
}