mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-15 20:02:15 +01:00
5.0.0 (#66)
This commit is contained in:
@@ -1,106 +1,264 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/printer"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/rand"
|
||||
)
|
||||
|
||||
const (
|
||||
remoteURL string = "remoteURL"
|
||||
webhookURL string = "webhookURL"
|
||||
paperWidth string = "paperWidth"
|
||||
paperHeight string = "paperHeight"
|
||||
marginTop string = "marginTop"
|
||||
marginBottom string = "marginBottom"
|
||||
marginLeft string = "marginLeft"
|
||||
marginRight string = "marginRight"
|
||||
landscape string = "landscape"
|
||||
webFontsTimeout string = "webFontsTimeout"
|
||||
filename string = "filename"
|
||||
resultFilename string = "resultFilename"
|
||||
waitTimeout string = "waitTimeout"
|
||||
webhookURL string = "webhookURL"
|
||||
remoteURL string = "remoteURL"
|
||||
waitDelay string = "waitDelay"
|
||||
paperWidth string = "paperWidth"
|
||||
paperHeight string = "paperHeight"
|
||||
marginTop string = "marginTop"
|
||||
marginBottom string = "marginBottom"
|
||||
marginLeft string = "marginLeft"
|
||||
marginRight string = "marginRight"
|
||||
landscape string = "landscape"
|
||||
)
|
||||
|
||||
// resource facilitates storing and accessing
|
||||
// data from a multipart/form-data request.
|
||||
type resource struct {
|
||||
values map[string]string
|
||||
dirPath string
|
||||
formValues map[string]string
|
||||
formFilesDirPath string
|
||||
opts *Options
|
||||
}
|
||||
|
||||
func newResource(c echo.Context) (*resource, error) {
|
||||
type resourceContext struct {
|
||||
echo.Context
|
||||
opts *Options
|
||||
resource *resource
|
||||
}
|
||||
|
||||
func newResource(ctx *resourceContext) (*resource, error) {
|
||||
r := &resource{
|
||||
formValues: formValues(ctx),
|
||||
opts: ctx.opts,
|
||||
}
|
||||
dirPath, err := rand.Get()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return r, err
|
||||
}
|
||||
r.formFilesDirPath = dirPath
|
||||
if err := os.MkdirAll(dirPath, 0755); err != nil {
|
||||
return nil, fmt.Errorf("%s: making directory: %v", dirPath, err)
|
||||
}
|
||||
r := &resource{values: values(c), dirPath: dirPath}
|
||||
form, err := c.MultipartForm()
|
||||
if err := formFiles(ctx, dirPath); err != nil {
|
||||
return r, err
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func formValues(ctx *resourceContext) map[string]string {
|
||||
v := make(map[string]string)
|
||||
v[resultFilename] = ctx.FormValue(resultFilename)
|
||||
v[waitTimeout] = ctx.FormValue(waitTimeout)
|
||||
v[webhookURL] = ctx.FormValue(webhookURL)
|
||||
v[remoteURL] = ctx.FormValue(remoteURL)
|
||||
v[waitDelay] = ctx.FormValue(waitDelay)
|
||||
v[paperWidth] = ctx.FormValue(paperWidth)
|
||||
v[paperHeight] = ctx.FormValue(paperHeight)
|
||||
v[marginTop] = ctx.FormValue(marginTop)
|
||||
v[marginBottom] = ctx.FormValue(marginBottom)
|
||||
v[marginLeft] = ctx.FormValue(marginLeft)
|
||||
v[marginRight] = ctx.FormValue(marginRight)
|
||||
v[landscape] = ctx.FormValue(landscape)
|
||||
return v
|
||||
}
|
||||
|
||||
func formFiles(ctx *resourceContext, dirPath string) error {
|
||||
form, err := ctx.MultipartForm()
|
||||
if err != nil {
|
||||
return r, fmt.Errorf("getting multipart form: %v", err)
|
||||
return fmt.Errorf("getting multipart form: %v", err)
|
||||
}
|
||||
for _, files := range form.File {
|
||||
for _, fh := range files {
|
||||
in, err := fh.Open()
|
||||
if err != nil {
|
||||
return r, fmt.Errorf("%s: opening file: %v", fh.Filename, err)
|
||||
return fmt.Errorf("%s: opening file: %v", fh.Filename, err)
|
||||
}
|
||||
defer in.Close()
|
||||
if err := r.writeFile(fh.Filename, in); err != nil {
|
||||
return r, err
|
||||
defer in.Close() // nolint: errcheck
|
||||
fpath := fmt.Sprintf("%s/%s", dirPath, fh.Filename)
|
||||
out, err := os.Create(fpath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: creating new file: %v", fpath, err)
|
||||
}
|
||||
defer out.Close() // nolint: errcheck
|
||||
if err := out.Chmod(0644); err != nil {
|
||||
return fmt.Errorf("%s: changing file mode: %v", fpath, err)
|
||||
}
|
||||
if _, err := io.Copy(out, in); err != nil {
|
||||
return fmt.Errorf("%s: writing file: %v", fpath, err)
|
||||
}
|
||||
if _, err := out.Seek(0, 0); err != nil {
|
||||
return fmt.Errorf("%s: resetting read pointer: %v", fpath, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func values(c echo.Context) map[string]string {
|
||||
v := make(map[string]string)
|
||||
v[remoteURL] = c.FormValue(remoteURL)
|
||||
v[webhookURL] = c.FormValue(webhookURL)
|
||||
v[paperWidth] = c.FormValue(paperWidth)
|
||||
v[paperHeight] = c.FormValue(paperHeight)
|
||||
v[marginTop] = c.FormValue(marginTop)
|
||||
v[marginBottom] = c.FormValue(marginBottom)
|
||||
v[marginLeft] = c.FormValue(marginLeft)
|
||||
v[marginRight] = c.FormValue(marginRight)
|
||||
v[landscape] = c.FormValue(landscape)
|
||||
v[webFontsTimeout] = c.FormValue(webFontsTimeout)
|
||||
v[filename] = c.FormValue(filename)
|
||||
return v
|
||||
}
|
||||
|
||||
func (r *resource) writeFile(filename string, in io.Reader) error {
|
||||
fpath := fmt.Sprintf("%s/%s", r.dirPath, filename)
|
||||
out, err := os.Create(fpath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: creating new file: %v", fpath, err)
|
||||
}
|
||||
defer out.Close()
|
||||
if err := out.Chmod(0644); err != nil {
|
||||
return fmt.Errorf("%s: changing file mode: %v", fpath, err)
|
||||
}
|
||||
if _, err := io.Copy(out, in); err != nil {
|
||||
return fmt.Errorf("%s: writing file: %v", fpath, err)
|
||||
}
|
||||
if _, err := out.Seek(0, 0); err != nil {
|
||||
return fmt.Errorf("%s: resetting read pointer: %v", fpath, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *resource) filePath(filename string) (string, error) {
|
||||
fpath := fmt.Sprintf("%s/%s", r.dirPath, filename)
|
||||
func (r *resource) close() error {
|
||||
if _, err := os.Stat(r.formFilesDirPath); os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return os.RemoveAll(r.formFilesDirPath)
|
||||
}
|
||||
|
||||
const defaultHeaderFooterHTML string = "<html><head></head><body></body></html>"
|
||||
|
||||
func (r *resource) chromePrinterOptions() (*printer.ChromeOptions, error) {
|
||||
timeout, err := r.float64(waitTimeout, r.opts.DefaultWaitTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
delay, err := r.float64(waitDelay, 0.0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
header, err := r.content("header.html", defaultHeaderFooterHTML)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
footer, err := r.content("footer.html", defaultHeaderFooterHTML)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
width, err := r.float64(paperWidth, 8.27)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
height, err := r.float64(paperHeight, 11.7)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
top, err := r.float64(marginTop, 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bottom, err := r.float64(marginBottom, 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
left, err := r.float64(marginLeft, 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
right, err := r.float64(marginRight, 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
landscape, err := r.bool(landscape, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &printer.ChromeOptions{
|
||||
WaitTimeout: timeout,
|
||||
WaitDelay: delay,
|
||||
HeaderHTML: header,
|
||||
FooterHTML: footer,
|
||||
PaperWidth: width,
|
||||
PaperHeight: height,
|
||||
MarginTop: top,
|
||||
MarginBottom: bottom,
|
||||
MarginLeft: left,
|
||||
MarginRight: right,
|
||||
Landscape: landscape,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *resource) officePrinterOptions() (*printer.OfficeOptions, error) {
|
||||
timeout, err := r.float64(waitTimeout, r.opts.DefaultWaitTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
landscape, err := r.bool(landscape, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &printer.OfficeOptions{
|
||||
WaitTimeout: timeout,
|
||||
Landscape: landscape,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *resource) mergePrinterOptions() (*printer.MergeOptions, error) {
|
||||
timeout, err := r.float64(waitTimeout, r.opts.DefaultWaitTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &printer.MergeOptions{
|
||||
WaitTimeout: timeout,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *resource) has(key string) bool {
|
||||
v, ok := r.formValues[key]
|
||||
if ok {
|
||||
ok = v != ""
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
func (r *resource) hasFile(filename string) bool {
|
||||
fpath := fmt.Sprintf("%s/%s", r.formFilesDirPath, filename)
|
||||
_, err := os.Stat(fpath)
|
||||
return !os.IsNotExist(err)
|
||||
}
|
||||
|
||||
func (r *resource) get(key string) (string, error) {
|
||||
v, ok := r.formValues[key]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("form value %s does not exist", key)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (r *resource) float64(key string, defaultValue float64) (float64, error) {
|
||||
if !r.has(key) {
|
||||
return defaultValue, nil
|
||||
}
|
||||
v, err := r.get(key)
|
||||
if err != nil {
|
||||
return 0.0, err
|
||||
}
|
||||
f, err := strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
return 0.0, fmt.Errorf("form value %s: %v", key, err)
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func (r *resource) bool(key string, defaultValue bool) (bool, error) {
|
||||
if !r.has(key) {
|
||||
return defaultValue, nil
|
||||
}
|
||||
v, err := r.get(key)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
b, err := strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("form value %s: %v", key, err)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (r *resource) fpath(filename string) (string, error) {
|
||||
fpath := fmt.Sprintf("%s/%s", r.formFilesDirPath, filename)
|
||||
_, err := os.Stat(fpath)
|
||||
if os.IsNotExist(err) {
|
||||
return "", fmt.Errorf("%s: file does not exist", filename)
|
||||
return "", fmt.Errorf("%s: form file does not exist", filename)
|
||||
}
|
||||
absPath, err := filepath.Abs(fpath)
|
||||
if err != nil {
|
||||
@@ -109,13 +267,28 @@ func (r *resource) filePath(filename string) (string, error) {
|
||||
return absPath, nil
|
||||
}
|
||||
|
||||
func (r *resource) filePaths(exts []string) ([]string, error) {
|
||||
func (r *resource) content(filename string, defaultValue string) (string, error) {
|
||||
if !r.hasFile(filename) {
|
||||
return defaultValue, nil
|
||||
}
|
||||
fpath, err := r.fpath(filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
b, err := ioutil.ReadFile(fpath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%s: reading form file: %v", fpath, err)
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
func (r *resource) fpaths(exts ...string) ([]string, error) {
|
||||
var fpaths []string
|
||||
err := filepath.Walk(r.dirPath, func(path string, info os.FileInfo, _ error) error {
|
||||
err := filepath.Walk(r.formFilesDirPath, func(path string, info os.FileInfo, _ error) error {
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
fpath, err := r.filePath(info.Name())
|
||||
fpath, err := r.fpath(info.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -130,86 +303,8 @@ func (r *resource) filePaths(exts []string) ([]string, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(fpaths) == 0 {
|
||||
return nil, fmt.Errorf("no form files found for extensions: %v", exts)
|
||||
}
|
||||
return fpaths, nil
|
||||
}
|
||||
|
||||
func (r *resource) paperSize() ([2]float64, error) {
|
||||
defaultSize := [2]float64{8.27, 11.7}
|
||||
widthStr := r.values[paperWidth]
|
||||
heightStr := r.values[paperHeight]
|
||||
if widthStr == "" || heightStr == "" {
|
||||
return defaultSize, nil
|
||||
}
|
||||
width, err := strconv.ParseFloat(widthStr, 64)
|
||||
if err != nil {
|
||||
return defaultSize, fmt.Errorf("paper width: %v", err)
|
||||
}
|
||||
height, err := strconv.ParseFloat(heightStr, 64)
|
||||
if err != nil {
|
||||
return defaultSize, fmt.Errorf("paper height: %v", err)
|
||||
}
|
||||
return [2]float64{width, height}, nil
|
||||
}
|
||||
|
||||
func (r *resource) paperMargins() ([4]float64, error) {
|
||||
defaultMargins := [4]float64{1, 1, 1, 1}
|
||||
topStr := r.values[marginTop]
|
||||
bottomStr := r.values[marginBottom]
|
||||
leftStr := r.values[marginLeft]
|
||||
rightStr := r.values[marginRight]
|
||||
if topStr == "" || bottomStr == "" || leftStr == "" || rightStr == "" {
|
||||
return defaultMargins, nil
|
||||
}
|
||||
top, err := strconv.ParseFloat(topStr, 64)
|
||||
if err != nil {
|
||||
return defaultMargins, fmt.Errorf("margin top: %v", err)
|
||||
}
|
||||
bottom, err := strconv.ParseFloat(bottomStr, 64)
|
||||
if err != nil {
|
||||
return defaultMargins, fmt.Errorf("margin bottom: %v", err)
|
||||
}
|
||||
left, err := strconv.ParseFloat(leftStr, 64)
|
||||
if err != nil {
|
||||
return defaultMargins, fmt.Errorf("margin left: %v", err)
|
||||
}
|
||||
right, err := strconv.ParseFloat(rightStr, 64)
|
||||
if err != nil {
|
||||
return defaultMargins, fmt.Errorf("margin right: %v", err)
|
||||
}
|
||||
return [4]float64{top, bottom, left, right}, nil
|
||||
}
|
||||
|
||||
func (r *resource) landscape() (bool, error) {
|
||||
landscapeStr := r.values[landscape]
|
||||
if landscapeStr == "" {
|
||||
return false, nil
|
||||
}
|
||||
landscape, err := strconv.ParseBool(landscapeStr)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("landscape: %v", err)
|
||||
}
|
||||
return landscape, nil
|
||||
}
|
||||
|
||||
func (r *resource) remoteURL() (string, error) {
|
||||
if r.values[remoteURL] == "" {
|
||||
return "", errors.New("no remote URL")
|
||||
}
|
||||
return r.values[remoteURL], nil
|
||||
}
|
||||
|
||||
func (r *resource) webFontsTimeout() (int64, error) {
|
||||
webFontsTimeoutStr := r.values[webFontsTimeout]
|
||||
if webFontsTimeoutStr == "" {
|
||||
return 500, nil
|
||||
}
|
||||
timeout, err := strconv.ParseInt(webFontsTimeoutStr, 10, 64)
|
||||
if err != nil {
|
||||
return 500, fmt.Errorf("web fonts timeout: %v", err)
|
||||
}
|
||||
return timeout, nil
|
||||
}
|
||||
|
||||
func (r *resource) webhookURL() string { return r.values[webhookURL] }
|
||||
func (r *resource) filename() string { return r.values[filename] }
|
||||
func (r *resource) removeAll() error { return os.RemoveAll(r.dirPath) }
|
||||
|
||||
Reference in New Issue
Block a user