From d82aa9d4b4870c32e6afa30d314e0699ece98ef1 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 9 Apr 2026 20:08:12 +0500 Subject: [PATCH] fix(import): decrypt owner-encrypted PDFs with pdfcpu; add imports bucket to minio-init MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - parsePDF now attempts to strip encryption via pdfcpu (empty user password) before handing bytes to dslipak/pdf — fixes '256-bit encryption key' error on publisher PDFs that use owner-only encryption (copy/print restrictions) - Add pdfcpu v0.11.1 as direct dependency (was already indirect) - docker-compose.yml minio-init: add 'imports' and 'translations' buckets so a fresh deploy creates all required buckets --- backend/go.mod | 1 + backend/internal/storage/import.go | 28 ++++++++++++++++++++++++++++ docker-compose.yml | 2 ++ 3 files changed, 31 insertions(+) diff --git a/backend/go.mod b/backend/go.mod index 959e443..3a532dd 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -38,6 +38,7 @@ require ( github.com/minio/crc64nvme v1.1.1 // indirect github.com/minio/md5-simd v1.1.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pdfcpu/pdfcpu v0.11.1 // indirect github.com/philhofer/fwd v1.2.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/backend/internal/storage/import.go b/backend/internal/storage/import.go index 73244ce..1a32964 100644 --- a/backend/internal/storage/import.go +++ b/backend/internal/storage/import.go @@ -15,6 +15,8 @@ import ( "github.com/libnovel/backend/internal/bookstore" "github.com/libnovel/backend/internal/domain" minio "github.com/minio/minio-go/v7" + "github.com/pdfcpu/pdfcpu/pkg/api" + "github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model" "golang.org/x/net/html" ) @@ -90,7 +92,33 @@ func AnalyzeFile(data []byte, fileType string) (chapterCount int, firstLines []s +// decryptPDF strips encryption from a PDF using an empty user password. +// Returns the decrypted bytes, or an error if decryption is not possible. +// This handles the common case of "owner-only" encrypted PDFs (copy/print +// restrictions) which use an empty user password and open normally in readers. +func decryptPDF(data []byte) ([]byte, error) { + conf := model.NewDefaultConfiguration() + conf.UserPW = "" + conf.OwnerPW = "" + + var out bytes.Buffer + err := api.Decrypt(bytes.NewReader(data), &out, conf) + if err != nil { + return nil, err + } + return out.Bytes(), nil +} + func parsePDF(data []byte) ([]bookstore.Chapter, error) { + // If the PDF is encrypted, try to decrypt it with an empty password. + // Many publisher PDFs use owner-only encryption (copy/print restrictions) + // with an empty user password, so they open normally but confuse parsers. + decrypted, err := decryptPDF(data) + if err == nil { + data = decrypted + } + // (if decryption fails we still attempt to parse — maybe it works anyway) + r, err := pdf.NewReader(bytes.NewReader(data), int64(len(data))) if err != nil { return nil, fmt.Errorf("open PDF: %w", err) diff --git a/docker-compose.yml b/docker-compose.yml index 59f451c..3b3c7fd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -58,6 +58,8 @@ services: mc mb --ignore-existing local/audio; mc mb --ignore-existing local/avatars; mc mb --ignore-existing local/catalogue; + mc mb --ignore-existing local/translations; + mc mb --ignore-existing local/imports; echo 'buckets ready'; " environment: