mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-17 12:42:16 +01:00
rolling back to golangci-lint 1.20.1 + moving normize filename to resource & tests with a file with special chars in its name + webhookurl custom headers done + preparing remote url custom headers
This commit is contained in:
2
Makefile
2
Makefile
@@ -3,7 +3,7 @@ VERSION=snapshot
|
|||||||
DOCKER_USER=
|
DOCKER_USER=
|
||||||
DOCKER_PASSWORD=
|
DOCKER_PASSWORD=
|
||||||
DOCKER_REPOSITORY=thecodingmachine
|
DOCKER_REPOSITORY=thecodingmachine
|
||||||
GOLANGCI_LINT_VERSION=1.21.0
|
GOLANGCI_LINT_VERSION=1.20.1
|
||||||
CODE_COVERAGE=0
|
CODE_COVERAGE=0
|
||||||
TINI_VERSION=0.18.0
|
TINI_VERSION=0.18.0
|
||||||
MAXIMUM_WAIT_TIMEOUT=30.0
|
MAXIMUM_WAIT_TIMEOUT=30.0
|
||||||
|
|||||||
@@ -33,4 +33,4 @@ RUN go mod download &&\
|
|||||||
# Copy our code source.
|
# Copy our code source.
|
||||||
COPY --chown=gotenberg:gotenberg . .
|
COPY --chown=gotenberg:gotenberg . .
|
||||||
|
|
||||||
CMD ["golangci-lint", "run" ,"--tests=false", "--enable-all", "--disable=dupl", "--disable=funlen" ]
|
CMD ["golangci-lint", "run" ,"--tests=false", "--enable-all", "--disable=dupl", "--disable=funlen", "--disable=wsl", "--disable=gocognit" ]
|
||||||
@@ -308,20 +308,47 @@ func convertAsync(ctx context.Context, p printer.Printer, filename, fpath string
|
|||||||
defer f.Close() // nolint: errcheck
|
defer f.Close() // nolint: errcheck
|
||||||
logger.DebugfOp(
|
logger.DebugfOp(
|
||||||
op,
|
op,
|
||||||
"sending result file '%s' to '%s'",
|
"preparing to send result file '%s' to '%s'...",
|
||||||
filename,
|
filename,
|
||||||
webhookURL,
|
webhookURL,
|
||||||
)
|
)
|
||||||
httpClient := &http.Client{
|
httpClient := &http.Client{
|
||||||
Timeout: xtime.Duration(webhookURLTimeout),
|
Timeout: xtime.Duration(webhookURLTimeout),
|
||||||
}
|
}
|
||||||
resp, err := httpClient.Post(webhookURL, "application/pdf", f) /* #nosec */
|
req, err := http.NewRequest(http.MethodPost, webhookURL, f)
|
||||||
|
if err != nil {
|
||||||
|
xerr := xerror.New(op, err)
|
||||||
|
logger.ErrorOp(xerror.Op(xerr), xerr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.Header.Set(echo.HeaderContentType, "application/pdf")
|
||||||
|
// set custom headers (if any).
|
||||||
|
for key, value := range resource.WebhookURLCustomHeaders(r) {
|
||||||
|
for _, v := range value {
|
||||||
|
req.Header.Add(key, v)
|
||||||
|
logger.DebugfOp(op, "added '%s' to custom header '%s'", v, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// send the result file.
|
||||||
|
logger.DebugfOp(
|
||||||
|
op,
|
||||||
|
"sending result file '%s' to '%s'...",
|
||||||
|
filename,
|
||||||
|
webhookURL,
|
||||||
|
)
|
||||||
|
resp, err := httpClient.Do(req) /* #nosec */
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xerr := xerror.New(op, err)
|
xerr := xerror.New(op, err)
|
||||||
logger.ErrorOp(xerror.Op(xerr), xerr)
|
logger.ErrorOp(xerror.Op(xerr), xerr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer resp.Body.Close() // nolint: errcheck
|
defer resp.Body.Close() // nolint: errcheck
|
||||||
|
logger.DebugfOp(
|
||||||
|
op,
|
||||||
|
"result file '%s' sent to '%s'",
|
||||||
|
filename,
|
||||||
|
webhookURL,
|
||||||
|
)
|
||||||
}()
|
}()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -579,11 +579,18 @@ func TestOfficeHandler(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestWebhook(t *testing.T) {
|
func TestWebhook(t *testing.T) {
|
||||||
|
customHeaderRealKey := http.CanonicalHeaderKey("MyCustomHeader")
|
||||||
|
customHeaderKey := fmt.Sprintf("%s%s", resource.WebhookURLCustomHeaderCanonicalBaseKey, customHeaderRealKey)
|
||||||
|
customHeaderValue := "foo"
|
||||||
status := make(chan error, 2)
|
status := make(chan error, 2)
|
||||||
rcv := echo.New()
|
rcv := echo.New()
|
||||||
rcv.POST("/foo", func(c echo.Context) error {
|
rcv.POST("/foo", func(c echo.Context) error {
|
||||||
if c.Request().Header.Get("Content-type") != "application/pdf" {
|
if c.Request().Header.Get(echo.HeaderContentType) != "application/pdf" {
|
||||||
status <- fmt.Errorf("wrong Content-type: got %s want %s", c.Request().Header.Get("Content-type"), "application/pdf")
|
status <- fmt.Errorf("wrong Content-type: got '%s' want '%s'", c.Request().Header.Get(echo.HeaderContentType), "application/pdf")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if c.Request().Header.Get(customHeaderRealKey) != customHeaderValue {
|
||||||
|
status <- fmt.Errorf("wrong '%s': got '%s' want '%s'", customHeaderRealKey, c.Request().Header.Get(customHeaderRealKey), customHeaderValue)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
body, err := ioutil.ReadAll(c.Request().Body)
|
body, err := ioutil.ReadAll(c.Request().Body)
|
||||||
@@ -607,6 +614,7 @@ func TestWebhook(t *testing.T) {
|
|||||||
body, contentType := test.MergeMultipartForm(t, map[string]string{string(resource.WebhookURLArgKey): "http://localhost:3001/foo"})
|
body, contentType := test.MergeMultipartForm(t, map[string]string{string(resource.WebhookURLArgKey): "http://localhost:3001/foo"})
|
||||||
req := httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
|
req := httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
|
||||||
req.Header.Set(echo.HeaderContentType, contentType)
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
|
req.Header.Set(customHeaderKey, customHeaderValue)
|
||||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||||
err := <-status
|
err := <-status
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@@ -620,5 +628,5 @@ func TestResultFilename(t *testing.T) {
|
|||||||
req.Header.Set(echo.HeaderContentType, contentType)
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
srv.ServeHTTP(rec, req)
|
srv.ServeHTTP(rec, req)
|
||||||
assert.Equal(t, "attachment; filename=\"foo.pdf\"", rec.Header().Get("Content-Disposition"))
|
assert.Equal(t, "attachment; filename=\"foo.pdf\"", rec.Header().Get(echo.HeaderContentDisposition))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/thecodingmachine/gotenberg/internal/app/xhttp/pkg/resource"
|
"github.com/thecodingmachine/gotenberg/internal/app/xhttp/pkg/resource"
|
||||||
"github.com/thecodingmachine/gotenberg/internal/pkg/conf"
|
"github.com/thecodingmachine/gotenberg/internal/pkg/conf"
|
||||||
"github.com/thecodingmachine/gotenberg/internal/pkg/normalize"
|
|
||||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xerror"
|
"github.com/thecodingmachine/gotenberg/internal/pkg/xerror"
|
||||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xlog"
|
"github.com/thecodingmachine/gotenberg/internal/pkg/xlog"
|
||||||
)
|
)
|
||||||
@@ -79,6 +78,10 @@ func (ctx *Context) WithResource(directoryName string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return r, err
|
return r, err
|
||||||
}
|
}
|
||||||
|
// retrieve custom headers from request.
|
||||||
|
for name, value := range ctx.Request().Header {
|
||||||
|
r.WithCustomHeader(name, value)
|
||||||
|
}
|
||||||
// retrieve form values from request.
|
// retrieve form values from request.
|
||||||
for _, key := range resource.ArgKeys() {
|
for _, key := range resource.ArgKeys() {
|
||||||
r.WithArg(key, ctx.FormValue(string(key)))
|
r.WithArg(key, ctx.FormValue(string(key)))
|
||||||
@@ -103,11 +106,7 @@ func (ctx *Context) WithResource(directoryName string) error {
|
|||||||
return r, err
|
return r, err
|
||||||
}
|
}
|
||||||
defer in.Close() // nolint: errcheck
|
defer in.Close() // nolint: errcheck
|
||||||
filename, err := normalize.String(fh.Filename)
|
if err := r.WithFile(fh.Filename, in); err != nil {
|
||||||
if err != nil {
|
|
||||||
return r, err
|
|
||||||
}
|
|
||||||
if err := r.WithFile(filename, in); err != nil {
|
|
||||||
return r, err
|
return r, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
37
internal/app/xhttp/pkg/resource/header.go
Normal file
37
internal/app/xhttp/pkg/resource/header.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package resource
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// RemoteURLCustomHeaderCanonicalBaseKey is the base key
|
||||||
|
// of custom headers send to the remote URL.
|
||||||
|
RemoteURLCustomHeaderCanonicalBaseKey string = "Gotenberg-Remoteurl-"
|
||||||
|
// WebhookURLCustomHeaderCanonicalBaseKey is the base key
|
||||||
|
// of custom headers send to the webhook URL.
|
||||||
|
WebhookURLCustomHeaderCanonicalBaseKey string = "Gotenberg-Webhookurl-"
|
||||||
|
)
|
||||||
|
|
||||||
|
func fetchCustomHeaders(r Resource, baseKey string) map[string][]string {
|
||||||
|
customHeaders := make(map[string][]string)
|
||||||
|
for key, value := range r.customHeaders {
|
||||||
|
if strings.Contains(key, baseKey) {
|
||||||
|
realKey := strings.Replace(key, baseKey, "", 1)
|
||||||
|
customHeaders[realKey] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return customHeaders
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteURLCustomHeaders is a helper for retrieving
|
||||||
|
// the custom headers for the URL conversion.
|
||||||
|
func RemoteURLCustomHeaders(r Resource) map[string][]string {
|
||||||
|
return fetchCustomHeaders(r, RemoteURLCustomHeaderCanonicalBaseKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebhookURLCustomHeaders is a helper for retrieving
|
||||||
|
// the custom headers for the webhook URL.
|
||||||
|
func WebhookURLCustomHeaders(r Resource) map[string][]string {
|
||||||
|
return fetchCustomHeaders(r, WebhookURLCustomHeaderCanonicalBaseKey)
|
||||||
|
}
|
||||||
62
internal/app/xhttp/pkg/resource/header_test.go
Normal file
62
internal/app/xhttp/pkg/resource/header_test.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package resource
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/thecodingmachine/gotenberg/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRemoteURLCustomHeaders(t *testing.T) {
|
||||||
|
const resourceDirectoryName string = "foo"
|
||||||
|
logger := test.DebugLogger()
|
||||||
|
r, err := New(logger, resourceDirectoryName)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
// should find the custom header.
|
||||||
|
customHeaderValue := "bar"
|
||||||
|
customHeaderCanonicalRealKey := "Foo"
|
||||||
|
customHeaderCanonicalKey := http.CanonicalHeaderKey(fmt.Sprintf("%s%s", RemoteURLCustomHeaderCanonicalBaseKey, customHeaderCanonicalRealKey))
|
||||||
|
r.WithCustomHeader(customHeaderCanonicalKey, []string{customHeaderValue})
|
||||||
|
r.WithCustomHeader("Bar", []string{"Bar"})
|
||||||
|
expected := map[string][]string{
|
||||||
|
customHeaderCanonicalRealKey: []string{
|
||||||
|
customHeaderValue,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
notExpected := map[string][]string{
|
||||||
|
customHeaderCanonicalKey: []string{
|
||||||
|
customHeaderValue,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
v := RemoteURLCustomHeaders(r)
|
||||||
|
assert.Equal(t, expected, v)
|
||||||
|
assert.NotEqual(t, notExpected, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWebhookURLCustomHeaders(t *testing.T) {
|
||||||
|
const resourceDirectoryName string = "foo"
|
||||||
|
logger := test.DebugLogger()
|
||||||
|
r, err := New(logger, resourceDirectoryName)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
// should find the custom header.
|
||||||
|
customHeaderValue := "bar"
|
||||||
|
customHeaderCanonicalRealKey := "Foo"
|
||||||
|
customHeaderCanonicalKey := http.CanonicalHeaderKey(fmt.Sprintf("%s%s", WebhookURLCustomHeaderCanonicalBaseKey, customHeaderCanonicalRealKey))
|
||||||
|
r.WithCustomHeader(customHeaderCanonicalKey, []string{customHeaderValue})
|
||||||
|
r.WithCustomHeader("Bar", []string{"Bar"})
|
||||||
|
expected := map[string][]string{
|
||||||
|
customHeaderCanonicalRealKey: []string{
|
||||||
|
customHeaderValue,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
notExpected := map[string][]string{
|
||||||
|
customHeaderCanonicalKey: []string{
|
||||||
|
customHeaderValue,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
v := WebhookURLCustomHeaders(r)
|
||||||
|
assert.Equal(t, expected, v)
|
||||||
|
assert.NotEqual(t, notExpected, v)
|
||||||
|
}
|
||||||
@@ -5,7 +5,9 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/thecodingmachine/gotenberg/internal/pkg/normalize"
|
||||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xassert"
|
"github.com/thecodingmachine/gotenberg/internal/pkg/xassert"
|
||||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xerror"
|
"github.com/thecodingmachine/gotenberg/internal/pkg/xerror"
|
||||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xlog"
|
"github.com/thecodingmachine/gotenberg/internal/pkg/xlog"
|
||||||
@@ -21,10 +23,11 @@ const TemporaryDirectory string = "tmp"
|
|||||||
// Resource helps managing
|
// Resource helps managing
|
||||||
// arguments and files for a conversion.
|
// arguments and files for a conversion.
|
||||||
type Resource struct {
|
type Resource struct {
|
||||||
logger xlog.Logger
|
logger xlog.Logger
|
||||||
dirPath string
|
dirPath string
|
||||||
args map[ArgKey]string
|
customHeaders map[string][]string
|
||||||
files map[string]file
|
args map[ArgKey]string
|
||||||
|
files map[string]file
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a Resource where its files will
|
// New creates a Resource where its files will
|
||||||
@@ -48,10 +51,11 @@ func New(logger xlog.Logger, directoryName string) (Resource, error) {
|
|||||||
}
|
}
|
||||||
logger.DebugfOp(op, "resource directory '%s' created", directoryName)
|
logger.DebugfOp(op, "resource directory '%s' created", directoryName)
|
||||||
return Resource{
|
return Resource{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
dirPath: dirPath,
|
dirPath: dirPath,
|
||||||
args: make(map[ArgKey]string),
|
customHeaders: make(map[string][]string),
|
||||||
files: make(map[string]file),
|
args: make(map[ArgKey]string),
|
||||||
|
files: make(map[string]file),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +74,19 @@ func (r Resource) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithCustomHeader add a new custom header to the Resource.
|
||||||
|
// Given key should be in canonical format.
|
||||||
|
func (r *Resource) WithCustomHeader(key string, value []string) {
|
||||||
|
const op string = "resource.Resource.WithCustomHeader"
|
||||||
|
if strings.Contains(key, RemoteURLCustomHeaderCanonicalBaseKey) ||
|
||||||
|
strings.Contains(key, WebhookURLCustomHeaderCanonicalBaseKey) {
|
||||||
|
r.customHeaders[key] = value
|
||||||
|
r.logger.DebugfOp(op, "added '%s' with value '%s' to resource custom headers", key, value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.logger.DebugfOp(op, "skipping '%s' as it is not a custom header...", key)
|
||||||
|
}
|
||||||
|
|
||||||
// WithArg add a new argument to the Resource.
|
// WithArg add a new argument to the Resource.
|
||||||
func (r *Resource) WithArg(key ArgKey, value string) {
|
func (r *Resource) WithArg(key ArgKey, value string) {
|
||||||
const op string = "resource.Resource.WithArg"
|
const op string = "resource.Resource.WithArg"
|
||||||
@@ -80,13 +97,24 @@ func (r *Resource) WithArg(key ArgKey, value string) {
|
|||||||
// WithFile add a new file to the Resource.
|
// WithFile add a new file to the Resource.
|
||||||
func (r *Resource) WithFile(filename string, in io.Reader) error {
|
func (r *Resource) WithFile(filename string, in io.Reader) error {
|
||||||
const op string = "resource.Resource.WithFile"
|
const op string = "resource.Resource.WithFile"
|
||||||
fpath := fmt.Sprintf("%s/%s", r.dirPath, filename)
|
resolver := func() error {
|
||||||
file := file{fpath: fpath}
|
// see https://github.com/thecodingmachine/gotenberg/issues/104.
|
||||||
if err := file.write(in); err != nil {
|
normalized, err := normalize.String(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fpath := fmt.Sprintf("%s/%s", r.dirPath, normalized)
|
||||||
|
file := file{fpath: fpath}
|
||||||
|
if err := file.write(in); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.files[filename] = file
|
||||||
|
r.logger.DebugfOp(op, "resource file '%s' created", filename)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := resolver(); err != nil {
|
||||||
return xerror.New(op, err)
|
return xerror.New(op, err)
|
||||||
}
|
}
|
||||||
r.files[filename] = file
|
|
||||||
r.logger.DebugfOp(op, "resource file '%s' created", filename)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ func OfficeFpaths(t *testing.T) []string {
|
|||||||
fpath(t, "office", "document.docx"),
|
fpath(t, "office", "document.docx"),
|
||||||
fpath(t, "office", "document.rtf"),
|
fpath(t, "office", "document.rtf"),
|
||||||
fpath(t, "office", "document.txt"),
|
fpath(t, "office", "document.txt"),
|
||||||
|
fpath(t, "office", "document_with_special_éà.txt"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
3
test/testdata/office/document_with_special_éà.txt
vendored
Normal file
3
test/testdata/office/document_with_special_éà.txt
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Gutenberg
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||||
Reference in New Issue
Block a user