package federationapi import ( "io" "mime/multipart" "net/http" "net/textproto" "github.com/dariuszjaniak/warp/server/internal/httputil" "github.com/dariuszjaniak/warp/server/internal/mediaapi" ) // The media endpoints of Matrix 1.11: how another server downloads media // uploaded here. They serve local media only — a server asking for a // third server's media asks that server — and answer in multipart/mixed, // a JSON part for metadata (an empty object, for now) and then the // content. func (s *Server) registerMediaRoutes(authed func(string, authedHandler)) { if s.media == nil { return } const v1 = "/_matrix/federation/v1" authed("GET "+v1+"/media/download/{mediaID}", s.handleMediaDownload) authed("GET "+v1+"/media/thumbnail/{mediaID}", s.handleMediaThumbnail) } func (s *Server) handleMediaDownload(w http.ResponseWriter, r *http.Request, _ string, _ []byte) error { wait, err := mediaapi.WaitParam(r) if err != nil { return err } content, err := s.media.Download(r.Context(), s.serverName, r.PathValue("mediaID"), mediaapi.FetchOptions{Wait: wait, LocalOnly: true}) if err != nil { return mediaapi.HTTPError(err) } defer func() { _ = content.Body.Close() }() writeMultipart(w, r, content, mediaapi.ContentDisposition(content.ContentType, content.FileName)) return nil } func (s *Server) handleMediaThumbnail(w http.ResponseWriter, r *http.Request, _ string, _ []byte) error { req, err := mediaapi.ThumbnailParams(r) if err != nil { return err } wait, err := mediaapi.WaitParam(r) if err != nil { return err } content, err := s.media.Thumbnail(r.Context(), s.serverName, r.PathValue("mediaID"), req, mediaapi.FetchOptions{Wait: wait, LocalOnly: true}) if err != nil { return mediaapi.HTTPError(err) } defer func() { _ = content.Body.Close() }() writeMultipart(w, r, content, mediaapi.ContentDisposition(content.ContentType, mediaapi.ThumbnailFileName(content.ContentType))) return nil } // writeMultipart sends content the way the federation media endpoints do. // // The status goes out first, so a failure after it can only cut the // answer short — which the other server sees as a broken multipart body — // and is only logged. func writeMultipart(w http.ResponseWriter, r *http.Request, content mediaapi.Content, disposition string) { mw := multipart.NewWriter(w) w.Header().Set("Content-Type", "multipart/mixed; boundary="+mw.Boundary()) mediaapi.SetSecurityHeaders(w.Header()) w.WriteHeader(http.StatusOK) err := func() error { metadata, err := mw.CreatePart(textproto.MIMEHeader{"Content-Type": {"application/json"}}) if err != nil { return err } if _, err := metadata.Write([]byte("{}")); err != nil { return err } part, err := mw.CreatePart(textproto.MIMEHeader{ "Content-Type": {content.ContentType}, "Content-Disposition": {disposition}, }) if err != nil { return err } if _, err := io.Copy(part, content.Body); err != nil { return err } return mw.Close() }() if err != nil { httputil.LoggerFrom(r.Context()).Debug("sending media over federation failed", "error", err) } }