diff --git a/server/resource.go b/server/resource.go index da69e132..f9d6a49c 100644 --- a/server/resource.go +++ b/server/resource.go @@ -314,6 +314,7 @@ func (s *Server) registerResourceRoutes(g *echo.Group) { } func (s *Server) registerResourcePublicRoutes(g *echo.Group) { + // (DEPRECATED) use /r/:resourceId/:publicId/:filename instead. g.GET("/r/:resourceId/:publicId", func(c echo.Context) error { ctx := c.Request().Context() resourceID, err := strconv.Atoi(c.Param("resourceId")) @@ -358,6 +359,56 @@ func (s *Server) registerResourcePublicRoutes(g *echo.Group) { } return c.Stream(http.StatusOK, resourceType, bytes.NewReader(blob)) }) + + g.GET("/r/:resourceId/:publicId/:filename", func(c echo.Context) error { + ctx := c.Request().Context() + resourceID, err := strconv.Atoi(c.Param("resourceId")) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("resourceId"))).SetInternal(err) + } + publicID, err := url.QueryUnescape(c.Param("publicId")) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("publicID is invalid: %s", c.Param("publicId"))).SetInternal(err) + } + filename, err := url.QueryUnescape(c.Param("filename")) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("filename is invalid: %s", c.Param("filename"))).SetInternal(err) + } + resourceFind := &api.ResourceFind{ + ID: &resourceID, + PublicID: &publicID, + Filename: &filename, + GetBlob: true, + } + resource, err := s.Store.FindResource(ctx, resourceFind) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to find resource by ID: %v", resourceID)).SetInternal(err) + } + + blob := resource.Blob + if resource.InternalPath != "" { + src, err := os.Open(resource.InternalPath) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to open the local resource: %s", resource.InternalPath)).SetInternal(err) + } + defer src.Close() + blob, err = io.ReadAll(src) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to read the local resource: %s", resource.InternalPath)).SetInternal(err) + } + } + + c.Response().Writer.Header().Set(echo.HeaderCacheControl, "max-age=31536000, immutable") + c.Response().Writer.Header().Set(echo.HeaderContentSecurityPolicy, "default-src 'self'") + resourceType := strings.ToLower(resource.Type) + if strings.HasPrefix(resourceType, "text") { + resourceType = echo.MIMETextPlainCharsetUTF8 + } else if strings.HasPrefix(resourceType, "video") || strings.HasPrefix(resourceType, "audio") { + http.ServeContent(c.Response(), c.Request(), resource.Filename, time.Unix(resource.UpdatedTs, 0), bytes.NewReader(blob)) + return nil + } + return c.Stream(http.StatusOK, resourceType, bytes.NewReader(blob)) + }) } func (s *Server) createResourceCreateActivity(c echo.Context, resource *api.Resource) error { diff --git a/web/src/utils/resource.ts b/web/src/utils/resource.ts index 1148b77a..e217e87a 100644 --- a/web/src/utils/resource.ts +++ b/web/src/utils/resource.ts @@ -3,5 +3,5 @@ export const getResourceUrl = (resource: Resource, withOrigin = true) => { return resource.externalLink; } - return `${withOrigin ? window.location.origin : ""}/o/r/${resource.id}/${resource.publicId}`; + return `${withOrigin ? window.location.origin : ""}/o/r/${resource.id}/${resource.publicId}/${resource.filename}`; };