diff --git a/docs/files.md b/docs/files.md index 5d17a751e7c..6b4c579eaf0 100644 --- a/docs/files.md +++ b/docs/files.md @@ -905,6 +905,9 @@ Download the file content. By default the `content-disposition` will be `inline`, but it will be `attachment` if the query string contains the parameter `Dl=1` +For a PDF file, it's possible to get only a single page by using the `Page` +parameter in the query-string (1 is the first page). + #### Request ```http @@ -929,6 +932,9 @@ Download the file content from its path. By default the `content-disposition` will be `inline`, but it will be `attachment` if the query string contains the parameter `Dl=1` +For a PDF file, it's possible to get only a single page by using the `Page` +parameter in the query-string (1 is the first page). + #### Request ```http diff --git a/model/vfs/file.go b/model/vfs/file.go index 526da8a531e..ddeb4418a3f 100644 --- a/model/vfs/file.go +++ b/model/vfs/file.go @@ -1,15 +1,18 @@ package vfs import ( + "bytes" "encoding/base64" "fmt" "mime" "net/http" "os" "path" + "path/filepath" "strings" "time" + "github.com/cozy/cozy-stack/pkg/config/config" "github.com/cozy/cozy-stack/pkg/consts" "github.com/cozy/cozy-stack/pkg/couchdb" "github.com/labstack/echo/v4" @@ -252,6 +255,28 @@ func ServeFileContent(fs VFS, doc *FileDoc, version *Version, filename, disposit return nil } +// ServePDFPage replies to an http request with a single page from a PDF file. +func ServePDFPage(fs VFS, doc *FileDoc, disposition string, page int, req *http.Request, w http.ResponseWriter) error { + ext := filepath.Ext(doc.DocName) + basename := strings.TrimSuffix(doc.DocName, ext) + filename := fmt.Sprintf("%s (%d)%s", basename, page, ext) + + f, err := fs.OpenFile(doc) + if err != nil { + return err + } + defer f.Close() + + extracted, err := config.PDF().ExtractPage(f, page) + if err != nil { + return err + } + content := bytes.NewReader(extracted.Bytes()) + + http.ServeContent(w, req, filename, doc.UpdatedAt, content) + return nil +} + // ModifyFileMetadata modify the metadata associated to a file. It can // be used to rename or move the file in the VFS. func ModifyFileMetadata(fs VFS, olddoc *FileDoc, patch *DocPatch) (*FileDoc, error) { diff --git a/web/files/files.go b/web/files/files.go index 0fb34b0e28a..71047ba90ba 100644 --- a/web/files/files.go +++ b/web/files/files.go @@ -905,7 +905,16 @@ func ReadFileContentFromIDHandler(c echo.Context) error { if c.QueryParam("Dl") == "1" { disposition = "attachment" } - err = vfs.ServeFileContent(instance.VFS(), doc, nil, "", disposition, c.Request(), c.Response()) + + if page := c.QueryParam("Page"); page != "" { + p, errp := strconv.Atoi(page) + if errp != nil { + return jsonapi.InvalidParameter("Page", errp) + } + err = vfs.ServePDFPage(instance.VFS(), doc, disposition, p, c.Request(), c.Response()) + } else { + err = vfs.ServeFileContent(instance.VFS(), doc, nil, "", disposition, c.Request(), c.Response()) + } if err != nil { return WrapVfsError(err) } @@ -1115,7 +1124,16 @@ func sendFileFromPath(c echo.Context, path string, checkPermission bool) error { } else if !checkPermission { addCSPRuleForDirectLink(c, doc.Class, doc.Mime) } - err = vfs.ServeFileContent(instance.VFS(), doc, nil, "", disposition, c.Request(), c.Response()) + + if page := c.QueryParam("Page"); page != "" { + p, errp := strconv.Atoi(page) + if errp != nil { + return jsonapi.InvalidParameter("Page", errp) + } + err = vfs.ServePDFPage(instance.VFS(), doc, disposition, p, c.Request(), c.Response()) + } else { + err = vfs.ServeFileContent(instance.VFS(), doc, nil, "", disposition, c.Request(), c.Response()) + } if err != nil { return WrapVfsError(err) }