mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-07-02 08:27:41 +08:00
747 lines
20 KiB
Go
747 lines
20 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"slices"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
|
)
|
|
|
|
const (
|
|
// EmbedsFormField represents the form field name for embedding files.
|
|
EmbedsFormField string = "embeds"
|
|
|
|
// WatermarkFormField represents the form field name for the watermark file.
|
|
WatermarkFormField string = "watermark"
|
|
|
|
// StampFormField represents the form field name for the stamp file.
|
|
StampFormField string = "stamp"
|
|
)
|
|
|
|
// FormData is a helper for validating and hydrating values from a
|
|
// "multipart/form-data" request.
|
|
//
|
|
// form := ctx.FormData()
|
|
type FormData struct {
|
|
values map[string][]string
|
|
files map[string]string
|
|
filesByField map[string][]string
|
|
diskToOriginal map[string]string
|
|
errors error
|
|
}
|
|
|
|
// Validate returns nil or an error related to the [FormData] values, with a
|
|
// [SentinelHttpError] (status code 400, errors' details as a message) wrapped
|
|
// inside.
|
|
//
|
|
// var foo string
|
|
//
|
|
// err := ctx.FormData().
|
|
// MandatoryString("foo", &foo, "bar").
|
|
// Validate()
|
|
func (form *FormData) Validate() error {
|
|
if form.errors == nil {
|
|
return nil
|
|
}
|
|
|
|
return WrapError(
|
|
form.errors,
|
|
NewSentinelHttpError(http.StatusBadRequest, fmt.Sprintf("Invalid form data: %s", form.errors)),
|
|
)
|
|
}
|
|
|
|
// String binds a form field to a string variable.
|
|
//
|
|
// var foo string
|
|
//
|
|
// ctx.FormData().String("foo", &foo, "bar")
|
|
func (form *FormData) String(key string, target *string, defaultValue string) *FormData {
|
|
return form.mustValue(key, target, defaultValue)
|
|
}
|
|
|
|
// MandatoryString binds a form field to a string variable. It populates
|
|
// an error if the value is empty or the "key" does not exist.
|
|
//
|
|
// var foo string
|
|
//
|
|
// ctx.FormData().MandatoryString("foo", &foo)
|
|
func (form *FormData) MandatoryString(key string, target *string) *FormData {
|
|
return form.mustMandatoryField(key, target)
|
|
}
|
|
|
|
// Bool binds a form field to a bool variable. It populates an error if
|
|
// the value is not bool.
|
|
//
|
|
// var foo bool
|
|
//
|
|
// ctx.FormData().Bool("foo", &foo, true)
|
|
func (form *FormData) Bool(key string, target *bool, defaultValue bool) *FormData {
|
|
return form.mustValue(key, target, defaultValue)
|
|
}
|
|
|
|
// MandatoryBool binds a form field to a bool variable. It populates an
|
|
// error if the value is not bool, is empty, or the "key" does not exist.
|
|
//
|
|
// var foo bool
|
|
//
|
|
// ctx.FormData().MandatoryBool("foo", &foo)
|
|
func (form *FormData) MandatoryBool(key string, target *bool) *FormData {
|
|
return form.mustMandatoryField(key, target)
|
|
}
|
|
|
|
// Int binds a form field to an int variable. It populates an error if the
|
|
// value is not int.
|
|
//
|
|
// var foo int
|
|
//
|
|
// ctx.FormData().Int("foo", &foo, 2)
|
|
func (form *FormData) Int(key string, target *int, defaultValue int) *FormData {
|
|
return form.mustValue(key, target, defaultValue)
|
|
}
|
|
|
|
// MandatoryInt binds a form field to an int variable. It populates an
|
|
// error if the value is not int, or is empty, or the "key" does not exist.
|
|
//
|
|
// var foo int
|
|
//
|
|
// ctx.FormData().MandatoryInt("foo", &foo)
|
|
func (form *FormData) MandatoryInt(key string, target *int) *FormData {
|
|
return form.mustMandatoryField(key, target)
|
|
}
|
|
|
|
// Float64 binds a form field to a float64 variable. It populates an error
|
|
// if the value is not float64.
|
|
//
|
|
// var foo float64
|
|
//
|
|
// ctx.FormData().Float64("foo", &foo, 2.0)
|
|
func (form *FormData) Float64(key string, target *float64, defaultValue float64) *FormData {
|
|
return form.mustValue(key, target, defaultValue)
|
|
}
|
|
|
|
// MandatoryFloat64 binds a form field to a float64 variable. It populates
|
|
// an error if the value is not float64, is empty, or the "key" does not exist.
|
|
//
|
|
// var foo float64
|
|
//
|
|
// ctx.FormData().MandatoryFloat64("foo", &foo)
|
|
func (form *FormData) MandatoryFloat64(key string, target *float64) *FormData {
|
|
return form.mustMandatoryField(key, target)
|
|
}
|
|
|
|
// Duration binds a form field to a time.Duration variable. It populates
|
|
// an error if the form field is not time.Duration.
|
|
//
|
|
// var foo time.Duration
|
|
//
|
|
// ctx.FormData().Duration("foo", &foo, time.Duration(2) * time.Second)
|
|
func (form *FormData) Duration(key string, target *time.Duration, defaultValue time.Duration) *FormData {
|
|
return form.mustValue(key, target, defaultValue)
|
|
}
|
|
|
|
// MandatoryDuration binds a form field to a time.Duration variable. It
|
|
// populates an error if the value is not time.Duration, or is empty, or the
|
|
// "key" does not exist.
|
|
//
|
|
// var foo time.Duration
|
|
//
|
|
// ctx.FormData().MandatoryDuration("foo", &foo)
|
|
func (form *FormData) MandatoryDuration(key string, target *time.Duration) *FormData {
|
|
return form.mustMandatoryField(key, target)
|
|
}
|
|
|
|
// Inches bind a form field to a float64 variable. It populates an error
|
|
// if the value cannot be computed back to inches.
|
|
//
|
|
// var foo float64
|
|
//
|
|
// ctx.FormData().Inches("foo", &foo, 2.0)
|
|
func (form *FormData) Inches(key string, target *float64, defaultValue float64) *FormData {
|
|
form.inches(key, target)
|
|
if *target == -math.MaxFloat64 {
|
|
*target = defaultValue
|
|
}
|
|
return form
|
|
}
|
|
|
|
// MandatoryInches binds a form field to a float64 variable. It populates
|
|
// an error if the value cannot be computed back to inches, is empty, or the
|
|
// "key" does not exist.
|
|
//
|
|
// var foo float64
|
|
//
|
|
// ctx.FormData().MandatoryInches("foo", &foo)
|
|
func (form *FormData) MandatoryInches(key string, target *float64) *FormData {
|
|
val, ok := form.values[key]
|
|
if !ok || val[0] == "" {
|
|
form.append(
|
|
fmt.Errorf("form field '%s' is required", key),
|
|
)
|
|
return form
|
|
}
|
|
return form.inches(key, target)
|
|
}
|
|
|
|
// inches tries to compute a string value to inches.
|
|
func (form *FormData) inches(key string, target *float64) *FormData {
|
|
var value string
|
|
form.mustValue(key, &value, "")
|
|
|
|
if value == "" {
|
|
*target = -math.MaxFloat64
|
|
return form
|
|
}
|
|
|
|
for _, unit := range []string{"pt", "px", "in", "mm", "cm", "pc"} {
|
|
if !strings.HasSuffix(value, unit) {
|
|
continue
|
|
}
|
|
|
|
val, err := strconv.ParseFloat(strings.TrimSuffix(value, unit), 64)
|
|
if err != nil {
|
|
form.append(
|
|
fmt.Errorf("form field '%s' is invalid (got '%s', resulting to %w)", key, value, err),
|
|
)
|
|
return form
|
|
}
|
|
|
|
switch unit {
|
|
case "pt":
|
|
*target = val * (1.0 / 72.0)
|
|
case "px":
|
|
*target = val * (1.0 / 96.0)
|
|
case "in":
|
|
*target = val
|
|
case "mm":
|
|
*target = val * (1.0 / 25.4)
|
|
case "cm":
|
|
*target = val * (1.0 / 2.54)
|
|
case "pc":
|
|
*target = val * (1.0 / 6.0)
|
|
}
|
|
|
|
return form
|
|
}
|
|
|
|
val, err := strconv.ParseFloat(value, 64)
|
|
if err != nil {
|
|
form.append(
|
|
fmt.Errorf("form field '%s' is invalid (got '%s', resulting to %w)", key, value, err),
|
|
)
|
|
return form
|
|
}
|
|
|
|
*target = val
|
|
return form
|
|
}
|
|
|
|
// Custom helps to define a custom binding function for a form field.
|
|
//
|
|
// var foo map[string]string
|
|
//
|
|
// ctx.FormData().Custom("foo", func(value string) error {
|
|
// if value == "" {
|
|
// foo = "bar"
|
|
//
|
|
// return nil
|
|
// }
|
|
//
|
|
// err := json.Unmarshal([]byte(value), &foo)
|
|
// if err != nil {
|
|
// return fmt.Errorf("unmarshal foo: %w", err)
|
|
// }
|
|
//
|
|
// return nil
|
|
// })
|
|
func (form *FormData) Custom(key string, assign func(value string) error) *FormData {
|
|
var value string
|
|
form.mustValue(key, &value, "")
|
|
|
|
err := assign(value)
|
|
if err != nil {
|
|
form.append(
|
|
fmt.Errorf("form field '%s' is invalid (got '%s', resulting to %w)", key, value, err),
|
|
)
|
|
}
|
|
|
|
return form
|
|
}
|
|
|
|
// MandatoryCustom helps to define a custom binding function for a form field.
|
|
// It populates an error if the value is empty or the "key" does not exist.
|
|
//
|
|
// var foo map[string]string
|
|
//
|
|
// ctx.FormData().MandatoryCustom("foo", func(value string) error {
|
|
// err := json.Unmarshal([]byte(value), &foo)
|
|
// if err != nil {
|
|
// return fmt.Errorf("unmarshal foo: %w", err)
|
|
// }
|
|
//
|
|
// return nil
|
|
// })
|
|
func (form *FormData) MandatoryCustom(key string, assign func(value string) error) *FormData {
|
|
var value string
|
|
form.mustMandatoryField(key, &value)
|
|
|
|
if value == "" {
|
|
return form
|
|
}
|
|
|
|
err := assign(value)
|
|
if err != nil {
|
|
form.append(
|
|
fmt.Errorf("form field '%s' is invalid (got '%s', resulting to %w)", key, value, err),
|
|
)
|
|
}
|
|
|
|
return form
|
|
}
|
|
|
|
// Path binds the absolute path of a form data file to a string variable.
|
|
//
|
|
// var path string
|
|
//
|
|
// ctx.FormData().Path("foo.txt", &path)
|
|
func (form *FormData) Path(filename string, target *string) *FormData {
|
|
return form.path(filename, target)
|
|
}
|
|
|
|
// MandatoryPath binds the absolute path of a form data file to a string
|
|
// variable. It populates an error if the file does not exist.
|
|
//
|
|
// var path string
|
|
//
|
|
// ctx.FormData().MandatoryPath("foo.txt", &path)
|
|
func (form *FormData) MandatoryPath(filename string, target *string) *FormData {
|
|
return form.mandatoryPath(filename, target)
|
|
}
|
|
|
|
// Content binds the content of a form data file to a string variable.
|
|
//
|
|
// var content string
|
|
//
|
|
// ctx.FormData().Content("foo.txt", &content, "bar")
|
|
func (form *FormData) Content(filename string, target *string, defaultValue string) *FormData {
|
|
var path string
|
|
form.path(filename, &path)
|
|
|
|
if path == "" {
|
|
*target = defaultValue
|
|
|
|
return form
|
|
}
|
|
|
|
return form.readFile(path, filename, target)
|
|
}
|
|
|
|
// MandatoryContent binds the content of a form data file to a string variable.
|
|
// It populates an error if the file does not exist.
|
|
//
|
|
// var content string
|
|
//
|
|
// ctx.FormData().MandatoryContent("foo.txt", &content)
|
|
func (form *FormData) MandatoryContent(filename string, target *string) *FormData {
|
|
var path string
|
|
form.mandatoryPath(filename, &path)
|
|
|
|
if path == "" {
|
|
return form
|
|
}
|
|
|
|
return form.readFile(path, filename, target)
|
|
}
|
|
|
|
// Paths bind the absolute paths of form data files, according to a list of
|
|
// file extensions, to a string slice variable.
|
|
//
|
|
// var paths []string
|
|
//
|
|
// ctx.FormData().Paths([]string{".txt"}, &paths)
|
|
func (form *FormData) Paths(extensions []string, target *[]string) *FormData {
|
|
return form.paths(extensions, target)
|
|
}
|
|
|
|
// Embeds binds the absolute paths of form data files that should be
|
|
// embedded in the PDF. Only files uploaded with the "embeds" field name
|
|
// will be included.
|
|
//
|
|
// var embeds []string
|
|
//
|
|
// ctx.FormData().Embeds(&embeds)
|
|
func (form *FormData) Embeds(target *[]string) *FormData {
|
|
if form.errors != nil {
|
|
return form
|
|
}
|
|
|
|
// Get files from the "embeds" field
|
|
if paths, ok := form.filesByField[EmbedsFormField]; ok {
|
|
*target = append(*target, paths...)
|
|
}
|
|
|
|
return form
|
|
}
|
|
|
|
// EmbedsMetadata parses the "embedsMetadata" form field (a JSON string) into
|
|
// a map keyed by filename. Each value is a map of property names to values
|
|
// (e.g., "mimeType" and "relationship").
|
|
//
|
|
// var metadata map[string]map[string]string
|
|
//
|
|
// ctx.FormData().EmbedsMetadata(&metadata)
|
|
func (form *FormData) EmbedsMetadata(target *map[string]map[string]string) *FormData {
|
|
if form.errors != nil {
|
|
return form
|
|
}
|
|
|
|
val, ok := form.values["embedsMetadata"]
|
|
if !ok || len(val) == 0 || val[0] == "" {
|
|
return form
|
|
}
|
|
|
|
raw := val[0]
|
|
parsed := make(map[string]map[string]string)
|
|
|
|
err := json.Unmarshal([]byte(raw), &parsed)
|
|
if err != nil {
|
|
form.append(
|
|
fmt.Errorf("form field 'embedsMetadata' is invalid: %w", err),
|
|
)
|
|
return form
|
|
}
|
|
|
|
*target = parsed
|
|
return form
|
|
}
|
|
|
|
// FacturX parses the "facturx" form field (a JSON string) into a
|
|
// [gotenberg.FacturX]. The "conformanceLevel" property is mandatory; the
|
|
// "documentType", "version", and "documentFileName" properties default to
|
|
// "INVOICE", "1.0", and "factur-x.xml" respectively. It leaves the target
|
|
// untouched when the field is absent.
|
|
//
|
|
// var facturX gotenberg.FacturX
|
|
//
|
|
// ctx.FormData().FacturX(&facturX, false)
|
|
func (form *FormData) FacturX(target *gotenberg.FacturX, mandatory bool) *FormData {
|
|
if form.errors != nil {
|
|
return form
|
|
}
|
|
|
|
val, ok := form.values["facturx"]
|
|
if !ok || len(val) == 0 || val[0] == "" {
|
|
if mandatory {
|
|
form.append(fmt.Errorf("form field '%s' is required", "facturx"))
|
|
}
|
|
return form
|
|
}
|
|
|
|
var parsed struct {
|
|
ConformanceLevel string `json:"conformanceLevel"`
|
|
DocumentType string `json:"documentType"`
|
|
DocumentFileName string `json:"documentFileName"`
|
|
Version string `json:"version"`
|
|
}
|
|
|
|
err := json.Unmarshal([]byte(val[0]), &parsed)
|
|
if err != nil {
|
|
form.append(fmt.Errorf("form field 'facturx' is invalid: %w", err))
|
|
return form
|
|
}
|
|
|
|
facturX := gotenberg.FacturX{
|
|
ConformanceLevel: parsed.ConformanceLevel,
|
|
DocumentType: parsed.DocumentType,
|
|
DocumentFileName: parsed.DocumentFileName,
|
|
Version: parsed.Version,
|
|
}
|
|
|
|
if facturX.DocumentType == "" {
|
|
facturX.DocumentType = gotenberg.FacturXDocumentTypeInvoice
|
|
}
|
|
|
|
if facturX.Version == "" {
|
|
facturX.Version = "1.0"
|
|
}
|
|
|
|
if facturX.DocumentFileName == "" {
|
|
facturX.DocumentFileName = "factur-x.xml"
|
|
}
|
|
|
|
switch facturX.ConformanceLevel {
|
|
case gotenberg.FacturXConformanceMinimum,
|
|
gotenberg.FacturXConformanceBasicWL,
|
|
gotenberg.FacturXConformanceBasic,
|
|
gotenberg.FacturXConformanceEN16931,
|
|
gotenberg.FacturXConformanceExtended,
|
|
gotenberg.FacturXConformanceXRechnung:
|
|
case "":
|
|
form.append(errors.New("form field 'facturx' is invalid: 'conformanceLevel' is required"))
|
|
return form
|
|
default:
|
|
form.append(fmt.Errorf("form field 'facturx' is invalid: unsupported 'conformanceLevel' '%s'", facturX.ConformanceLevel))
|
|
return form
|
|
}
|
|
|
|
switch facturX.DocumentType {
|
|
case gotenberg.FacturXDocumentTypeInvoice,
|
|
gotenberg.FacturXDocumentTypeOrder,
|
|
gotenberg.FacturXDocumentTypeOrderResponse,
|
|
gotenberg.FacturXDocumentTypeOrderChange:
|
|
default:
|
|
form.append(fmt.Errorf("form field 'facturx' is invalid: unsupported 'documentType' '%s'", facturX.DocumentType))
|
|
return form
|
|
}
|
|
|
|
*target = facturX
|
|
return form
|
|
}
|
|
|
|
// MandatoryPaths binds the absolute paths of form data files, according to a
|
|
// list of file extensions, to a string slice variable. It populates an error
|
|
// if there is no file for given file extensions.
|
|
//
|
|
// var paths []string
|
|
//
|
|
// ctx.FormData().MandatoryPaths([]string{".txt"}, &paths)
|
|
func (form *FormData) MandatoryPaths(extensions []string, target *[]string) *FormData {
|
|
form.paths(extensions, target)
|
|
|
|
if len(*target) > 0 {
|
|
return form
|
|
}
|
|
|
|
form.append(
|
|
fmt.Errorf("no form file found for extensions: %v", extensions),
|
|
)
|
|
|
|
return form
|
|
}
|
|
|
|
// Watermark binds the absolute path of the form data file that should be
|
|
// used as a watermark source. Only a file uploaded with the "watermark"
|
|
// field name will be included.
|
|
func (form *FormData) Watermark(target *string) *FormData {
|
|
if form.errors != nil {
|
|
return form
|
|
}
|
|
|
|
if paths, ok := form.filesByField[WatermarkFormField]; ok && len(paths) > 0 {
|
|
*target = paths[0]
|
|
}
|
|
|
|
return form
|
|
}
|
|
|
|
// Stamp binds the absolute path of the form data file that should be
|
|
// used as a stamp source. Only a file uploaded with the "stamp"
|
|
// field name will be included.
|
|
func (form *FormData) Stamp(target *string) *FormData {
|
|
if form.errors != nil {
|
|
return form
|
|
}
|
|
|
|
if paths, ok := form.filesByField[StampFormField]; ok && len(paths) > 0 {
|
|
*target = paths[0]
|
|
}
|
|
|
|
return form
|
|
}
|
|
|
|
// paths bind the absolute paths of form data files, according to a list of
|
|
// file extensions, to a string slice variable.
|
|
// embeds, watermark, and stamp files are excluded.
|
|
func (form *FormData) paths(extensions []string, target *[]string) *FormData {
|
|
embeds, ok := form.filesByField[EmbedsFormField]
|
|
watermarks, wmOk := form.filesByField[WatermarkFormField]
|
|
stamps, stOk := form.filesByField[StampFormField]
|
|
|
|
// Collect (originalFilename, diskPath) pairs so that we can sort by
|
|
// original filename rather than by UUID-based disk name.
|
|
// See https://github.com/gotenberg/gotenberg/issues/1500.
|
|
type entry struct {
|
|
original string
|
|
disk string
|
|
}
|
|
var entries []entry
|
|
|
|
for filename, path := range form.files {
|
|
if ok && slices.Contains(embeds, path) {
|
|
continue
|
|
}
|
|
|
|
if wmOk && slices.Contains(watermarks, path) {
|
|
continue
|
|
}
|
|
|
|
if stOk && slices.Contains(stamps, path) {
|
|
continue
|
|
}
|
|
|
|
for _, ext := range extensions {
|
|
// See https://github.com/gotenberg/gotenberg/issues/228.
|
|
if strings.ToLower(filepath.Ext(filename)) == ext {
|
|
entries = append(entries, entry{original: filename, disk: path})
|
|
}
|
|
}
|
|
}
|
|
|
|
// See https://github.com/gotenberg/gotenberg/issues/139.
|
|
originals := make(gotenberg.AlphanumericSort, len(entries))
|
|
for i, e := range entries {
|
|
originals[i] = e.original
|
|
}
|
|
sort.Sort(originals)
|
|
|
|
// Build a lookup from original name to disk path.
|
|
lookup := make(map[string]string, len(entries))
|
|
for _, e := range entries {
|
|
lookup[e.original] = e.disk
|
|
}
|
|
for _, o := range originals {
|
|
*target = append(*target, lookup[o])
|
|
}
|
|
|
|
return form
|
|
}
|
|
|
|
// append adds an error to the list of errors.
|
|
func (form *FormData) append(err error) {
|
|
form.errors = errors.Join(form.errors, err)
|
|
}
|
|
|
|
// mustValue binds the target interface with a form field. If the value is
|
|
// empty or the "key" does not exist, it binds the default value. Currently,
|
|
// only the string, bool, int, float64 and time.Duration types are bindable.
|
|
func (form *FormData) mustValue(key string, target any, defaultValue any) *FormData {
|
|
val, ok := form.values[key]
|
|
|
|
if !ok || val[0] == "" {
|
|
switch t := (target).(type) {
|
|
case *string:
|
|
*t = defaultValue.(string)
|
|
case *bool:
|
|
*t = defaultValue.(bool)
|
|
case *int:
|
|
*t = defaultValue.(int)
|
|
case *float64:
|
|
*t = defaultValue.(float64)
|
|
case *time.Duration:
|
|
*t = defaultValue.(time.Duration)
|
|
default:
|
|
panic("target type not supported")
|
|
}
|
|
|
|
return form
|
|
}
|
|
|
|
return form.mustAssign(key, val[0], target)
|
|
}
|
|
|
|
// mustMandatoryField binds the target interface with a form field. It
|
|
// populates an error if the value is empty or the "key" does not exist.
|
|
// Currently, only the string, bool, int, float64 and time.Duration types are
|
|
// bindable.
|
|
func (form *FormData) mustMandatoryField(key string, target any) *FormData {
|
|
val, ok := form.values[key]
|
|
|
|
if !ok || val[0] == "" {
|
|
form.append(
|
|
fmt.Errorf("form field '%s' is required", key),
|
|
)
|
|
|
|
return form
|
|
}
|
|
|
|
form.mustAssign(key, val[0], target)
|
|
|
|
return form
|
|
}
|
|
|
|
// mustAssign parses the string value and tries to convert it to the target
|
|
// interface real type. Currently, only the string, bool, int, float64 and
|
|
// time.Duration types are bindable.
|
|
func (form *FormData) mustAssign(key, value string, target any) *FormData {
|
|
var err error
|
|
|
|
switch t := (target).(type) {
|
|
case *string:
|
|
*t = value
|
|
case *bool:
|
|
*t, err = strconv.ParseBool(value)
|
|
case *int:
|
|
*t, err = strconv.Atoi(value)
|
|
case *float64:
|
|
*t, err = strconv.ParseFloat(value, 64)
|
|
case *time.Duration:
|
|
*t, err = time.ParseDuration(value)
|
|
default:
|
|
panic("target type not supported")
|
|
}
|
|
|
|
if err != nil {
|
|
form.append(
|
|
fmt.Errorf("form field '%s' is invalid (got '%s', resulting to %w)", key, value, err),
|
|
)
|
|
}
|
|
|
|
return form
|
|
}
|
|
|
|
// path binds the absolute path of a form data file to a string variable.
|
|
func (form *FormData) path(filename string, target *string) *FormData {
|
|
for name, path := range form.files {
|
|
// See https://github.com/gotenberg/gotenberg/issues/228.
|
|
nameLowerExt := strings.TrimSuffix(name, filepath.Ext(name)) + strings.ToLower(filepath.Ext(name))
|
|
if name == filename || nameLowerExt == filename {
|
|
*target = path
|
|
return form
|
|
}
|
|
}
|
|
|
|
return form
|
|
}
|
|
|
|
// mandatoryPath binds the absolute path of a form data file to a string
|
|
// variable. It populates an error if the file does not exist.
|
|
func (form *FormData) mandatoryPath(filename string, target *string) *FormData {
|
|
form.path(filename, target)
|
|
|
|
if *target != "" {
|
|
return form
|
|
}
|
|
|
|
form.append(
|
|
fmt.Errorf("form file '%s' is required", filename),
|
|
)
|
|
|
|
return form
|
|
}
|
|
|
|
// readFile binds the content of a file to a string variable. It populates an
|
|
// error if it fails to read the file content.
|
|
func (form *FormData) readFile(path, filename string, target *string) *FormData {
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
form.append(
|
|
fmt.Errorf("form file '%s' is invalid (%w)", filename, err),
|
|
)
|
|
|
|
return form
|
|
}
|
|
|
|
*target = string(b)
|
|
|
|
return form
|
|
}
|