mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-16 20:32:13 +01:00
fix: LibreOffice newer versions stability (#697)
This commit is contained in:
@@ -19,11 +19,12 @@ import (
|
||||
"github.com/chromedp/cdproto/page"
|
||||
"github.com/chromedp/cdproto/runtime"
|
||||
"github.com/chromedp/chromedp"
|
||||
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||
"github.com/gotenberg/gotenberg/v7/pkg/modules/api"
|
||||
flag "github.com/spf13/pflag"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||
"github.com/gotenberg/gotenberg/v7/pkg/modules/api"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -68,8 +69,11 @@ var (
|
||||
// Chromium is a module which provides both an API and routes for converting
|
||||
// HTML document to PDF.
|
||||
type Chromium struct {
|
||||
binPath string
|
||||
engine gotenberg.PDFEngine
|
||||
binPath string
|
||||
engine gotenberg.PDFEngine
|
||||
fs *gotenberg.FileSystem
|
||||
logger *zap.Logger
|
||||
|
||||
failedStartsThreshold int
|
||||
userAgent string
|
||||
incognito bool
|
||||
@@ -311,6 +315,20 @@ func (mod *Chromium) Provision(ctx *gotenberg.Context) error {
|
||||
|
||||
mod.binPath = binPath
|
||||
|
||||
// Logger.
|
||||
loggerProvider, err := ctx.Module(new(gotenberg.LoggerProvider))
|
||||
if err != nil {
|
||||
return fmt.Errorf("get logger provider: %w", err)
|
||||
}
|
||||
|
||||
logger, err := loggerProvider.(gotenberg.LoggerProvider).Logger(mod)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get logger: %w", err)
|
||||
}
|
||||
|
||||
mod.logger = logger
|
||||
|
||||
// PDF engine.
|
||||
provider, err := ctx.Module(new(gotenberg.PDFEngineProvider))
|
||||
if err != nil {
|
||||
return fmt.Errorf("get PDF engine provider: %w", err)
|
||||
@@ -323,6 +341,9 @@ func (mod *Chromium) Provision(ctx *gotenberg.Context) error {
|
||||
|
||||
mod.engine = engine
|
||||
|
||||
// File system.
|
||||
mod.fs = gotenberg.NewFileSystem()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -412,7 +433,7 @@ func (mod Chromium) Routes() ([]api.Route, error) {
|
||||
// the end of the conversion.
|
||||
func (mod Chromium) PDF(ctx context.Context, logger *zap.Logger, URL, outputPath string, options Options) error {
|
||||
debug := debugLogger{logger: logger.Named("browser")}
|
||||
userProfileDirPath := gotenberg.NewDirPath()
|
||||
userProfileDirPath := mod.fs.NewDirPath()
|
||||
|
||||
args := append(chromedp.DefaultExecAllocatorOptions[:],
|
||||
chromedp.CombinedOutput(debug),
|
||||
@@ -799,7 +820,6 @@ func (mod Chromium) PDF(ctx context.Context, logger *zap.Logger, URL, outputPath
|
||||
|
||||
evaluate := chromedp.Evaluate(expression, &ok)
|
||||
err := evaluate.Do(ctx)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("evaluate: %v: %w", err, ErrInvalidEvaluationExpression)
|
||||
}
|
||||
@@ -886,7 +906,7 @@ func (mod Chromium) PDF(ctx context.Context, logger *zap.Logger, URL, outputPath
|
||||
}
|
||||
}()
|
||||
|
||||
file, err := os.OpenFile(outputPath, os.O_CREATE|os.O_WRONLY, 0600)
|
||||
file, err := os.OpenFile(outputPath, os.O_CREATE|os.O_WRONLY, 0o600)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open output path: %w", err)
|
||||
}
|
||||
@@ -921,13 +941,20 @@ func (mod Chromium) PDF(ctx context.Context, logger *zap.Logger, URL, outputPath
|
||||
activeInstancesCountMu.Unlock()
|
||||
|
||||
// Always remove the user profile directory created by Chromium.
|
||||
go func() {
|
||||
logger.Debug(fmt.Sprintf("remove user profile directory '%s'", userProfileDirPath))
|
||||
defer func() {
|
||||
go func() {
|
||||
// FIXME: Chromium seems to recreate the user profile directory
|
||||
// right after its deletion if we do not wait a certain amount
|
||||
// of time.
|
||||
time.Sleep(10 * time.Second)
|
||||
|
||||
err := os.RemoveAll(userProfileDirPath)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("remove user profile directory: %s", err))
|
||||
}
|
||||
err := os.RemoveAll(userProfileDirPath)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("remove Chromium's user profile directory: %s", err))
|
||||
}
|
||||
|
||||
logger.Debug(fmt.Sprintf("'%s' Chromium's user profile directory removed", userProfileDirPath))
|
||||
}()
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -10,18 +10,11 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/alexliesenfeld/health"
|
||||
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||
)
|
||||
|
||||
type ProtoModule struct {
|
||||
descriptor func() gotenberg.ModuleDescriptor
|
||||
}
|
||||
|
||||
func (mod ProtoModule) Descriptor() gotenberg.ModuleDescriptor {
|
||||
return mod.descriptor()
|
||||
}
|
||||
|
||||
type ProtoAPI struct {
|
||||
pdf func(_ context.Context, _ *zap.Logger, _, _ string, _ Options) error
|
||||
}
|
||||
@@ -30,28 +23,6 @@ func (mod ProtoAPI) PDF(ctx context.Context, logger *zap.Logger, URL, outputPath
|
||||
return mod.pdf(ctx, logger, URL, outputPath, options)
|
||||
}
|
||||
|
||||
type ProtoPDFEngineProvider struct {
|
||||
ProtoModule
|
||||
pdfEngine func() (gotenberg.PDFEngine, error)
|
||||
}
|
||||
|
||||
func (mod ProtoPDFEngineProvider) PDFEngine() (gotenberg.PDFEngine, error) {
|
||||
return mod.pdfEngine()
|
||||
}
|
||||
|
||||
type ProtoPDFEngine struct {
|
||||
merge func(_ context.Context, _ *zap.Logger, _ []string, _ string) error
|
||||
convert func(_ context.Context, _ *zap.Logger, _, _, _ string) error
|
||||
}
|
||||
|
||||
func (mod ProtoPDFEngine) Merge(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
||||
return mod.merge(ctx, logger, inputPaths, outputPath)
|
||||
}
|
||||
|
||||
func (mod ProtoPDFEngine) Convert(ctx context.Context, logger *zap.Logger, format, inputPath, outputPath string) error {
|
||||
return mod.convert(ctx, logger, format, inputPath, outputPath)
|
||||
}
|
||||
|
||||
func TestDefaultOptions(t *testing.T) {
|
||||
actual := DefaultOptions()
|
||||
notExpect := Options{}
|
||||
@@ -73,11 +44,13 @@ func TestChromium_Descriptor(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestChromium_Provision(t *testing.T) {
|
||||
for i, tc := range []struct {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
ctx *gotenberg.Context
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
scenario: "no logger provider",
|
||||
ctx: func() *gotenberg.Context {
|
||||
return gotenberg.NewContext(
|
||||
gotenberg.ParsedFlags{
|
||||
@@ -89,12 +62,16 @@ func TestChromium_Provision(t *testing.T) {
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
scenario: "no logger from logger provider",
|
||||
ctx: func() *gotenberg.Context {
|
||||
mod := struct{ ProtoPDFEngineProvider }{}
|
||||
mod.descriptor = func() gotenberg.ModuleDescriptor {
|
||||
mod := struct {
|
||||
gotenberg.ModuleMock
|
||||
gotenberg.LoggerProviderMock
|
||||
}{}
|
||||
mod.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||
return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return mod }}
|
||||
}
|
||||
mod.pdfEngine = func() (gotenberg.PDFEngine, error) {
|
||||
mod.LoggerMock = func(mod gotenberg.Module) (*zap.Logger, error) {
|
||||
return nil, errors.New("foo")
|
||||
}
|
||||
|
||||
@@ -110,13 +87,75 @@ func TestChromium_Provision(t *testing.T) {
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
scenario: "no PDF engine provider",
|
||||
ctx: func() *gotenberg.Context {
|
||||
mod := struct{ ProtoPDFEngineProvider }{}
|
||||
mod.descriptor = func() gotenberg.ModuleDescriptor {
|
||||
mod := struct {
|
||||
gotenberg.ModuleMock
|
||||
gotenberg.LoggerProviderMock
|
||||
}{}
|
||||
mod.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||
return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return mod }}
|
||||
}
|
||||
mod.pdfEngine = func() (gotenberg.PDFEngine, error) {
|
||||
return struct{ ProtoPDFEngine }{}, nil
|
||||
mod.LoggerMock = func(mod gotenberg.Module) (*zap.Logger, error) {
|
||||
return zap.NewNop(), nil
|
||||
}
|
||||
|
||||
return gotenberg.NewContext(
|
||||
gotenberg.ParsedFlags{
|
||||
FlagSet: new(Chromium).Descriptor().FlagSet,
|
||||
},
|
||||
[]gotenberg.ModuleDescriptor{
|
||||
mod.Descriptor(),
|
||||
},
|
||||
)
|
||||
}(),
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
scenario: "no PDF engine from PDF engine provider",
|
||||
ctx: func() *gotenberg.Context {
|
||||
mod := struct {
|
||||
gotenberg.ModuleMock
|
||||
gotenberg.LoggerProviderMock
|
||||
gotenberg.PDFEngineProviderMock
|
||||
}{}
|
||||
mod.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||
return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return mod }}
|
||||
}
|
||||
mod.LoggerMock = func(mod gotenberg.Module) (*zap.Logger, error) {
|
||||
return zap.NewNop(), nil
|
||||
}
|
||||
mod.PDFEngineMock = func() (gotenberg.PDFEngine, error) {
|
||||
return nil, errors.New("foo")
|
||||
}
|
||||
|
||||
return gotenberg.NewContext(
|
||||
gotenberg.ParsedFlags{
|
||||
FlagSet: new(Chromium).Descriptor().FlagSet,
|
||||
},
|
||||
[]gotenberg.ModuleDescriptor{
|
||||
mod.Descriptor(),
|
||||
},
|
||||
)
|
||||
}(),
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
scenario: "provision success",
|
||||
ctx: func() *gotenberg.Context {
|
||||
mod := struct {
|
||||
gotenberg.ModuleMock
|
||||
gotenberg.LoggerProviderMock
|
||||
gotenberg.PDFEngineProviderMock
|
||||
}{}
|
||||
mod.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||
return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return mod }}
|
||||
}
|
||||
mod.LoggerMock = func(mod gotenberg.Module) (*zap.Logger, error) {
|
||||
return zap.NewNop(), nil
|
||||
}
|
||||
mod.PDFEngineMock = func() (gotenberg.PDFEngine, error) {
|
||||
return gotenberg.PDFEngineMock{}, nil
|
||||
}
|
||||
|
||||
return gotenberg.NewContext(
|
||||
@@ -134,11 +173,11 @@ func TestChromium_Provision(t *testing.T) {
|
||||
err := mod.Provision(tc.ctx)
|
||||
|
||||
if tc.expectErr && err == nil {
|
||||
t.Errorf("test %d: expected error but got: %v", i, err)
|
||||
t.Errorf("test %s: expected error but got: %v", tc.scenario, err)
|
||||
}
|
||||
|
||||
if !tc.expectErr && err != nil {
|
||||
t.Errorf("test %d: expected no error but got: %v", i, err)
|
||||
t.Errorf("test %s: expected no error but got: %v", tc.scenario, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -643,16 +682,18 @@ func TestChromium_PDF(t *testing.T) {
|
||||
mod.allowList = tc.allowList
|
||||
mod.denyList = tc.denyList
|
||||
mod.disableJavaScript = tc.disableJavaScript
|
||||
mod.fs = gotenberg.NewFileSystem()
|
||||
|
||||
outputDir, err := gotenberg.MkdirAll()
|
||||
ctxFs := gotenberg.NewFileSystem()
|
||||
outputDir, err := ctxFs.MkdirAll()
|
||||
if err != nil {
|
||||
t.Fatalf("test %s: expected error but got: %v", tc.name, err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err := os.RemoveAll(outputDir)
|
||||
err := os.RemoveAll(ctxFs.WorkingDirPath())
|
||||
if err != nil {
|
||||
t.Fatalf("test %s: expected no error but got: %v", tc.name, err)
|
||||
t.Fatalf("test %s: expected no error while cleaning up but got: %v", tc.name, err)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -678,9 +719,5 @@ func TestChromium_PDF(t *testing.T) {
|
||||
|
||||
// Interface guards.
|
||||
var (
|
||||
_ gotenberg.Module = (*ProtoModule)(nil)
|
||||
_ API = (*ProtoAPI)(nil)
|
||||
_ gotenberg.PDFEngineProvider = (*ProtoPDFEngineProvider)(nil)
|
||||
_ gotenberg.Module = (*ProtoPDFEngineProvider)(nil)
|
||||
_ gotenberg.PDFEngine = (*ProtoPDFEngine)(nil)
|
||||
_ API = (*ProtoAPI)(nil)
|
||||
)
|
||||
|
||||
@@ -43,7 +43,6 @@ func listenForEventRequestPaused(ctx context.Context, logger *zap.Logger, allowL
|
||||
if allow {
|
||||
req := fetch.ContinueRequest(e.RequestID)
|
||||
err := req.Do(executorCtx)
|
||||
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("continue request: %s", err))
|
||||
}
|
||||
@@ -53,7 +52,6 @@ func listenForEventRequestPaused(ctx context.Context, logger *zap.Logger, allowL
|
||||
|
||||
req := fetch.FailRequest(e.RequestID, network.ErrorReasonAccessDenied)
|
||||
err := req.Do(executorCtx)
|
||||
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("fail request: %s", err))
|
||||
}
|
||||
|
||||
@@ -12,12 +12,13 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||
"github.com/gotenberg/gotenberg/v7/pkg/modules/api"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"github.com/russross/blackfriday/v2"
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||
"github.com/gotenberg/gotenberg/v7/pkg/modules/api"
|
||||
)
|
||||
|
||||
// FormDataChromiumPDFOptions creates Options form the form data. Fallback to
|
||||
@@ -163,7 +164,6 @@ func convertURLRoute(chromium API, engine gotenberg.PDFEngine) api.Route {
|
||||
return nil
|
||||
}).
|
||||
Validate()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("validate form data: %w", err)
|
||||
}
|
||||
@@ -197,7 +197,6 @@ func convertHTMLRoute(chromium API, engine gotenberg.PDFEngine) api.Route {
|
||||
MandatoryPath("index.html", &inputPath).
|
||||
String("pdfFormat", &PDFformat, "").
|
||||
Validate()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("validate form data: %w", err)
|
||||
}
|
||||
@@ -236,7 +235,6 @@ func convertMarkdownRoute(chromium API, engine gotenberg.PDFEngine) api.Route {
|
||||
MandatoryPaths([]string{".md"}, &markdownPaths).
|
||||
String("pdfFormat", &PDFformat, "").
|
||||
Validate()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("validate form data: %w", err)
|
||||
}
|
||||
@@ -284,7 +282,6 @@ func convertMarkdownRoute(chromium API, engine gotenberg.PDFEngine) api.Route {
|
||||
return template.HTML(sanitized), nil
|
||||
},
|
||||
}).ParseFiles(inputPath)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse template file: %w", err)
|
||||
}
|
||||
@@ -308,7 +305,7 @@ func convertMarkdownRoute(chromium API, engine gotenberg.PDFEngine) api.Route {
|
||||
|
||||
inputPath = ctx.GeneratePath(".html")
|
||||
|
||||
err = os.WriteFile(inputPath, buffer.Bytes(), 0600)
|
||||
err = os.WriteFile(inputPath, buffer.Bytes(), 0o600)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write template result: %w", err)
|
||||
}
|
||||
|
||||
@@ -8,10 +8,11 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||
"github.com/gotenberg/gotenberg/v7/pkg/modules/api"
|
||||
"github.com/labstack/echo/v4"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||
"github.com/gotenberg/gotenberg/v7/pkg/modules/api"
|
||||
)
|
||||
|
||||
func TestFormDataChromiumPDFOptions(t *testing.T) {
|
||||
@@ -588,8 +589,7 @@ func TestConvertMarkdownHandler(t *testing.T) {
|
||||
} {
|
||||
func() {
|
||||
if tc.outputDir != "" {
|
||||
err := os.MkdirAll(tc.outputDir, 0755)
|
||||
|
||||
err := os.MkdirAll(tc.outputDir, 0o755)
|
||||
if err != nil {
|
||||
t.Fatalf("test %d: expected error but got: %v", i, err)
|
||||
}
|
||||
@@ -784,8 +784,8 @@ func TestConvertURL(t *testing.T) {
|
||||
return chromiumAPI
|
||||
}(),
|
||||
engine: func() gotenberg.PDFEngine {
|
||||
return &ProtoPDFEngine{
|
||||
convert: func(_ context.Context, _ *zap.Logger, _, _, _ string) error {
|
||||
return &gotenberg.PDFEngineMock{
|
||||
ConvertMock: func(_ context.Context, _ *zap.Logger, _, _, _ string) error {
|
||||
return gotenberg.ErrPDFFormatNotAvailable
|
||||
},
|
||||
}
|
||||
@@ -807,8 +807,8 @@ func TestConvertURL(t *testing.T) {
|
||||
return chromiumAPI
|
||||
}(),
|
||||
engine: func() gotenberg.PDFEngine {
|
||||
return &ProtoPDFEngine{
|
||||
convert: func(_ context.Context, _ *zap.Logger, _, _, _ string) error {
|
||||
return &gotenberg.PDFEngineMock{
|
||||
ConvertMock: func(_ context.Context, _ *zap.Logger, _, _, _ string) error {
|
||||
return errors.New("foo")
|
||||
},
|
||||
}
|
||||
@@ -828,8 +828,8 @@ func TestConvertURL(t *testing.T) {
|
||||
return chromiumAPI
|
||||
}(),
|
||||
engine: func() gotenberg.PDFEngine {
|
||||
return &ProtoPDFEngine{
|
||||
convert: func(_ context.Context, _ *zap.Logger, _, _, _ string) error {
|
||||
return &gotenberg.PDFEngineMock{
|
||||
ConvertMock: func(_ context.Context, _ *zap.Logger, _, _, _ string) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user