mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-08 00:22:14 +01:00
311 lines
7.3 KiB
Go
311 lines
7.3 KiB
Go
package api
|
|
|
|
import (
|
|
"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 (
|
|
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"
|
|
)
|
|
|
|
type resource struct {
|
|
formValues map[string]string
|
|
formFilesDirPath string
|
|
opts *Options
|
|
}
|
|
|
|
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 r, err
|
|
}
|
|
r.formFilesDirPath = dirPath
|
|
if err := os.MkdirAll(dirPath, 0755); err != nil {
|
|
return nil, fmt.Errorf("%s: making directory: %v", dirPath, err)
|
|
}
|
|
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 fmt.Errorf("getting multipart form: %v", err)
|
|
}
|
|
for _, files := range form.File {
|
|
for _, fh := range files {
|
|
in, err := fh.Open()
|
|
if err != nil {
|
|
return fmt.Errorf("%s: opening file: %v", fh.Filename, 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 nil
|
|
}
|
|
|
|
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: form file does not exist", filename)
|
|
}
|
|
absPath, err := filepath.Abs(fpath)
|
|
if err != nil {
|
|
return "", fmt.Errorf("%s: getting absolute path: %v", fpath, err)
|
|
}
|
|
return absPath, nil
|
|
}
|
|
|
|
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.formFilesDirPath, func(path string, info os.FileInfo, _ error) error {
|
|
if info.IsDir() {
|
|
return nil
|
|
}
|
|
fpath, err := r.fpath(info.Name())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, ext := range exts {
|
|
if filepath.Ext(fpath) == ext {
|
|
fpaths = append(fpaths, fpath)
|
|
return nil
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
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
|
|
}
|