mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-18 21:22:15 +01:00
updating with base branch
This commit is contained in:
@@ -8,21 +8,61 @@ import (
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/thecodingmachine/gotenberg/internal/app/xhttp/pkg/context"
|
||||
"github.com/thecodingmachine/gotenberg/internal/app/xhttp/pkg/resource"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/conf"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/printer"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xerror"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xrand"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xtime"
|
||||
)
|
||||
|
||||
const (
|
||||
pingEndpoint string = "/ping"
|
||||
mergeEndpoint string = "/merge"
|
||||
convertGroupEndpoint string = "/convert"
|
||||
htmlEndpoint string = "/html"
|
||||
urlEndpoint string = "/url"
|
||||
markdownEndpoint string = "/markdown"
|
||||
officeEndpoint string = "/office"
|
||||
)
|
||||
func pingEndpoint(config conf.Config) string {
|
||||
return fmt.Sprintf("%s%s", config.RootPath(), "ping")
|
||||
}
|
||||
|
||||
func mergeEndpoint(config conf.Config) string {
|
||||
return fmt.Sprintf("%s%s", config.RootPath(), "merge")
|
||||
}
|
||||
|
||||
func htmlEndpoint(config conf.Config) string {
|
||||
return fmt.Sprintf("%s%s", config.RootPath(), "convert/html")
|
||||
}
|
||||
|
||||
func urlEndpoint(config conf.Config) string {
|
||||
return fmt.Sprintf("%s%s", config.RootPath(), "convert/url")
|
||||
}
|
||||
|
||||
func markdownEndpoint(config conf.Config) string {
|
||||
return fmt.Sprintf("%s%s", config.RootPath(), "convert/markdown")
|
||||
}
|
||||
|
||||
func officeEndpoint(config conf.Config) string {
|
||||
return fmt.Sprintf("%s%s", config.RootPath(), "convert/office")
|
||||
}
|
||||
|
||||
func isMultipartFormDataEndpoint(config conf.Config, path string) bool {
|
||||
var multipartFormDataEndpoints []string
|
||||
multipartFormDataEndpoints = append(multipartFormDataEndpoints, mergeEndpoint(config))
|
||||
if !config.DisableGoogleChrome() {
|
||||
multipartFormDataEndpoints = append(
|
||||
multipartFormDataEndpoints,
|
||||
htmlEndpoint(config),
|
||||
urlEndpoint(config),
|
||||
markdownEndpoint(config),
|
||||
)
|
||||
}
|
||||
if !config.DisableUnoconv() {
|
||||
multipartFormDataEndpoints = append(
|
||||
multipartFormDataEndpoints,
|
||||
officeEndpoint(config),
|
||||
)
|
||||
}
|
||||
for _, endpoint := range multipartFormDataEndpoints {
|
||||
if endpoint == path {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// pingHandler is the handler for healthcheck.
|
||||
func pingHandler(c echo.Context) error {
|
||||
@@ -98,6 +138,7 @@ func urlHandler(c echo.Context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts.CustomHTTPHeaders = resource.RemoteURLCustomHTTPHeaders(r)
|
||||
if !r.HasArg(resource.RemoteURLArgKey) {
|
||||
return xerror.Invalid(
|
||||
op,
|
||||
@@ -282,20 +323,50 @@ func convertAsync(ctx context.Context, p printer.Printer, filename, fpath string
|
||||
defer f.Close() // nolint: errcheck
|
||||
logger.DebugfOp(
|
||||
op,
|
||||
"sending result file '%s' to '%s'",
|
||||
"preparing to send result file '%s' to '%s'...",
|
||||
filename,
|
||||
webhookURL,
|
||||
)
|
||||
httpClient := &http.Client{
|
||||
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).
|
||||
customHTTPHeaders := resource.WebhookURLCustomHTTPHeaders(r)
|
||||
if len(customHTTPHeaders) > 0 {
|
||||
for key, value := range customHTTPHeaders {
|
||||
req.Header.Set(key, value)
|
||||
logger.DebugfOp(op, "set '%s' to custom HTTP header '%s'", value, key)
|
||||
}
|
||||
} else {
|
||||
logger.DebugOp(op, "skipping custom HTTP headers as none have been provided...")
|
||||
}
|
||||
// send the result file.
|
||||
logger.DebugfOp(
|
||||
op,
|
||||
"sending result file '%s' to '%s'...",
|
||||
filename,
|
||||
webhookURL,
|
||||
)
|
||||
resp, err := httpClient.Do(req) /* #nosec */
|
||||
if err != nil {
|
||||
xerr := xerror.New(op, err)
|
||||
logger.ErrorOp(xerror.Op(xerr), xerr)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close() // nolint: errcheck
|
||||
logger.DebugfOp(
|
||||
op,
|
||||
"result file '%s' sent to '%s'",
|
||||
filename,
|
||||
webhookURL,
|
||||
)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -19,39 +19,51 @@ func TestPingHandler(t *testing.T) {
|
||||
// should return 200.
|
||||
config := conf.DefaultConfig()
|
||||
srv := New(config)
|
||||
req := httptest.NewRequest(http.MethodGet, pingEndpoint, nil)
|
||||
endpoint := pingEndpoint(config)
|
||||
req := httptest.NewRequest(http.MethodGet, endpoint, nil)
|
||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||
// should return 405 as Method is wrong.
|
||||
req = httptest.NewRequest(http.MethodPost, endpoint, nil)
|
||||
test.AssertStatusCode(t, http.StatusMethodNotAllowed, srv, req)
|
||||
}
|
||||
|
||||
func TestMergeHandler(t *testing.T) {
|
||||
config := conf.DefaultConfig()
|
||||
srv := New(config)
|
||||
endpoint := mergeEndpoint(config)
|
||||
// should return 200.
|
||||
body, contentType := test.MergeMultipartForm(t, nil)
|
||||
req := httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
|
||||
req := httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||
// should return 405 as Method is wrong.
|
||||
req = httptest.NewRequest(http.MethodGet, endpoint, nil)
|
||||
test.AssertStatusCode(t, http.StatusMethodNotAllowed, srv, req)
|
||||
// should return 415 as Content-Type is wrong.
|
||||
body, _ = test.MergeMultipartForm(t, nil)
|
||||
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||
test.AssertStatusCode(t, http.StatusUnsupportedMediaType, srv, req)
|
||||
// should return 400 as "waitTimeout" form field
|
||||
// value is < 0.
|
||||
body, contentType = test.MergeMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "-1"})
|
||||
req = httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
|
||||
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
||||
// should return 400 as "waitTimeout" form field
|
||||
// value is is > config.MaximumWaitTimeout().
|
||||
body, contentType = test.MergeMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "31"})
|
||||
req = httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
|
||||
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
||||
// should return 400 as "waitTimeout" form field
|
||||
// value is invalid.
|
||||
body, contentType = test.MergeMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "not a float"})
|
||||
req = httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
|
||||
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
||||
// should return 504.
|
||||
body, contentType = test.MergeMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "0"})
|
||||
req = httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
|
||||
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusGatewayTimeout, srv, req)
|
||||
}
|
||||
@@ -59,12 +71,19 @@ func TestMergeHandler(t *testing.T) {
|
||||
func TestHTMLHandler(t *testing.T) {
|
||||
config := conf.DefaultConfig()
|
||||
srv := New(config)
|
||||
endpoint := fmt.Sprintf("%s%s", convertGroupEndpoint, htmlEndpoint)
|
||||
endpoint := htmlEndpoint(config)
|
||||
// should return 200.
|
||||
body, contentType := test.HTMLMultipartForm(t, nil)
|
||||
req := httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||
// should return 405 as Method is wrong.
|
||||
req = httptest.NewRequest(http.MethodGet, endpoint, nil)
|
||||
test.AssertStatusCode(t, http.StatusMethodNotAllowed, srv, req)
|
||||
// should return 415 as Content-Type is wrong.
|
||||
body, _ = test.HTMLMultipartForm(t, nil)
|
||||
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||
test.AssertStatusCode(t, http.StatusUnsupportedMediaType, srv, req)
|
||||
// should return 400 as "waitTimeout" form field
|
||||
// value is < 0.
|
||||
body, contentType = test.HTMLMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "-1"})
|
||||
@@ -184,17 +203,42 @@ func TestHTMLHandler(t *testing.T) {
|
||||
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
||||
// should return 400 as "googleChromeRpccBufferSize" form field
|
||||
// value is < 0.
|
||||
body, contentType = test.HTMLMultipartForm(t, map[string]string{string(resource.GoogleChromeRpccBufferSizeArgKey): "-1"})
|
||||
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
||||
// should return 400 as "googleChromeRpccBufferSize" form field
|
||||
// value is is > config.MaximumGoogleChromeRpccBufferSize().
|
||||
body, contentType = test.HTMLMultipartForm(t, map[string]string{string(resource.GoogleChromeRpccBufferSizeArgKey): "104857601"})
|
||||
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
||||
// should return 400 as "googleChromeRpccBufferSize" form field
|
||||
// value is invalid.
|
||||
body, contentType = test.HTMLMultipartForm(t, map[string]string{string(resource.GoogleChromeRpccBufferSizeArgKey): "not an int"})
|
||||
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
||||
}
|
||||
|
||||
func TestURLHandler(t *testing.T) {
|
||||
config := conf.DefaultConfig()
|
||||
srv := New(config)
|
||||
endpoint := fmt.Sprintf("%s%s", convertGroupEndpoint, urlEndpoint)
|
||||
endpoint := urlEndpoint(config)
|
||||
// should return 200.
|
||||
body, contentType := test.URLMultipartForm(t, nil)
|
||||
req := httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||
// should return 405 as Method is wrong.
|
||||
req = httptest.NewRequest(http.MethodGet, endpoint, nil)
|
||||
test.AssertStatusCode(t, http.StatusMethodNotAllowed, srv, req)
|
||||
// should return 415 as Content-Type is wrong.
|
||||
body, _ = test.URLMultipartForm(t, nil)
|
||||
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||
test.AssertStatusCode(t, http.StatusUnsupportedMediaType, srv, req)
|
||||
// should return 400 as "waitTimeout" form field
|
||||
// value is < 0.
|
||||
body, contentType = test.URLMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "-1"})
|
||||
@@ -314,17 +358,42 @@ func TestURLHandler(t *testing.T) {
|
||||
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
||||
// should return 400 as "googleChromeRpccBufferSize" form field
|
||||
// value is < 0.
|
||||
body, contentType = test.URLMultipartForm(t, map[string]string{string(resource.GoogleChromeRpccBufferSizeArgKey): "-1"})
|
||||
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
||||
// should return 400 as "googleChromeRpccBufferSize" form field
|
||||
// value is is > config.MaximumGoogleChromeRpccBufferSize().
|
||||
body, contentType = test.URLMultipartForm(t, map[string]string{string(resource.GoogleChromeRpccBufferSizeArgKey): "104857601"})
|
||||
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
||||
// should return 400 as "googleChromeRpccBufferSize" form field
|
||||
// value is invalid.
|
||||
body, contentType = test.URLMultipartForm(t, map[string]string{string(resource.GoogleChromeRpccBufferSizeArgKey): "not an int"})
|
||||
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
||||
}
|
||||
|
||||
func TestMarkdownHandler(t *testing.T) {
|
||||
config := conf.DefaultConfig()
|
||||
srv := New(config)
|
||||
endpoint := fmt.Sprintf("%s%s", convertGroupEndpoint, markdownEndpoint)
|
||||
endpoint := markdownEndpoint(config)
|
||||
// should return 200.
|
||||
body, contentType := test.MarkdownMultipartForm(t, nil)
|
||||
req := httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||
// should return 405 as Method is wrong.
|
||||
req = httptest.NewRequest(http.MethodGet, endpoint, nil)
|
||||
test.AssertStatusCode(t, http.StatusMethodNotAllowed, srv, req)
|
||||
// should return 415 as Content-Type is wrong.
|
||||
body, _ = test.MarkdownMultipartForm(t, nil)
|
||||
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||
test.AssertStatusCode(t, http.StatusUnsupportedMediaType, srv, req)
|
||||
// should return 400 as "waitTimeout" form field
|
||||
// value is < 0.
|
||||
body, contentType = test.MarkdownMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "-1"})
|
||||
@@ -444,17 +513,42 @@ func TestMarkdownHandler(t *testing.T) {
|
||||
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
||||
// should return 400 as "googleChromeRpccBufferSize" form field
|
||||
// value is < 0.
|
||||
body, contentType = test.MarkdownMultipartForm(t, map[string]string{string(resource.GoogleChromeRpccBufferSizeArgKey): "-1"})
|
||||
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
||||
// should return 400 as "googleChromeRpccBufferSize" form field
|
||||
// value is is > config.MaximumGoogleChromeRpccBufferSize().
|
||||
body, contentType = test.MarkdownMultipartForm(t, map[string]string{string(resource.GoogleChromeRpccBufferSizeArgKey): "104857601"})
|
||||
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
||||
// should return 400 as "googleChromeRpccBufferSize" form field
|
||||
// value is invalid.
|
||||
body, contentType = test.MarkdownMultipartForm(t, map[string]string{string(resource.GoogleChromeRpccBufferSizeArgKey): "not an int"})
|
||||
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
||||
}
|
||||
|
||||
func TestOfficeHandler(t *testing.T) {
|
||||
config := conf.DefaultConfig()
|
||||
srv := New(config)
|
||||
endpoint := fmt.Sprintf("%s%s", convertGroupEndpoint, officeEndpoint)
|
||||
endpoint := officeEndpoint(config)
|
||||
// should return 200.
|
||||
body, contentType := test.OfficeMultipartForm(t, nil)
|
||||
req := httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||
// should return 405 as Method is wrong.
|
||||
req = httptest.NewRequest(http.MethodGet, endpoint, nil)
|
||||
test.AssertStatusCode(t, http.StatusMethodNotAllowed, srv, req)
|
||||
// should return 415 as Content-Type is wrong.
|
||||
body, _ = test.OfficeMultipartForm(t, nil)
|
||||
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||
test.AssertStatusCode(t, http.StatusUnsupportedMediaType, srv, req)
|
||||
// should return 400 as "waitTimeout" form field
|
||||
// value is < 0.
|
||||
body, contentType = test.OfficeMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "-1"})
|
||||
@@ -487,11 +581,18 @@ func TestOfficeHandler(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestWebhook(t *testing.T) {
|
||||
customHeaderRealKey := http.CanonicalHeaderKey("MyCustomHeader")
|
||||
customHeaderKey := fmt.Sprintf("%s%s", resource.WebhookURLCustomHTTPHeaderCanonicalBaseKey, customHeaderRealKey)
|
||||
customHeaderValue := "foo"
|
||||
status := make(chan error, 2)
|
||||
rcv := echo.New()
|
||||
rcv.POST("/foo", func(c echo.Context) error {
|
||||
if c.Request().Header.Get("Content-type") != "application/pdf" {
|
||||
status <- fmt.Errorf("wrong Content-type: got %s want %s", 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(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
|
||||
}
|
||||
body, err := ioutil.ReadAll(c.Request().Body)
|
||||
@@ -513,8 +614,9 @@ func TestWebhook(t *testing.T) {
|
||||
srv := New(config)
|
||||
// our custom server should receive the PDF.
|
||||
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(config), body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
req.Header.Set(customHeaderKey, customHeaderValue)
|
||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||
err := <-status
|
||||
assert.NoError(t, err)
|
||||
@@ -524,9 +626,9 @@ func TestResultFilename(t *testing.T) {
|
||||
config := conf.DefaultConfig()
|
||||
srv := New(config)
|
||||
body, contentType := test.MergeMultipartForm(t, map[string]string{string(resource.ResultFilenameArgKey): "foo.pdf"})
|
||||
req := httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
|
||||
req := httptest.NewRequest(http.MethodPost, mergeEndpoint(config), body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
rec := httptest.NewRecorder()
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package xhttp
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/thecodingmachine/gotenberg/internal/app/xhttp/pkg/context"
|
||||
@@ -25,12 +26,28 @@ func contextMiddleware(config conf.Config) echo.MiddlewareFunc {
|
||||
// extend the current echo context with our custom
|
||||
// context.
|
||||
ctx := context.New(c, logger, config)
|
||||
// if its an healthcheck request, there
|
||||
// is no need to create a Resource.
|
||||
if ctx.Path() == pingEndpoint {
|
||||
// if it's not a multipart/form-data request,
|
||||
// there is no need to create a Resource.
|
||||
if !isMultipartFormDataEndpoint(config, ctx.Path()) {
|
||||
// validate method for healthcheck endpoint.
|
||||
if ctx.Path() == pingEndpoint(config) && ctx.Request().Method != http.MethodGet {
|
||||
err := doErr(ctx, echo.NewHTTPError(http.StatusMethodNotAllowed))
|
||||
return ctx.LogRequestResult(err, false)
|
||||
}
|
||||
return next(ctx)
|
||||
}
|
||||
// if the endpoint is not for healthcheck, create a
|
||||
// validate method.
|
||||
if ctx.Request().Method != http.MethodPost {
|
||||
err := doErr(ctx, echo.NewHTTPError(http.StatusMethodNotAllowed))
|
||||
return ctx.LogRequestResult(err, false)
|
||||
}
|
||||
// validate Content-Type.
|
||||
contentType := ctx.Request().Header.Get("Content-Type")
|
||||
if !strings.Contains(contentType, "multipart/form-data") {
|
||||
err := doErr(ctx, echo.NewHTTPError(http.StatusUnsupportedMediaType))
|
||||
return ctx.LogRequestResult(err, false)
|
||||
}
|
||||
// it's a multipart/form-data request, create a
|
||||
// Resource.
|
||||
if err := ctx.WithResource(trace); err != nil {
|
||||
err = doCleanup(ctx, err)
|
||||
@@ -43,14 +60,14 @@ func contextMiddleware(config conf.Config) echo.MiddlewareFunc {
|
||||
}
|
||||
|
||||
// loggerMiddleware logs the result of a request.
|
||||
func loggerMiddleware() echo.MiddlewareFunc {
|
||||
func loggerMiddleware(config conf.Config) echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
ctx := context.MustCastFromEchoContext(c)
|
||||
err := next(ctx)
|
||||
// we do not want to log healthcheck requests if
|
||||
// log level is not set to DEBUG.
|
||||
isDebug := ctx.Path() == pingEndpoint
|
||||
isDebug := ctx.Path() == pingEndpoint(config)
|
||||
return ctx.LogRequestResult(err, isDebug)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,19 +52,25 @@ func chromePrinterOptions(r resource.Resource, config conf.Config) (printer.Chro
|
||||
if err != nil {
|
||||
return printer.ChromePrinterOptions{}, err
|
||||
}
|
||||
googleChromeRpccBufferSize, err := resource.GoogleChromeRpccBufferSizeArg(r, config)
|
||||
if err != nil {
|
||||
return printer.ChromePrinterOptions{}, err
|
||||
}
|
||||
return printer.ChromePrinterOptions{
|
||||
WaitTimeout: waitTimeout,
|
||||
WaitDelay: waitDelay,
|
||||
HeaderHTML: headerHTML,
|
||||
FooterHTML: footerHTML,
|
||||
PaperWidth: paperWidth,
|
||||
PaperHeight: paperHeight,
|
||||
MarginTop: marginTop,
|
||||
MarginBottom: marginBottom,
|
||||
MarginLeft: marginLeft,
|
||||
MarginRight: marginRight,
|
||||
Landscape: landscape,
|
||||
PageRanges: pageRanges,
|
||||
WaitTimeout: waitTimeout,
|
||||
WaitDelay: waitDelay,
|
||||
HeaderHTML: headerHTML,
|
||||
FooterHTML: footerHTML,
|
||||
PaperWidth: paperWidth,
|
||||
PaperHeight: paperHeight,
|
||||
MarginTop: marginTop,
|
||||
MarginBottom: marginBottom,
|
||||
MarginLeft: marginLeft,
|
||||
MarginRight: marginRight,
|
||||
Landscape: landscape,
|
||||
PageRanges: pageRanges,
|
||||
RpccBufferSize: googleChromeRpccBufferSize,
|
||||
CustomHTTPHeaders: make(map[string]string),
|
||||
}, nil
|
||||
}
|
||||
opts, err := resolver()
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/thecodingmachine/gotenberg/internal/app/xhttp/pkg/resource"
|
||||
"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/xlog"
|
||||
)
|
||||
@@ -79,6 +78,10 @@ func (ctx *Context) WithResource(directoryName string) error {
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
// retrieve custom headers from request.
|
||||
for key, value := range ctx.Request().Header {
|
||||
r.WithCustomHTTPHeader(key, value[0])
|
||||
}
|
||||
// retrieve form values from request.
|
||||
for _, key := range resource.ArgKeys() {
|
||||
r.WithArg(key, ctx.FormValue(string(key)))
|
||||
@@ -103,11 +106,7 @@ func (ctx *Context) WithResource(directoryName string) error {
|
||||
return r, err
|
||||
}
|
||||
defer in.Close() // nolint: errcheck
|
||||
filename, err := normalize.String(fh.Filename)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
if err := r.WithFile(filename, in); err != nil {
|
||||
if err := r.WithFile(fh.Filename, in); err != nil {
|
||||
return r, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,9 @@ const (
|
||||
// PageRangesArgKey is the key
|
||||
// of the argument "pageRanges".
|
||||
PageRangesArgKey ArgKey = "pageRanges"
|
||||
// GoogleChromeRpccBufferSizeArgKey is the key
|
||||
// of the argument "googleChromeRpccBufferSize".
|
||||
GoogleChromeRpccBufferSizeArgKey ArgKey = "googleChromeRpccBufferSize"
|
||||
)
|
||||
|
||||
/*
|
||||
@@ -77,6 +80,7 @@ func ArgKeys() []ArgKey {
|
||||
MarginRightArgKey,
|
||||
LandscapeArgKey,
|
||||
PageRangesArgKey,
|
||||
GoogleChromeRpccBufferSizeArgKey,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,3 +273,24 @@ func MarginArgs(r Resource, config conf.Config) (float64, float64, float64, floa
|
||||
marginRight,
|
||||
nil
|
||||
}
|
||||
|
||||
/*
|
||||
GoogleChromeRpccBufferSizeArg is a helper for retrieving
|
||||
the "googleChromeRpccBufferSize" argument as int64.
|
||||
|
||||
It also validates it against the application
|
||||
configuration.
|
||||
*/
|
||||
func GoogleChromeRpccBufferSizeArg(r Resource, config conf.Config) (int64, error) {
|
||||
const op string = "resource.GoogleChromeRpccBufferSizeArg"
|
||||
result, err := r.Int64Arg(
|
||||
GoogleChromeRpccBufferSizeArgKey,
|
||||
config.DefaultGoogleChromeRpccBufferSize(),
|
||||
xassert.Int64NotInferiorTo(0.0),
|
||||
xassert.Int64NotSuperiorTo(config.MaximumGoogleChromeRpccBufferSize()),
|
||||
)
|
||||
if err != nil {
|
||||
return result, xerror.New(op, err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ func TestArgKeys(t *testing.T) {
|
||||
MarginRightArgKey,
|
||||
LandscapeArgKey,
|
||||
PageRangesArgKey,
|
||||
GoogleChromeRpccBufferSizeArgKey,
|
||||
}
|
||||
assert.Equal(t, expected, ArgKeys())
|
||||
}
|
||||
@@ -301,3 +302,47 @@ func TestMarginArgs(t *testing.T) {
|
||||
err = r.Close()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestGoogleChromeRpccBufferSizeArg(t *testing.T) {
|
||||
const resourceDirectoryName string = "foo"
|
||||
var expected int64
|
||||
logger := test.DebugLogger()
|
||||
config := conf.DefaultConfig()
|
||||
r, err := New(logger, resourceDirectoryName)
|
||||
assert.Nil(t, err)
|
||||
// argument does not exist.
|
||||
expected = config.DefaultGoogleChromeRpccBufferSize()
|
||||
v, err := GoogleChromeRpccBufferSizeArg(r, config)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, v)
|
||||
// argument exist.
|
||||
expected = 10
|
||||
r.WithArg(GoogleChromeRpccBufferSizeArgKey, "10")
|
||||
v, err = GoogleChromeRpccBufferSizeArg(r, config)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, v)
|
||||
// should not be OK as argument
|
||||
// value is < 0.
|
||||
expected = config.DefaultGoogleChromeRpccBufferSize()
|
||||
r.WithArg(GoogleChromeRpccBufferSizeArgKey, "-1")
|
||||
v, err = GoogleChromeRpccBufferSizeArg(r, config)
|
||||
test.AssertError(t, err)
|
||||
assert.Equal(t, expected, v)
|
||||
// should not be OK as argument
|
||||
// value is > config.MaximumGoogleChromeRpccBufferSize().
|
||||
expected = config.DefaultGoogleChromeRpccBufferSize()
|
||||
r.WithArg(GoogleChromeRpccBufferSizeArgKey, "104857601")
|
||||
v, err = GoogleChromeRpccBufferSizeArg(r, config)
|
||||
test.AssertError(t, err)
|
||||
assert.Equal(t, expected, v)
|
||||
// should not be OK as
|
||||
// argument value is invalid.
|
||||
expected = config.DefaultGoogleChromeRpccBufferSize()
|
||||
r.WithArg(GoogleChromeRpccBufferSizeArgKey, "foo")
|
||||
v, err = GoogleChromeRpccBufferSizeArg(r, config)
|
||||
test.AssertError(t, err)
|
||||
assert.Equal(t, expected, v)
|
||||
// finally...
|
||||
err = r.Close()
|
||||
assert.Nil(t, 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 (
|
||||
// RemoteURLCustomHTTPHeaderCanonicalBaseKey is the base key
|
||||
// of custom headers send to the remote URL.
|
||||
RemoteURLCustomHTTPHeaderCanonicalBaseKey string = "Gotenberg-Remoteurl-"
|
||||
// WebhookURLCustomHTTPHeaderCanonicalBaseKey is the base key
|
||||
// of custom headers send to the webhook URL.
|
||||
WebhookURLCustomHTTPHeaderCanonicalBaseKey string = "Gotenberg-Webhookurl-"
|
||||
)
|
||||
|
||||
func fetchCustomHTTPHeaders(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
|
||||
}
|
||||
|
||||
// RemoteURLCustomHTTPHeaders is a helper for retrieving
|
||||
// the custom headers for the URL conversion.
|
||||
func RemoteURLCustomHTTPHeaders(r Resource) map[string]string {
|
||||
return fetchCustomHTTPHeaders(r, RemoteURLCustomHTTPHeaderCanonicalBaseKey)
|
||||
}
|
||||
|
||||
// WebhookURLCustomHTTPHeaders is a helper for retrieving
|
||||
// the custom headers for the webhook URL.
|
||||
func WebhookURLCustomHTTPHeaders(r Resource) map[string]string {
|
||||
return fetchCustomHTTPHeaders(r, WebhookURLCustomHTTPHeaderCanonicalBaseKey)
|
||||
}
|
||||
54
internal/app/xhttp/pkg/resource/header_test.go
Normal file
54
internal/app/xhttp/pkg/resource/header_test.go
Normal file
@@ -0,0 +1,54 @@
|
||||
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", RemoteURLCustomHTTPHeaderCanonicalBaseKey, customHeaderCanonicalRealKey))
|
||||
r.WithCustomHTTPHeader(customHeaderCanonicalKey, customHeaderValue)
|
||||
r.WithCustomHTTPHeader("Bar", "Bar")
|
||||
expected := map[string]string{
|
||||
customHeaderCanonicalRealKey: customHeaderValue,
|
||||
}
|
||||
notExpected := map[string]string{
|
||||
customHeaderCanonicalKey: customHeaderValue,
|
||||
}
|
||||
v := RemoteURLCustomHTTPHeaders(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", WebhookURLCustomHTTPHeaderCanonicalBaseKey, customHeaderCanonicalRealKey))
|
||||
r.WithCustomHTTPHeader(customHeaderCanonicalKey, customHeaderValue)
|
||||
r.WithCustomHTTPHeader("Bar", "Bar")
|
||||
expected := map[string]string{
|
||||
customHeaderCanonicalRealKey: customHeaderValue,
|
||||
}
|
||||
notExpected := map[string]string{
|
||||
customHeaderCanonicalKey: customHeaderValue,
|
||||
}
|
||||
v := WebhookURLCustomHTTPHeaders(r)
|
||||
assert.Equal(t, expected, v)
|
||||
assert.NotEqual(t, notExpected, v)
|
||||
}
|
||||
@@ -3,9 +3,12 @@ package resource
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/normalize"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xassert"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xerror"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xlog"
|
||||
@@ -21,10 +24,11 @@ const TemporaryDirectory string = "tmp"
|
||||
// Resource helps managing
|
||||
// arguments and files for a conversion.
|
||||
type Resource struct {
|
||||
logger xlog.Logger
|
||||
dirPath string
|
||||
args map[ArgKey]string
|
||||
files map[string]file
|
||||
logger xlog.Logger
|
||||
dirPath string
|
||||
customHeaders map[string]string
|
||||
args map[ArgKey]string
|
||||
files map[string]file
|
||||
}
|
||||
|
||||
// New creates a Resource where its files will
|
||||
@@ -48,10 +52,11 @@ func New(logger xlog.Logger, directoryName string) (Resource, error) {
|
||||
}
|
||||
logger.DebugfOp(op, "resource directory '%s' created", directoryName)
|
||||
return Resource{
|
||||
logger: logger,
|
||||
dirPath: dirPath,
|
||||
args: make(map[ArgKey]string),
|
||||
files: make(map[string]file),
|
||||
logger: logger,
|
||||
dirPath: dirPath,
|
||||
customHeaders: make(map[string]string),
|
||||
args: make(map[ArgKey]string),
|
||||
files: make(map[string]file),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -70,6 +75,21 @@ func (r Resource) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithCustomHTTPHeader add a new custom header to the Resource.
|
||||
// Given key should be in canonical format.
|
||||
func (r *Resource) WithCustomHTTPHeader(key string, value string) {
|
||||
const op string = "resource.Resource.WithCustomHTTPHeader"
|
||||
// should already be in canonical format.
|
||||
canonicalKey := http.CanonicalHeaderKey(key)
|
||||
if strings.Contains(canonicalKey, RemoteURLCustomHTTPHeaderCanonicalBaseKey) ||
|
||||
strings.Contains(canonicalKey, WebhookURLCustomHTTPHeaderCanonicalBaseKey) {
|
||||
r.customHeaders[canonicalKey] = value
|
||||
r.logger.DebugfOp(op, "added '%s' with value '%s' to resource custom HTTP headers", canonicalKey, value)
|
||||
return
|
||||
}
|
||||
r.logger.DebugfOp(op, "skipping '%s' as it is not a custom HTTP header...", canonicalKey)
|
||||
}
|
||||
|
||||
// WithArg add a new argument to the Resource.
|
||||
func (r *Resource) WithArg(key ArgKey, value string) {
|
||||
const op string = "resource.Resource.WithArg"
|
||||
@@ -80,13 +100,24 @@ func (r *Resource) WithArg(key ArgKey, value string) {
|
||||
// WithFile add a new file to the Resource.
|
||||
func (r *Resource) WithFile(filename string, in io.Reader) error {
|
||||
const op string = "resource.Resource.WithFile"
|
||||
fpath := fmt.Sprintf("%s/%s", r.dirPath, filename)
|
||||
file := file{fpath: fpath}
|
||||
if err := file.write(in); err != nil {
|
||||
resolver := func() error {
|
||||
// see https://github.com/thecodingmachine/gotenberg/issues/104.
|
||||
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)
|
||||
}
|
||||
r.files[filename] = file
|
||||
r.logger.DebugfOp(op, "resource file '%s' created", filename)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -11,22 +11,21 @@ func New(config conf.Config) *echo.Echo {
|
||||
srv.HideBanner = true
|
||||
srv.HidePort = true
|
||||
srv.Use(contextMiddleware(config))
|
||||
srv.Use(loggerMiddleware())
|
||||
srv.Use(loggerMiddleware(config))
|
||||
srv.Use(cleanupMiddleware())
|
||||
srv.Use(errorMiddleware())
|
||||
srv.GET(pingEndpoint, pingHandler)
|
||||
srv.POST(mergeEndpoint, mergeHandler)
|
||||
srv.GET(pingEndpoint(config), pingHandler)
|
||||
srv.POST(mergeEndpoint(config), mergeHandler)
|
||||
if config.DisableGoogleChrome() && config.DisableUnoconv() {
|
||||
return srv
|
||||
}
|
||||
g := srv.Group(convertGroupEndpoint)
|
||||
if !config.DisableGoogleChrome() {
|
||||
g.POST(htmlEndpoint, htmlHandler)
|
||||
g.POST(urlEndpoint, urlHandler)
|
||||
g.POST(markdownEndpoint, markdownHandler)
|
||||
srv.POST(htmlEndpoint(config), htmlHandler)
|
||||
srv.POST(urlEndpoint(config), urlHandler)
|
||||
srv.POST(markdownEndpoint(config), markdownHandler)
|
||||
}
|
||||
if !config.DisableUnoconv() {
|
||||
g.POST(officeEndpoint, officeHandler)
|
||||
srv.POST(officeEndpoint(config), officeHandler)
|
||||
}
|
||||
return srv
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package xhttp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
@@ -13,37 +12,46 @@ import (
|
||||
"github.com/thecodingmachine/gotenberg/test"
|
||||
)
|
||||
|
||||
func TestNonExistingEndpoint(t *testing.T) {
|
||||
config, err := conf.FromEnv()
|
||||
assert.Nil(t, err)
|
||||
srv := New(config)
|
||||
// "/" endpoint should return 404.
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
|
||||
}
|
||||
|
||||
func TestDisableChromeEndpoints(t *testing.T) {
|
||||
os.Setenv(conf.DisableGoogleChromeEnvVar, "1")
|
||||
config, err := conf.FromEnv()
|
||||
assert.Nil(t, err)
|
||||
srv := New(config)
|
||||
// Ping endpoint should return 200.
|
||||
req := httptest.NewRequest(http.MethodGet, pingEndpoint, nil)
|
||||
req := httptest.NewRequest(http.MethodGet, pingEndpoint(config), nil)
|
||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||
// Merge endpoint should return 200.
|
||||
body, contentType := test.MergeMultipartForm(t, nil)
|
||||
req = httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
|
||||
req = httptest.NewRequest(http.MethodPost, mergeEndpoint(config), body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||
// HTML endpoint should return 404.
|
||||
body, contentType = test.HTMLMultipartForm(t, nil)
|
||||
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, htmlEndpoint), body)
|
||||
req = httptest.NewRequest(http.MethodPost, htmlEndpoint(config), body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
|
||||
// URL endpoint should return 404.
|
||||
body, contentType = test.URLMultipartForm(t, nil)
|
||||
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, urlEndpoint), body)
|
||||
req = httptest.NewRequest(http.MethodPost, urlEndpoint(config), body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
|
||||
// Markdown endpoint should return 404.
|
||||
body, contentType = test.MarkdownMultipartForm(t, nil)
|
||||
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, markdownEndpoint), body)
|
||||
req = httptest.NewRequest(http.MethodPost, markdownEndpoint(config), body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
|
||||
// Office endpoint should return 200.
|
||||
body, contentType = test.OfficeMultipartForm(t, nil)
|
||||
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, officeEndpoint), body)
|
||||
req = httptest.NewRequest(http.MethodPost, officeEndpoint(config), body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||
// finally...
|
||||
@@ -56,31 +64,31 @@ func TestDisableUnoconvEndpoints(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
srv := New(config)
|
||||
// Ping endpoint should return 200.
|
||||
req := httptest.NewRequest(http.MethodGet, pingEndpoint, nil)
|
||||
req := httptest.NewRequest(http.MethodGet, pingEndpoint(config), nil)
|
||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||
// Merge endpoint should return 200.
|
||||
body, contentType := test.MergeMultipartForm(t, nil)
|
||||
req = httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
|
||||
req = httptest.NewRequest(http.MethodPost, mergeEndpoint(config), body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||
// HTML endpoint should return 200.
|
||||
body, contentType = test.HTMLMultipartForm(t, nil)
|
||||
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, htmlEndpoint), body)
|
||||
req = httptest.NewRequest(http.MethodPost, htmlEndpoint(config), body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||
// URL endpoint should return 200.
|
||||
body, contentType = test.URLMultipartForm(t, nil)
|
||||
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, urlEndpoint), body)
|
||||
req = httptest.NewRequest(http.MethodPost, urlEndpoint(config), body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||
// Markdown endpoint should return 404.
|
||||
// Markdown endpoint should return 200.
|
||||
body, contentType = test.MarkdownMultipartForm(t, nil)
|
||||
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, markdownEndpoint), body)
|
||||
req = httptest.NewRequest(http.MethodPost, markdownEndpoint(config), body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||
// Office endpoint should return 404.
|
||||
body, contentType = test.OfficeMultipartForm(t, nil)
|
||||
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, officeEndpoint), body)
|
||||
req = httptest.NewRequest(http.MethodPost, officeEndpoint(config), body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
|
||||
// finally...
|
||||
@@ -93,34 +101,71 @@ func TestDisableChromeAndUnoconvEndpoints(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
srv := New(config)
|
||||
// Ping endpoint should return 200.
|
||||
req := httptest.NewRequest(http.MethodGet, pingEndpoint, nil)
|
||||
req := httptest.NewRequest(http.MethodGet, pingEndpoint(config), nil)
|
||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||
// Merge endpoint should return 200.
|
||||
body, contentType := test.MergeMultipartForm(t, nil)
|
||||
req = httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
|
||||
req = httptest.NewRequest(http.MethodPost, mergeEndpoint(config), body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||
// HTML endpoint should return 404.
|
||||
body, contentType = test.HTMLMultipartForm(t, nil)
|
||||
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, htmlEndpoint), body)
|
||||
req = httptest.NewRequest(http.MethodPost, htmlEndpoint(config), body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
|
||||
// URL endpoint should return 404.
|
||||
body, contentType = test.URLMultipartForm(t, nil)
|
||||
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, urlEndpoint), body)
|
||||
req = httptest.NewRequest(http.MethodPost, urlEndpoint(config), body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
|
||||
// Markdown endpoint should return 404.
|
||||
body, contentType = test.MarkdownMultipartForm(t, nil)
|
||||
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, markdownEndpoint), body)
|
||||
req = httptest.NewRequest(http.MethodPost, markdownEndpoint(config), body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
|
||||
// Office endpoint should return 404.
|
||||
body, contentType = test.OfficeMultipartForm(t, nil)
|
||||
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, officeEndpoint), body)
|
||||
req = httptest.NewRequest(http.MethodPost, officeEndpoint(config), body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
|
||||
// finally...
|
||||
os.Setenv(conf.DisableGoogleChromeEnvVar, "0")
|
||||
os.Setenv(conf.DisableUnoconvEnvVar, "0")
|
||||
}
|
||||
|
||||
func TestCustomRootPath(t *testing.T) {
|
||||
os.Setenv(conf.RootPathEnvVar, "/foo/")
|
||||
config, err := conf.FromEnv()
|
||||
assert.Nil(t, err)
|
||||
srv := New(config)
|
||||
// Ping endpoint should return 200.
|
||||
req := httptest.NewRequest(http.MethodGet, pingEndpoint(config), nil)
|
||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||
// Merge endpoint should return 200.
|
||||
body, contentType := test.MergeMultipartForm(t, nil)
|
||||
req = httptest.NewRequest(http.MethodPost, mergeEndpoint(config), body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||
// HTML endpoint should return 200.
|
||||
body, contentType = test.HTMLMultipartForm(t, nil)
|
||||
req = httptest.NewRequest(http.MethodPost, htmlEndpoint(config), body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||
// URL endpoint should return 200.
|
||||
body, contentType = test.URLMultipartForm(t, nil)
|
||||
req = httptest.NewRequest(http.MethodPost, urlEndpoint(config), body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||
// Markdown endpoint should return 200.
|
||||
body, contentType = test.MarkdownMultipartForm(t, nil)
|
||||
req = httptest.NewRequest(http.MethodPost, markdownEndpoint(config), body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||
// Office endpoint should return 200.
|
||||
body, contentType = test.OfficeMultipartForm(t, nil)
|
||||
req = httptest.NewRequest(http.MethodPost, officeEndpoint(config), body)
|
||||
req.Header.Set(echo.HeaderContentType, contentType)
|
||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||
// finally...
|
||||
os.Setenv(conf.RootPathEnvVar, "/")
|
||||
}
|
||||
|
||||
@@ -34,35 +34,47 @@ const (
|
||||
// LogLevelEnvVar contains the name
|
||||
// of the environment variable "LOG_LEVEL".
|
||||
LogLevelEnvVar string = "LOG_LEVEL"
|
||||
// RootPathEnvVar contains the name
|
||||
// of the environment variable "ROOT_PATH".
|
||||
RootPathEnvVar string = "ROOT_PATH"
|
||||
// DefaultGoogleChromeRpccBufferSizeEnvVar contains the name
|
||||
// of the environment variable "DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE".
|
||||
DefaultGoogleChromeRpccBufferSizeEnvVar string = "DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE"
|
||||
)
|
||||
|
||||
// Config contains the application
|
||||
// configuration.
|
||||
type Config struct {
|
||||
maximumWaitTimeout float64
|
||||
maximumWaitDelay float64
|
||||
maximumWebhookURLTimeout float64
|
||||
defaultWaitTimeout float64
|
||||
defaultWebhookURLTimeout float64
|
||||
defaultListenPort int64
|
||||
disableGoogleChrome bool
|
||||
disableUnoconv bool
|
||||
logLevel xlog.Level
|
||||
maximumWaitTimeout float64
|
||||
maximumWaitDelay float64
|
||||
maximumWebhookURLTimeout float64
|
||||
defaultWaitTimeout float64
|
||||
defaultWebhookURLTimeout float64
|
||||
defaultListenPort int64
|
||||
disableGoogleChrome bool
|
||||
disableUnoconv bool
|
||||
logLevel xlog.Level
|
||||
rootPath string
|
||||
maximumGoogleChromeRpccBufferSize int64
|
||||
defaultGoogleChromeRpccBufferSize int64
|
||||
}
|
||||
|
||||
// DefaultConfig returns the default
|
||||
// configuration.
|
||||
func DefaultConfig() Config {
|
||||
return Config{
|
||||
maximumWaitTimeout: 30.0,
|
||||
maximumWaitDelay: 10.0,
|
||||
maximumWebhookURLTimeout: 30.0,
|
||||
defaultWaitTimeout: 10.0,
|
||||
defaultWebhookURLTimeout: 10.0,
|
||||
defaultListenPort: 3000,
|
||||
disableGoogleChrome: false,
|
||||
disableUnoconv: false,
|
||||
logLevel: xlog.InfoLevel,
|
||||
maximumWaitTimeout: 30.0,
|
||||
maximumWaitDelay: 10.0,
|
||||
maximumWebhookURLTimeout: 30.0,
|
||||
defaultWaitTimeout: 10.0,
|
||||
defaultWebhookURLTimeout: 10.0,
|
||||
defaultListenPort: 3000,
|
||||
disableGoogleChrome: false,
|
||||
disableUnoconv: false,
|
||||
logLevel: xlog.InfoLevel,
|
||||
rootPath: "/",
|
||||
maximumGoogleChromeRpccBufferSize: 104857600, // ~100 MB
|
||||
defaultGoogleChromeRpccBufferSize: 1048576, // 1 MB
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,6 +168,26 @@ func FromEnv() (Config, error) {
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
rootPath, err := xassert.StringFromEnv(
|
||||
RootPathEnvVar,
|
||||
c.rootPath,
|
||||
xassert.StringStartWith("/"),
|
||||
xassert.StringEndWith("/"),
|
||||
)
|
||||
c.rootPath = rootPath
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
defaultGoogleChromeRpccBufferSize, err := xassert.Int64FromEnv(
|
||||
DefaultGoogleChromeRpccBufferSizeEnvVar,
|
||||
c.defaultGoogleChromeRpccBufferSize,
|
||||
xassert.Int64NotInferiorTo(0),
|
||||
xassert.Int64NotSuperiorTo(c.MaximumGoogleChromeRpccBufferSize()),
|
||||
)
|
||||
c.defaultGoogleChromeRpccBufferSize = defaultGoogleChromeRpccBufferSize
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
result, err := resolver()
|
||||
@@ -224,3 +256,21 @@ func (c Config) DisableUnoconv() bool {
|
||||
func (c Config) LogLevel() xlog.Level {
|
||||
return c.logLevel
|
||||
}
|
||||
|
||||
// RootPath returns the rooth path from
|
||||
// the configuration.
|
||||
func (c Config) RootPath() string {
|
||||
return c.rootPath
|
||||
}
|
||||
|
||||
// MaximumGoogleChromeRpccBufferSize returns the maximum
|
||||
// Google Chrome rpcc buffer size from the configuration.
|
||||
func (c Config) MaximumGoogleChromeRpccBufferSize() int64 {
|
||||
return c.maximumGoogleChromeRpccBufferSize
|
||||
}
|
||||
|
||||
// DefaultGoogleChromeRpccBufferSize returns the default
|
||||
// Google Chrome rpcc buffer size from the configuration.
|
||||
func (c Config) DefaultGoogleChromeRpccBufferSize() int64 {
|
||||
return c.defaultGoogleChromeRpccBufferSize
|
||||
}
|
||||
|
||||
@@ -320,6 +320,66 @@ func TestLogLevelFromEnv(t *testing.T) {
|
||||
os.Unsetenv(LogLevelEnvVar)
|
||||
}
|
||||
|
||||
func TestRootPathFromEnv(t *testing.T) {
|
||||
var (
|
||||
expected Config
|
||||
result Config
|
||||
err error
|
||||
)
|
||||
// ROOT_PATH correctly set.
|
||||
os.Setenv(RootPathEnvVar, "/foo/")
|
||||
expected = DefaultConfig()
|
||||
expected.rootPath = "/foo/"
|
||||
result, err = FromEnv()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, result)
|
||||
os.Unsetenv(RootPathEnvVar)
|
||||
// ROOT_PATH wrongly set.
|
||||
os.Setenv(RootPathEnvVar, "foo")
|
||||
expected = DefaultConfig()
|
||||
result, err = FromEnv()
|
||||
test.AssertError(t, err)
|
||||
assert.Equal(t, expected, result)
|
||||
os.Unsetenv(RootPathEnvVar)
|
||||
}
|
||||
|
||||
func TestDefaultGoogleChromeRpccBufferSizeFromEnv(t *testing.T) {
|
||||
var (
|
||||
expected Config
|
||||
result Config
|
||||
err error
|
||||
)
|
||||
// DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE correctly set.
|
||||
os.Setenv(DefaultGoogleChromeRpccBufferSizeEnvVar, "100")
|
||||
expected = DefaultConfig()
|
||||
expected.defaultGoogleChromeRpccBufferSize = 100
|
||||
result, err = FromEnv()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, result)
|
||||
os.Unsetenv(DefaultGoogleChromeRpccBufferSizeEnvVar)
|
||||
// DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE wrongly set.
|
||||
os.Setenv(DefaultGoogleChromeRpccBufferSizeEnvVar, "foo")
|
||||
expected = DefaultConfig()
|
||||
result, err = FromEnv()
|
||||
test.AssertError(t, err)
|
||||
assert.Equal(t, expected, result)
|
||||
os.Unsetenv(DefaultGoogleChromeRpccBufferSizeEnvVar)
|
||||
// DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE < 0.
|
||||
os.Setenv(DefaultGoogleChromeRpccBufferSizeEnvVar, "-1")
|
||||
expected = DefaultConfig()
|
||||
result, err = FromEnv()
|
||||
test.AssertError(t, err)
|
||||
assert.Equal(t, expected, result)
|
||||
os.Unsetenv(DefaultGoogleChromeRpccBufferSizeEnvVar)
|
||||
// DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE > 100 MB (maximumGoogleChromeRpccBufferSize).
|
||||
os.Setenv(DefaultGoogleChromeRpccBufferSizeEnvVar, "104857601")
|
||||
expected = DefaultConfig()
|
||||
result, err = FromEnv()
|
||||
test.AssertError(t, err)
|
||||
assert.Equal(t, expected, result)
|
||||
os.Unsetenv(DefaultGoogleChromeRpccBufferSizeEnvVar)
|
||||
}
|
||||
|
||||
func TestGetters(t *testing.T) {
|
||||
result := DefaultConfig()
|
||||
assert.Equal(t, result.maximumWaitTimeout, result.MaximumWaitTimeout())
|
||||
@@ -331,4 +391,7 @@ func TestGetters(t *testing.T) {
|
||||
assert.Equal(t, result.disableGoogleChrome, result.DisableGoogleChrome())
|
||||
assert.Equal(t, result.disableUnoconv, result.DisableUnoconv())
|
||||
assert.Equal(t, result.logLevel, result.LogLevel())
|
||||
assert.Equal(t, result.rootPath, result.RootPath())
|
||||
assert.Equal(t, result.maximumGoogleChromeRpccBufferSize, result.MaximumGoogleChromeRpccBufferSize())
|
||||
assert.Equal(t, result.defaultGoogleChromeRpccBufferSize, result.DefaultGoogleChromeRpccBufferSize())
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package printer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
@@ -30,18 +31,20 @@ type chromePrinter struct {
|
||||
// ChromePrinterOptions helps customizing the
|
||||
// Google Chrome Printer behaviour.
|
||||
type ChromePrinterOptions struct {
|
||||
WaitTimeout float64
|
||||
WaitDelay float64
|
||||
HeaderHTML string
|
||||
FooterHTML string
|
||||
PaperWidth float64
|
||||
PaperHeight float64
|
||||
MarginTop float64
|
||||
MarginBottom float64
|
||||
MarginLeft float64
|
||||
MarginRight float64
|
||||
Landscape bool
|
||||
PageRanges string
|
||||
WaitTimeout float64
|
||||
WaitDelay float64
|
||||
HeaderHTML string
|
||||
FooterHTML string
|
||||
PaperWidth float64
|
||||
PaperHeight float64
|
||||
MarginTop float64
|
||||
MarginBottom float64
|
||||
MarginLeft float64
|
||||
MarginRight float64
|
||||
Landscape bool
|
||||
PageRanges string
|
||||
RpccBufferSize int64
|
||||
CustomHTTPHeaders map[string]string
|
||||
}
|
||||
|
||||
// DefaultChromePrinterOptions returns the default
|
||||
@@ -49,18 +52,20 @@ type ChromePrinterOptions struct {
|
||||
func DefaultChromePrinterOptions(config conf.Config) ChromePrinterOptions {
|
||||
const defaultHeaderFooterHTML string = "<html><head></head><body></body></html>"
|
||||
return ChromePrinterOptions{
|
||||
WaitTimeout: config.DefaultWaitTimeout(),
|
||||
WaitDelay: 0.0,
|
||||
HeaderHTML: defaultHeaderFooterHTML,
|
||||
FooterHTML: defaultHeaderFooterHTML,
|
||||
PaperWidth: 8.27,
|
||||
PaperHeight: 11.7,
|
||||
MarginTop: 1.0,
|
||||
MarginBottom: 1.0,
|
||||
MarginLeft: 1.0,
|
||||
MarginRight: 1.0,
|
||||
Landscape: false,
|
||||
PageRanges: "",
|
||||
WaitTimeout: config.DefaultWaitTimeout(),
|
||||
WaitDelay: 0.0,
|
||||
HeaderHTML: defaultHeaderFooterHTML,
|
||||
FooterHTML: defaultHeaderFooterHTML,
|
||||
PaperWidth: 8.27,
|
||||
PaperHeight: 11.7,
|
||||
MarginTop: 1.0,
|
||||
MarginBottom: 1.0,
|
||||
MarginLeft: 1.0,
|
||||
MarginRight: 1.0,
|
||||
Landscape: false,
|
||||
PageRanges: "",
|
||||
RpccBufferSize: config.DefaultGoogleChromeRpccBufferSize(),
|
||||
CustomHTTPHeaders: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +118,18 @@ func (p chromePrinter) Print(destination string) error {
|
||||
}
|
||||
// connect the client to the new target.
|
||||
newTargetWsURL := fmt.Sprintf("ws://127.0.0.1:9222/devtools/page/%s", newTarget.TargetID)
|
||||
newContextConn, err := rpcc.DialContext(ctx, newTargetWsURL)
|
||||
newContextConn, err := rpcc.DialContext(
|
||||
ctx,
|
||||
newTargetWsURL,
|
||||
/*
|
||||
see:
|
||||
https://github.com/thecodingmachine/gotenberg/issues/108
|
||||
https://github.com/mafredri/cdp/issues/4
|
||||
https://github.com/ChromeDevTools/devtools-protocol/issues/24
|
||||
*/
|
||||
rpcc.WithWriteBufferSize(int(p.opts.RpccBufferSize)),
|
||||
rpcc.WithCompression(),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -133,6 +149,10 @@ func (p chromePrinter) Print(destination string) error {
|
||||
if err := p.enableEvents(ctx, targetClient); err != nil {
|
||||
return err
|
||||
}
|
||||
// add custom headers (if any).
|
||||
if err := p.setCustomHTTPHeaders(ctx, targetClient); err != nil {
|
||||
return err
|
||||
}
|
||||
// listen for all events.
|
||||
if err := p.listenEvents(ctx, targetClient); err != nil {
|
||||
return err
|
||||
@@ -166,7 +186,7 @@ func (p chromePrinter) Print(destination string) error {
|
||||
printToPdfArgs,
|
||||
)
|
||||
if err != nil {
|
||||
// TODO: find a way to check it in the handlers.
|
||||
// find a way to check it in the handlers?
|
||||
if strings.Contains(err.Error(), "Page range syntax error") {
|
||||
return xerror.Invalid(
|
||||
op,
|
||||
@@ -174,6 +194,16 @@ func (p chromePrinter) Print(destination string) error {
|
||||
err,
|
||||
)
|
||||
}
|
||||
if strings.Contains(err.Error(), "rpcc: message too large") {
|
||||
return xerror.Invalid(
|
||||
op,
|
||||
fmt.Sprintf(
|
||||
"'%d' bytes are not enough: increase the Google Chrome rpcc buffer size (up to 100 MB)",
|
||||
p.opts.RpccBufferSize,
|
||||
),
|
||||
err,
|
||||
)
|
||||
}
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(destination, print.Data, 0644); err != nil {
|
||||
@@ -238,6 +268,32 @@ func (p chromePrinter) enableEvents(ctx context.Context, client *cdp.Client) err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p chromePrinter) setCustomHTTPHeaders(ctx context.Context, client *cdp.Client) error {
|
||||
const op string = "printer.chromePrinter.setCustomHTTPHeaders"
|
||||
resolver := func() error {
|
||||
if len(p.opts.CustomHTTPHeaders) == 0 {
|
||||
p.logger.DebugOp(op, "skipping custom HTTP headers as none have been provided...")
|
||||
return nil
|
||||
}
|
||||
customHTTPHeaders := make(map[string]string)
|
||||
// useless but for the logs.
|
||||
for key, value := range p.opts.CustomHTTPHeaders {
|
||||
customHTTPHeaders[key] = value
|
||||
p.logger.DebugfOp(op, "set '%s' to custom HTTP header '%s'", value, key)
|
||||
}
|
||||
b, err := json.Marshal(customHTTPHeaders)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// should always be called after client.Network.Enable.
|
||||
return client.Network.SetExtraHTTPHeaders(ctx, network.NewSetExtraHTTPHeadersArgs(b))
|
||||
}
|
||||
if err := resolver(); err != nil {
|
||||
return xerror.New(op, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p chromePrinter) listenEvents(ctx context.Context, client *cdp.Client) error {
|
||||
const op string = "printer.chromePrinter.listenEvents"
|
||||
resolver := func() error {
|
||||
|
||||
@@ -2,6 +2,7 @@ package printer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/conf"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xcontext"
|
||||
@@ -54,6 +55,8 @@ func (p mergePrinter) Print(destination string) error {
|
||||
defer cancel()
|
||||
p.ctx = ctx
|
||||
}
|
||||
// see https://github.com/thecodingmachine/gotenberg/issues/139.
|
||||
sort.Strings(p.fpaths)
|
||||
p.logger.DebugfOp(op, "merging '%v'...", p.fpaths)
|
||||
resolver := func() error {
|
||||
var args []string
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/phayes/freeport"
|
||||
@@ -56,6 +57,8 @@ func (p officePrinter) Print(destination string) error {
|
||||
ctx, cancel := xcontext.WithTimeout(p.logger, p.opts.WaitTimeout)
|
||||
defer cancel()
|
||||
resolver := func() error {
|
||||
// see https://github.com/thecodingmachine/gotenberg/issues/139.
|
||||
sort.Strings(p.fpaths)
|
||||
fpaths := make([]string, len(p.fpaths))
|
||||
dirPath := filepath.Dir(destination)
|
||||
for i, fpath := range p.fpaths {
|
||||
@@ -111,7 +114,7 @@ func (p officePrinter) unoconv(ctx context.Context, fpath, destination string) e
|
||||
}
|
||||
args = append(args, "--output", destination, fpath)
|
||||
if err := xexec.Run(ctx, p.logger, "unoconv", args...); err != nil {
|
||||
// TODO: find a way to check it in the handlers.
|
||||
// find a way to check it in the handlers?
|
||||
if p.opts.PageRanges != "" && strings.Contains(err.Error(), "exit status 5") {
|
||||
return xerror.Invalid(
|
||||
op,
|
||||
|
||||
@@ -2,6 +2,7 @@ package xassert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xerror"
|
||||
)
|
||||
@@ -54,7 +55,67 @@ func StringOneOf(values []string) RuleString {
|
||||
}
|
||||
}
|
||||
|
||||
type ruleStringStartWith struct {
|
||||
*baseRuleString
|
||||
startWith string
|
||||
}
|
||||
|
||||
func (r ruleStringStartWith) validate() error {
|
||||
const op string = "xassert.ruleStringStartWith.validate"
|
||||
if strings.HasPrefix(r.value, r.startWith) {
|
||||
return nil
|
||||
}
|
||||
return xerror.Invalid(
|
||||
op,
|
||||
fmt.Sprintf("'%s' should start with '%s', got '%s'", r.key, r.startWith, r.value),
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
StringStartWith returns a RuleString for
|
||||
validating that a string starts with
|
||||
given string.
|
||||
*/
|
||||
func StringStartWith(startWith string) RuleString {
|
||||
return ruleStringStartWith{
|
||||
&baseRuleString{},
|
||||
startWith,
|
||||
}
|
||||
}
|
||||
|
||||
type ruleStringEndWith struct {
|
||||
*baseRuleString
|
||||
endWith string
|
||||
}
|
||||
|
||||
func (r ruleStringEndWith) validate() error {
|
||||
const op string = "xassert.ruleStringEndWith.validate"
|
||||
if strings.HasSuffix(r.value, r.endWith) {
|
||||
return nil
|
||||
}
|
||||
return xerror.Invalid(
|
||||
op,
|
||||
fmt.Sprintf("'%s' should end with '%s', got '%s'", r.key, r.endWith, r.value),
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
StringEndWith returns a RuleString for
|
||||
validating that a string ends with
|
||||
given string.
|
||||
*/
|
||||
func StringEndWith(endWith string) RuleString {
|
||||
return ruleStringEndWith{
|
||||
&baseRuleString{},
|
||||
endWith,
|
||||
}
|
||||
}
|
||||
|
||||
// Compile-time checks to ensure type implements desired interfaces.
|
||||
var (
|
||||
_ = RuleString(new(ruleStringOneOf))
|
||||
_ = RuleString(new(ruleStringStartWith))
|
||||
_ = RuleString(new(ruleStringEndWith))
|
||||
)
|
||||
|
||||
@@ -18,3 +18,27 @@ func TestStringOfOne(t *testing.T) {
|
||||
err = rule.validate()
|
||||
test.AssertError(t, err)
|
||||
}
|
||||
|
||||
func TestStringStartWith(t *testing.T) {
|
||||
rule := StringStartWith("foo")
|
||||
// should be OK.
|
||||
rule.with("FOO", "foobarfoo")
|
||||
err := rule.validate()
|
||||
assert.Nil(t, err)
|
||||
// should not be OK.
|
||||
rule.with("FOO", "qux")
|
||||
err = rule.validate()
|
||||
test.AssertError(t, err)
|
||||
}
|
||||
|
||||
func TestStringEndWith(t *testing.T) {
|
||||
rule := StringEndWith("foo")
|
||||
// should be OK.
|
||||
rule.with("FOO", "foobarfoo")
|
||||
err := rule.validate()
|
||||
assert.Nil(t, err)
|
||||
// should not be OK.
|
||||
rule.with("FOO", "qux")
|
||||
err = rule.validate()
|
||||
test.AssertError(t, err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user