From dce50855841d99d7b680148f00ec80f83c1cf2e2 Mon Sep 17 00:00:00 2001 From: Horst Rutter Date: Sun, 3 Dec 2023 19:51:19 +0100 Subject: [PATCH] Fix #665, #473 --- cmd/pdfcpu/process.go | 134 ++++++++++++++++++----------- cmd/pdfcpu/usage.go | 31 ++++++- pkg/api/test/encryption_test.go | 33 ++++++- pkg/cli/cli.go | 2 +- pkg/cli/cmd.go | 8 +- pkg/cli/list.go | 45 +++++++--- pkg/cli/test/encryption_test.go | 28 +++--- pkg/pdfcpu/crypto.go | 2 +- pkg/pdfcpu/model/config.yml | 9 +- pkg/pdfcpu/model/configuration.go | 35 ++++++-- pkg/pdfcpu/model/parseConfig.go | 2 +- pkg/pdfcpu/model/parseConfig_js.go | 2 +- pkg/pdfcpu/write.go | 2 +- 13 files changed, 233 insertions(+), 100 deletions(-) diff --git a/cmd/pdfcpu/process.go b/cmd/pdfcpu/process.go index db82c8e7b..d9bb7530c 100644 --- a/cmd/pdfcpu/process.go +++ b/cmd/pdfcpu/process.go @@ -170,7 +170,7 @@ func processValidateCommand(conf *model.Configuration) { os.Exit(1) } - filesIn := []string{} + inFiles := []string{} for _, arg := range flag.Args() { if strings.Contains(arg, "*") { matches, err := filepath.Glob(arg) @@ -178,13 +178,13 @@ func processValidateCommand(conf *model.Configuration) { fmt.Fprintf(os.Stderr, "%s", err) os.Exit(1) } - filesIn = append(filesIn, matches...) + inFiles = append(inFiles, matches...) continue } if conf.CheckFileNameExt { ensurePDFExtension(arg) } - filesIn = append(filesIn, arg) + inFiles = append(inFiles, arg) } if mode != "" && mode != "strict" && mode != "s" && mode != "relaxed" && mode != "r" { @@ -203,7 +203,7 @@ func processValidateCommand(conf *model.Configuration) { conf.ValidateLinks = true } - process(cli.ValidateCommand(filesIn, conf)) + process(cli.ValidateCommand(inFiles, conf)) } func processOptimizeCommand(conf *model.Configuration) { @@ -295,19 +295,19 @@ func processSplitCommand(conf *model.Configuration) { process(cli.SplitCommand(inFile, outDir, span, conf)) } -func sortFiles(filesIn []string) { +func sortFiles(inFiles []string) { // See PR #631 re := regexp.MustCompile(`\d+`) sort.Slice( - filesIn, + inFiles, func(i, j int) bool { - ssi := re.FindAllString(filesIn[i], 1) - ssj := re.FindAllString(filesIn[j], 1) + ssi := re.FindAllString(inFiles[i], 1) + ssj := re.FindAllString(inFiles[j], 1) if len(ssi) == 0 || len(ssj) == 0 { - return filesIn[i] <= filesIn[j] + return inFiles[i] <= inFiles[j] } i1, _ := strconv.Atoi(ssi[0]) i2, _ := strconv.Atoi(ssj[0]) @@ -316,7 +316,7 @@ func sortFiles(filesIn []string) { } func processArgsForMerge(conf *model.Configuration) ([]string, string) { - filesIn := []string{} + inFiles := []string{} outFile := "" for i, arg := range flag.Args() { if i == 0 { @@ -334,15 +334,15 @@ func processArgsForMerge(conf *model.Configuration) ([]string, string) { fmt.Fprintf(os.Stderr, "%s", err) os.Exit(1) } - filesIn = append(filesIn, matches...) + inFiles = append(inFiles, matches...) continue } if conf.CheckFileNameExt { ensurePDFExtension(arg) } - filesIn = append(filesIn, arg) + inFiles = append(inFiles, arg) } - return filesIn, outFile + return inFiles, outFile } func processMergeCommand(conf *model.Configuration) { @@ -369,10 +369,10 @@ func processMergeCommand(conf *model.Configuration) { fmt.Fprintf(os.Stderr, "merge zip: -d(ivider) not applicable and will be ignored\n") } - filesIn, outFile := processArgsForMerge(conf) + inFiles, outFile := processArgsForMerge(conf) if sorted { - sortFiles(filesIn) + sortFiles(inFiles) } if conf == nil { @@ -387,13 +387,13 @@ func processMergeCommand(conf *model.Configuration) { switch mode { case "create": - cmd = cli.MergeCreateCommand(filesIn, outFile, dividerPage, conf) + cmd = cli.MergeCreateCommand(inFiles, outFile, dividerPage, conf) case "zip": - cmd = cli.MergeCreateZipCommand(filesIn, outFile, conf) + cmd = cli.MergeCreateZipCommand(inFiles, outFile, conf) case "append": - cmd = cli.MergeAppendCommand(filesIn, outFile, dividerPage, conf) + cmd = cli.MergeAppendCommand(inFiles, outFile, dividerPage, conf) } @@ -616,40 +616,65 @@ func processExtractAttachmentsCommand(conf *model.Configuration) { } func processListPermissionsCommand(conf *model.Configuration) { - if len(flag.Args()) != 1 || selectedPages != "" { + if len(flag.Args()) == 0 || selectedPages != "" { fmt.Fprintf(os.Stderr, "usage: %s\n", usagePermList) os.Exit(1) } - inFile := flag.Arg(0) - if conf.CheckFileNameExt { - ensurePDFExtension(inFile) + inFiles := []string{} + for _, arg := range flag.Args() { + if strings.Contains(arg, "*") { + matches, err := filepath.Glob(arg) + if err != nil { + fmt.Fprintf(os.Stderr, "%s", err) + os.Exit(1) + } + inFiles = append(inFiles, matches...) + continue + } + if conf.CheckFileNameExt { + ensurePDFExtension(arg) + } + inFiles = append(inFiles, arg) } - process(cli.ListPermissionsCommand(inFile, conf)) + process(cli.ListPermissionsCommand(inFiles, conf)) } func permCompletion(permPrefix string) string { - var permStr string for _, perm := range []string{"none", "print", "all"} { if !strings.HasPrefix(perm, permPrefix) { continue } - if len(permStr) > 0 { - return "" - } - permStr = perm + return perm } - return permStr + return permPrefix +} + +func isBinary(s string) bool { + _, err := strconv.ParseUint(s, 2, 12) + return err == nil +} + +func isHex(s string) bool { + if s[0] != 'x' { + return false + } + s = s[1:] + _, err := strconv.ParseUint(s, 16, 16) + return err == nil } func processSetPermissionsCommand(conf *model.Configuration) { if perm != "" { perm = permCompletion(perm) } - if len(flag.Args()) != 1 || selectedPages != "" || - !(perm == "none" || perm == "print" || perm == "all") { + if len(flag.Args()) != 1 || selectedPages != "" { + fmt.Fprintf(os.Stderr, "usage: %s\n\n", usagePermSet) + os.Exit(1) + } + if perm != "" && perm != "none" && perm != "print" && perm != "all" && !isBinary(perm) && !isHex(perm) { fmt.Fprintf(os.Stderr, "usage: %s\n\n", usagePermSet) os.Exit(1) } @@ -659,12 +684,23 @@ func processSetPermissionsCommand(conf *model.Configuration) { ensurePDFExtension(inFile) } - if perm == "print" { - conf.Permissions = model.PermissionsPrint - } - - if perm == "all" { - conf.Permissions = model.PermissionsAll + if perm != "" { + switch perm { + case "none": + conf.Permissions = model.PermissionsNone + case "print": + conf.Permissions = model.PermissionsPrint + case "all": + conf.Permissions = model.PermissionsAll + default: + var p uint64 + if perm[0] == 'x' { + p, _ = strconv.ParseUint(perm[1:], 16, 16) + } else { + p, _ = strconv.ParseUint(perm, 2, 12) + } + conf.Permissions = model.PermissionFlags(p) + } } process(cli.SetPermissionsCommand(inFile, "", conf)) @@ -1373,7 +1409,7 @@ func processInfoCommand(conf *model.Configuration) { os.Exit(1) } - filesIn := []string{} + inFiles := []string{} for _, arg := range flag.Args() { if strings.Contains(arg, "*") { matches, err := filepath.Glob(arg) @@ -1381,13 +1417,13 @@ func processInfoCommand(conf *model.Configuration) { fmt.Fprintf(os.Stderr, "%s", err) os.Exit(1) } - filesIn = append(filesIn, matches...) + inFiles = append(inFiles, matches...) continue } if conf.CheckFileNameExt { ensurePDFExtension(arg) } - filesIn = append(filesIn, arg) + inFiles = append(inFiles, arg) } selectedPages, err := api.ParsePageSelection(selectedPages) @@ -1398,7 +1434,7 @@ func processInfoCommand(conf *model.Configuration) { processDiplayUnit(conf) - process(cli.InfoCommand(filesIn, selectedPages, json, conf)) + process(cli.InfoCommand(inFiles, selectedPages, json, conf)) } func processListFontsCommand(conf *model.Configuration) { @@ -1806,7 +1842,7 @@ func processListImagesCommand(conf *model.Configuration) { os.Exit(1) } - filesIn := []string{} + inFiles := []string{} for _, arg := range flag.Args() { if strings.Contains(arg, "*") { matches, err := filepath.Glob(arg) @@ -1814,13 +1850,13 @@ func processListImagesCommand(conf *model.Configuration) { fmt.Fprintf(os.Stderr, "%s", err) os.Exit(1) } - filesIn = append(filesIn, matches...) + inFiles = append(inFiles, matches...) continue } if conf.CheckFileNameExt { ensurePDFExtension(arg) } - filesIn = append(filesIn, arg) + inFiles = append(inFiles, arg) } selectedPages, err := api.ParsePageSelection(selectedPages) @@ -1829,7 +1865,7 @@ func processListImagesCommand(conf *model.Configuration) { os.Exit(1) } - process(cli.ListImagesCommand(filesIn, selectedPages, conf)) + process(cli.ListImagesCommand(inFiles, selectedPages, conf)) } func processDumpCommand(conf *model.Configuration) { @@ -1894,7 +1930,7 @@ func processListFormFieldsCommand(conf *model.Configuration) { os.Exit(1) } - filesIn := []string{} + inFiles := []string{} for _, arg := range flag.Args() { if strings.Contains(arg, "*") { matches, err := filepath.Glob(arg) @@ -1902,16 +1938,16 @@ func processListFormFieldsCommand(conf *model.Configuration) { fmt.Fprintf(os.Stderr, "%s", err) os.Exit(1) } - filesIn = append(filesIn, matches...) + inFiles = append(inFiles, matches...) continue } if conf.CheckFileNameExt { ensurePDFExtension(arg) } - filesIn = append(filesIn, arg) + inFiles = append(inFiles, arg) } - process(cli.ListFormFieldsCommand(filesIn, conf)) + process(cli.ListFormFieldsCommand(inFiles, conf)) } func processRemoveFormFieldsCommand(conf *model.Configuration) { diff --git a/cmd/pdfcpu/usage.go b/cmd/pdfcpu/usage.go index e0cf44c47..e379cbf7a 100644 --- a/cmd/pdfcpu/usage.go +++ b/cmd/pdfcpu/usage.go @@ -94,7 +94,7 @@ common flags: -v(erbose) ... turn on logging mode ... validation mode links ... check for broken links - inFile ... a list of pdf input files + inFile ... input PDF file The validation modes are: @@ -264,8 +264,8 @@ content ... extract raw page content pdfcpu portfolio add test.pdf "test.mp3, Test sound file" "test.mkv, Test video file" ` - usagePermList = "pdfcpu permissions list [-upw userpw] [-opw ownerpw] inFile" - usagePermSet = "pdfcpu permissions set [-perm none|print|all] [-upw userpw] -opw ownerpw inFile" + generalFlags + usagePermList = "pdfcpu permissions list [-upw userpw] [-opw ownerpw] inFile..." + usagePermSet = "pdfcpu permissions set [-perm none|print|all|max4Hex|max12Bits] [-upw userpw] -opw ownerpw inFile" + generalFlags usagePerm = "usage: " + usagePermList + "\n " + usagePermSet @@ -273,7 +273,30 @@ content ... extract raw page content usageLongPerm = `Manage user access permissions. perm ... user access permissions - inFile ... input PDF file` + inFile ... input PDF file + + perm modes: + + none: 000000000000 (x000) + print: 100000000100 (x804) + all: 111100111100 (xF3C) + max4Hex: x + max. 4 hex digits + max12Bits: max. 12 binary digits + + using the permission bits: + + 1: - + 2: - + 3: Print (security handlers rev.2), draft print (security handlers >= rev.3) + 4: Modify contents by operations other than controlled by bits 6, 9, 11. + 5: Copy, extract text & graphics + 6: Add or modify annotations, fill form fields, in conjunction with bit 4 create/mod form fields. + 7: - + 8: - + 9: Fill form fields (security handlers >= rev.3) + 10: Copy, extract text & graphics (security handlers >= rev.3) (unused since PDF 2.0) + 11: Assemble document (security handlers >= rev.3) + 12: Print (security handlers >= rev.3)` usageEncrypt = "usage: pdfcpu encrypt [-m(ode) rc4|aes] [-key 40|128|256] [-perm none|print|all] [-upw userpw] -opw ownerpw inFile [outFile]" + generalFlags usageLongEncrypt = `Setup password protection based on user and owner password. diff --git a/pkg/api/test/encryption_test.go b/pkg/api/test/encryption_test.go index d5ef9eff5..05c2b69bc 100644 --- a/pkg/api/test/encryption_test.go +++ b/pkg/api/test/encryption_test.go @@ -95,8 +95,9 @@ func setPermissions(t *testing.T, aes bool, keyLength int, msg, outFile string) if err != nil { t.Fatalf("%s: get permissions %s: %v\n", msg, outFile, err) } + // Ensure permissions all. - if p == nil || *p != model.PermissionsAll { + if p == nil || uint16(*p) != uint16(model.PermissionsAll) { t.Fatal() } @@ -138,7 +139,7 @@ func testEncryption(t *testing.T, fileName string, alg string, keyLength int) { t.Fatalf("%s: get permissions %s: %v\n", msg, inFile, err) } // Ensure permissions none. - if p == nil || *p != model.PermissionsNone { + if p == nil || uint16(*p) != uint16(model.PermissionsNone) { t.Fatal() } @@ -149,7 +150,7 @@ func testEncryption(t *testing.T, fileName string, alg string, keyLength int) { t.Fatalf("%s: get permissions %s: %v\n", msg, inFile, err) } // Ensure permissions none. - if p == nil || *p != model.PermissionsNone { + if p == nil || uint16(*p) != uint16(model.PermissionsNone) { t.Fatal() } @@ -191,3 +192,29 @@ func TestEncryption(t *testing.T) { testEncryption(t, fileName, "aes", 256) } } + +func TestSetPermissions(t *testing.T) { + msg := "TestSetPermissions" + inFile := filepath.Join(inDir, "5116.DCT_Filter.pdf") + outFile := filepath.Join(outDir, "out.pdf") + + conf := confForAlgorithm(true, 256, "upw", "opw") + permNew := model.PermissionsNone | model.PermissionPrintRev2 | model.PermissionPrintRev3 + conf.Permissions = permNew + + if err := api.EncryptFile(inFile, outFile, conf); err != nil { + t.Fatalf("%s: encrypt %s: %v\n", msg, outFile, err) + } + + conf = confForAlgorithm(true, 256, "upw", "opw") + p, err := api.GetPermissionsFile(outFile, conf) + if err != nil { + t.Fatalf("%s: get permissions %s: %v\n", msg, outFile, err) + } + if p == nil { + t.Fatalf("%s: missing permissions", msg) + } + if uint16(*p) != uint16(permNew) { + t.Fatalf("%s: got: %d want: %d", msg, uint16(*p), uint16(permNew)) + } +} diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index dd1ecba45..f80c85498 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -59,7 +59,7 @@ func ChangeOwnerPassword(cmd *Command) ([]string, error) { // ListPermissions of inFile. func ListPermissions(cmd *Command) ([]string, error) { - return ListPermissionsFile(*cmd.InFile, cmd.Conf) + return ListPermissionsFile(cmd.InFiles, cmd.Conf) } // SetPermissions of inFile. diff --git a/pkg/cli/cmd.go b/pkg/cli/cmd.go index 8a633445e..636a37d1e 100644 --- a/pkg/cli/cmd.go +++ b/pkg/cli/cmd.go @@ -445,15 +445,15 @@ func ChangeOwnerPWCommand(inFile, outFile string, pwOld, pwNew *string, conf *mo } // ListPermissionsCommand create a new command to list permissions. -func ListPermissionsCommand(inFile string, conf *model.Configuration) *Command { +func ListPermissionsCommand(inFiles []string, conf *model.Configuration) *Command { if conf == nil { conf = model.NewDefaultConfiguration() } conf.Cmd = model.LISTPERMISSIONS return &Command{ - Mode: model.LISTPERMISSIONS, - InFile: &inFile, - Conf: conf} + Mode: model.LISTPERMISSIONS, + InFiles: inFiles, + Conf: conf} } // SetPermissionsCommand creates a new command to add permissions. diff --git a/pkg/cli/list.go b/pkg/cli/list.go index de13f37d8..5fcb1fed7 100644 --- a/pkg/cli/list.go +++ b/pkg/cli/list.go @@ -174,6 +174,8 @@ func listFormFields(rs io.ReadSeeker, conf *model.Configuration) ([]string, erro // ListFormFieldsFile returns a list of form field ids in inFile. func ListFormFieldsFile(inFiles []string, conf *model.Configuration) ([]string, error) { + log.SetCLILogger(nil) + ss := []string{} for _, fn := range inFiles { @@ -191,13 +193,13 @@ func ListFormFieldsFile(inFiles []string, conf *model.Configuration) ([]string, output, err := listFormFields(f, conf) if err != nil { if len(inFiles) > 1 { - ss = append(ss, fmt.Sprintf("\n%s: %v", fn, err)) + ss = append(ss, fmt.Sprintf("\n%s:\n%v", fn, err)) continue } return nil, err } - ss = append(ss, "\n"+fn) + ss = append(ss, "\n"+fn+":\n") ss = append(ss, output...) } @@ -237,6 +239,8 @@ func ListImagesFile(inFiles []string, selectedPages []string, conf *model.Config log.CLI.Printf("pages: all\n") } + log.SetCLILogger(nil) + ss := []string{} for _, fn := range inFiles { @@ -257,7 +261,7 @@ func ListImagesFile(inFiles []string, selectedPages []string, conf *model.Config } return nil, err } - ss = append(ss, "\n"+fn) + ss = append(ss, "\n"+fn+":") ss = append(ss, output...) } @@ -381,17 +385,34 @@ func listPermissions(rs io.ReadSeeker, conf *model.Configuration) ([]string, err } // ListPermissionsFile returns a list of user access permissions for inFile. -func ListPermissionsFile(inFile string, conf *model.Configuration) ([]string, error) { - f, err := os.Open(inFile) - if err != nil { - return nil, err - } +func ListPermissionsFile(inFiles []string, conf *model.Configuration) ([]string, error) { + log.SetCLILogger(nil) - defer func() { - f.Close() - }() + var ss []string + + for i, fn := range inFiles { + if i > 0 { + ss = append(ss, "") + } + f, err := os.Open(fn) + if err != nil { + return nil, err + } + defer func() { + f.Close() + }() + ssx, err := listPermissions(f, conf) + if err != nil { + if len(inFiles) == 1 { + return nil, err + } + fmt.Fprintf(os.Stderr, "%s: %v\n", fn, err) + } + ss = append(ss, fn+":") + ss = append(ss, ssx...) + } - return listPermissions(f, conf) + return ss, nil } func listProperties(rs io.ReadSeeker, conf *model.Configuration) ([]string, error) { diff --git a/pkg/cli/test/encryption_test.go b/pkg/cli/test/encryption_test.go index cecd38f2a..71481acc5 100644 --- a/pkg/cli/test/encryption_test.go +++ b/pkg/cli/test/encryption_test.go @@ -141,14 +141,14 @@ func testEncryptDecryptUseCase1(t *testing.T, fileName string, aes bool, keyLeng func ensurePermissionsNone(t *testing.T, listPermOutput []string) { t.Helper() - if len(listPermOutput) == 0 || !strings.HasPrefix(listPermOutput[0], "permission bits: 0") { + if len(listPermOutput) == 0 || !strings.HasPrefix(listPermOutput[1], "permission bits: 000000000000") { t.Fail() } } func ensurePermissionsAll(t *testing.T, listPermOutput []string) { t.Helper() - if len(listPermOutput) == 0 || listPermOutput[0] != "permission bits: 111100111100" { + if len(listPermOutput) == 0 || !strings.HasPrefix(listPermOutput[1], "permission bits: 111100111100") { t.Fail() } } @@ -224,7 +224,7 @@ func testEncryptDecryptUseCase2(t *testing.T, fileName string, aes bool, keyLeng // List permissions conf = model.NewDefaultConfiguration() conf.OwnerPW = "opw" - cmd = cli.ListPermissionsCommand(outFile, conf) + cmd = cli.ListPermissionsCommand([]string{outFile}, conf) list, err := cli.Process(cmd) if err != nil { t.Fatalf("%s: list permissions for %s: %v\n", msg, outFile, err) @@ -445,12 +445,12 @@ func testPermissionsOPWOnly(t *testing.T, fileName string, aes bool, keyLength i outFile := filepath.Join(outDir, "test.pdf") t.Log(inFile) - cmd := cli.ListPermissionsCommand(inFile, nil) + cmd := cli.ListPermissionsCommand([]string{inFile}, nil) list, err := cli.Process(cmd) if err != nil { t.Fatalf("%s: list permissions %s: %v\n", msg, inFile, err) } - if len(list) == 0 || list[0] != "Full access" { + if len(list) == 0 || list[1] != "Full access" { t.Fail() } @@ -461,7 +461,7 @@ func testPermissionsOPWOnly(t *testing.T, fileName string, aes bool, keyLength i t.Fatalf("%s: encrypt %s: %v\n", msg, outFile, err) } - cmd = cli.ListPermissionsCommand(outFile, nil) + cmd = cli.ListPermissionsCommand([]string{outFile}, nil) if list, err = cli.Process(cmd); err != nil { t.Fatalf("%s: list permissions %s: %v\n", msg, outFile, err) } @@ -475,7 +475,7 @@ func testPermissionsOPWOnly(t *testing.T, fileName string, aes bool, keyLength i t.Fatalf("%s: set all permissions for %s: %v\n", msg, outFile, err) } - cmd = cli.ListPermissionsCommand(outFile, nil) + cmd = cli.ListPermissionsCommand([]string{outFile}, nil) if list, err = cli.Process(cmd); err != nil { t.Fatalf("%s: list permissions for %s: %v\n", msg, outFile, err) } @@ -505,12 +505,12 @@ func testPermissions(t *testing.T, fileName string, aes bool, keyLength int) { outFile := filepath.Join(outDir, "test.pdf") t.Log(inFile) - cmd := cli.ListPermissionsCommand(inFile, nil) + cmd := cli.ListPermissionsCommand([]string{inFile}, nil) list, err := cli.Process(cmd) if err != nil { t.Fatalf("%s: list permissions %s: %v\n", msg, inFile, err) } - if len(list) == 0 || list[0] != "Full access" { + if len(list) == 0 || list[1] != "Full access" { t.Fail() } @@ -522,14 +522,14 @@ func testPermissions(t *testing.T, fileName string, aes bool, keyLength int) { t.Fatalf("%s: encrypt %s: %v\n", msg, outFile, err) } - cmd = cli.ListPermissionsCommand(outFile, nil) + cmd = cli.ListPermissionsCommand([]string{outFile}, nil) if _, err = cli.Process(cmd); err == nil { t.Fatalf("%s: list permissions w/o pw %s\n", msg, outFile) } conf = confForAlgorithm(aes, keyLength) conf.UserPW = "upw" - cmd = cli.ListPermissionsCommand(outFile, conf) + cmd = cli.ListPermissionsCommand([]string{outFile}, conf) if list, err = cli.Process(cmd); err != nil { t.Fatalf("%s: list permissions %s: %v\n", msg, outFile, err) } @@ -537,7 +537,7 @@ func testPermissions(t *testing.T, fileName string, aes bool, keyLength int) { conf = model.NewDefaultConfiguration() conf.OwnerPW = "opw" - cmd = cli.ListPermissionsCommand(outFile, conf) + cmd = cli.ListPermissionsCommand([]string{outFile}, conf) if list, err = cli.Process(cmd); err != nil { t.Fatalf("%s: list permissions %s: %v\n", msg, outFile, err) } @@ -575,14 +575,14 @@ func testPermissions(t *testing.T, fileName string, aes bool, keyLength int) { t.Fatalf("%s: set all permissions for %s: %v\n", msg, outFile, err) } - cmd = cli.ListPermissionsCommand(outFile, nil) + cmd = cli.ListPermissionsCommand([]string{outFile}, nil) if _, err = cli.Process(cmd); err == nil { t.Fatalf("%s: list permissions w/o pw %s\n", msg, outFile) } conf = confForAlgorithm(aes, keyLength) conf.OwnerPW = "opw" - cmd = cli.ListPermissionsCommand(outFile, conf) + cmd = cli.ListPermissionsCommand([]string{outFile}, conf) if list, err = cli.Process(cmd); err != nil { t.Fatalf("%s: list permissions for %s: %v\n", msg, outFile, err) } diff --git a/pkg/pdfcpu/crypto.go b/pkg/pdfcpu/crypto.go index d204b1ad1..adc94c44f 100644 --- a/pkg/pdfcpu/crypto.go +++ b/pkg/pdfcpu/crypto.go @@ -577,7 +577,7 @@ func supportedCFEntry(d types.Dict) (bool, error) { func perms(p int) (list []string) { - list = append(list, fmt.Sprintf("permission bits: %12b", uint32(p)&0x0F3C)) + list = append(list, fmt.Sprintf("permission bits: %012b (x%03X)", uint32(p)&0x0F3C, uint32(p)&0x0F3C)) list = append(list, fmt.Sprintf("Bit 3: %t (print(rev2), print quality(rev>=3))", p&0x0004 > 0)) list = append(list, fmt.Sprintf("Bit 4: %t (modify other than controlled by bits 6,9,11)", p&0x0008 > 0)) list = append(list, fmt.Sprintf("Bit 5: %t (extract(rev2), extract other than controlled by bit 10(rev>=3))", p&0x0010 > 0)) diff --git a/pkg/pdfcpu/model/config.yml b/pkg/pdfcpu/model/config.yml index ac6983005..e215d0c31 100644 --- a/pkg/pdfcpu/model/config.yml +++ b/pkg/pdfcpu/model/config.yml @@ -29,10 +29,11 @@ encryptUsingAES: true encryptKeyLength: 256 # permissions for encrypted files: -# -3901 = 0xF0C3 (PermissionsNone) -# -1849 = 0xF8C7 (PermissionsPrint) -# -1 = 0xFFFF (PermissionsAll) -permissions: -3901 +# 0xF0C3 (PermissionsNone) +# 0xF8C7 (PermissionsPrint) +# 0xFFFF (PermissionsAll) +# See more at model.PermissionFlags and PDF spec table 22 +permissions: 0xF0C3 # displayUnit: # points diff --git a/pkg/pdfcpu/model/configuration.go b/pkg/pdfcpu/model/configuration.go index 83063761d..b7f3c6b6e 100644 --- a/pkg/pdfcpu/model/configuration.go +++ b/pkg/pdfcpu/model/configuration.go @@ -38,19 +38,44 @@ const ( ValidationNone ) +// See table 22 - User access permissions +type PermissionFlags int + +const ( + UnusedFlag1 PermissionFlags = 1 << iota // Bit 1: unused + UnusedFlag2 // Bit 2: unused + PermissionPrintRev2 // Bit 3: Print (security handlers rev.2), draft print (security handlers >= rev.3) + PermissionModify // Bit 4: Modify contents by operations other than controlled by bits 6, 9, 11. + PermissionExtract // Bit 5: Copy, extract text & graphics + PermissionModAnnFillForm // Bit 6: Add or modify annotations, fill form fields, in conjunction with bit 4 create/mod form fields. + UnusedFlag7 // Bit 7: unused + UnusedFlag8 // Bit 8: unused + PermissionFillRev3 // Bit 9: Fill form fields (security handlers >= rev.3) + PermissionExtractRev3 // Bit 10: Copy, extract text & graphics (security handlers >= rev.3) (unused since PDF 2.0) + PermissionAssembleRev3 // Bit 11: Assemble document (security handlers >= rev.3) + PermissionPrintRev3 // Bit 12: Print (security handlers >= rev.3) +) + +const ( + PermissionsNone = PermissionFlags(0xF0C3) + PermissionsPrint = PermissionsNone + PermissionPrintRev2 + PermissionPrintRev3 + PermissionsAll = PermissionFlags(0xFFFF) +) + const ( // StatsFileNameDefault is the standard stats filename. StatsFileNameDefault = "stats.csv" // PermissionsAll enables all user access permission bits. - PermissionsAll int16 = -1 // 0xFFFF + // int16(value), err := strconv.ParseUint("F8C7", 16, 16) = FFCs + flags + //PermissionsAll int16 = -1 // 0xFFFF // PermissionsPrint disables all user access permissions bits except for printing. - PermissionsPrint int16 = -1849 // 0xF8C7 + //PermissionsPrint int16 = -1849 // 0xF8C7 // PermissionsNone disables all user access permissions bits. - PermissionsNone int16 = -3901 // 0xF0C3 + //PermissionsNone int16 = -3901 // 0xF0C3 ) @@ -197,7 +222,7 @@ type Configuration struct { EncryptKeyLength int // Supplied user access permissions, see Table 22. - Permissions int16 + Permissions PermissionFlags // int16 // Command being executed. Cmd CommandMode @@ -288,7 +313,7 @@ func newDefaultConfiguration() *Configuration { WriteXRefStream: true, EncryptUsingAES: true, EncryptKeyLength: 256, - Permissions: PermissionsNone, + Permissions: PermissionsPrint, TimestampFormat: "2006-01-02 15:04", DateFormat: "2006-01-02", HeaderBufSize: 100, diff --git a/pkg/pdfcpu/model/parseConfig.go b/pkg/pdfcpu/model/parseConfig.go index f00e7002c..a69cb55c5 100644 --- a/pkg/pdfcpu/model/parseConfig.go +++ b/pkg/pdfcpu/model/parseConfig.go @@ -59,7 +59,7 @@ func loadedConfig(c configuration, configPath string) *Configuration { conf.WriteXRefStream = c.WriteXRefStream conf.EncryptUsingAES = c.EncryptUsingAES conf.EncryptKeyLength = c.EncryptKeyLength - conf.Permissions = int16(c.Permissions) + conf.Permissions = PermissionFlags(c.Permissions) switch c.ValidationMode { case "ValidationStrict": diff --git a/pkg/pdfcpu/model/parseConfig_js.go b/pkg/pdfcpu/model/parseConfig_js.go index 9c63b5cda..ca74d3cbb 100644 --- a/pkg/pdfcpu/model/parseConfig_js.go +++ b/pkg/pdfcpu/model/parseConfig_js.go @@ -129,7 +129,7 @@ func handleConfPermissions(v string, c *Configuration) error { if err != nil { return errors.Errorf("permissions is numeric, got: %s", v) } - c.Permissions = int16(i) + c.Permissions = PermissionFlags(c.Permissions) return nil } diff --git a/pkg/pdfcpu/write.go b/pkg/pdfcpu/write.go index d55a99790..d02af0fd0 100644 --- a/pkg/pdfcpu/write.go +++ b/pkg/pdfcpu/write.go @@ -908,7 +908,7 @@ func setupEncryption(ctx *model.Context) error { d := newEncryptDict( ctx.EncryptUsingAES, ctx.EncryptKeyLength, - ctx.Permissions, + int16(ctx.Permissions), ) if ctx.E, err = supportedEncryption(ctx, d); err != nil {