pdf-lite 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.commitlintrc.cjs +25 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +19 -0
- package/.github/workflows/docs.yaml +93 -0
- package/.github/workflows/prepare-release.yaml +79 -0
- package/.github/workflows/release.yaml +80 -0
- package/.github/workflows/test.yaml +35 -0
- package/.husky/commit-msg +1 -0
- package/.husky/pre-commit +1 -0
- package/.prettierignore +4 -0
- package/.prettierrc +4 -0
- package/CONTRIBUTING.md +109 -0
- package/EXAMPLES.md +1515 -0
- package/LICENSE +21 -0
- package/README.md +285 -0
- package/examples/001-create-pdf.ts +112 -0
- package/examples/002-create-encrypted-pdf.ts +121 -0
- package/examples/003-sign-pdf.ts +347 -0
- package/examples/004-incremental-update.ts +206 -0
- package/examples/005-modify-acroform.ts +374 -0
- package/examples/006-tokeniser-example.ts +131 -0
- package/examples/007-decoder-example.ts +197 -0
- package/package.json +72 -0
- package/packages/pdf-lite/README.md +3 -0
- package/packages/pdf-lite/package.json +68 -0
- package/packages/pdf-lite/scripts/create-encryption-tests.sh +41 -0
- package/packages/pdf-lite/scripts/gen-signing-keys.sh +290 -0
- package/packages/pdf-lite/scripts/generate-all-signing-keys.sh +70 -0
- package/packages/pdf-lite/src/core/decoder.ts +454 -0
- package/packages/pdf-lite/src/core/generators.ts +128 -0
- package/packages/pdf-lite/src/core/incremental-parser.ts +221 -0
- package/packages/pdf-lite/src/core/index.ts +2 -0
- package/packages/pdf-lite/src/core/objects/pdf-array.ts +54 -0
- package/packages/pdf-lite/src/core/objects/pdf-boolean.ts +19 -0
- package/packages/pdf-lite/src/core/objects/pdf-comment.ts +50 -0
- package/packages/pdf-lite/src/core/objects/pdf-date.ts +74 -0
- package/packages/pdf-lite/src/core/objects/pdf-dictionary.ts +171 -0
- package/packages/pdf-lite/src/core/objects/pdf-hexadecimal.ts +54 -0
- package/packages/pdf-lite/src/core/objects/pdf-indirect-object.ts +137 -0
- package/packages/pdf-lite/src/core/objects/pdf-name.ts +19 -0
- package/packages/pdf-lite/src/core/objects/pdf-null.ts +15 -0
- package/packages/pdf-lite/src/core/objects/pdf-number.ts +98 -0
- package/packages/pdf-lite/src/core/objects/pdf-object-reference.ts +30 -0
- package/packages/pdf-lite/src/core/objects/pdf-object.ts +107 -0
- package/packages/pdf-lite/src/core/objects/pdf-start-xref.ts +39 -0
- package/packages/pdf-lite/src/core/objects/pdf-stream.ts +687 -0
- package/packages/pdf-lite/src/core/objects/pdf-string.ts +38 -0
- package/packages/pdf-lite/src/core/objects/pdf-trailer.ts +57 -0
- package/packages/pdf-lite/src/core/objects/pdf-xref-table.ts +264 -0
- package/packages/pdf-lite/src/core/parser.ts +22 -0
- package/packages/pdf-lite/src/core/ref.ts +102 -0
- package/packages/pdf-lite/src/core/serializer.ts +68 -0
- package/packages/pdf-lite/src/core/streams/object-stream.ts +20 -0
- package/packages/pdf-lite/src/core/tokeniser.ts +687 -0
- package/packages/pdf-lite/src/core/tokens/boolean-token.ts +20 -0
- package/packages/pdf-lite/src/core/tokens/byte-offset-token.ts +20 -0
- package/packages/pdf-lite/src/core/tokens/comment-token.ts +32 -0
- package/packages/pdf-lite/src/core/tokens/end-array-token.ts +10 -0
- package/packages/pdf-lite/src/core/tokens/end-dictionary-token.ts +10 -0
- package/packages/pdf-lite/src/core/tokens/end-object-token.ts +10 -0
- package/packages/pdf-lite/src/core/tokens/end-stream-token.ts +11 -0
- package/packages/pdf-lite/src/core/tokens/hexadecimal-token.ts +22 -0
- package/packages/pdf-lite/src/core/tokens/name-token.ts +19 -0
- package/packages/pdf-lite/src/core/tokens/null-token.ts +9 -0
- package/packages/pdf-lite/src/core/tokens/number-token.ts +164 -0
- package/packages/pdf-lite/src/core/tokens/object-reference-token.ts +24 -0
- package/packages/pdf-lite/src/core/tokens/start-array-token.ts +10 -0
- package/packages/pdf-lite/src/core/tokens/start-dictionary-token.ts +10 -0
- package/packages/pdf-lite/src/core/tokens/start-object-token.ts +28 -0
- package/packages/pdf-lite/src/core/tokens/start-stream-token.ts +52 -0
- package/packages/pdf-lite/src/core/tokens/start-xref-token.ts +10 -0
- package/packages/pdf-lite/src/core/tokens/stream-chunk-token.ts +8 -0
- package/packages/pdf-lite/src/core/tokens/string-token.ts +17 -0
- package/packages/pdf-lite/src/core/tokens/token.ts +43 -0
- package/packages/pdf-lite/src/core/tokens/trailer-token.ts +12 -0
- package/packages/pdf-lite/src/core/tokens/whitespace-token.ts +43 -0
- package/packages/pdf-lite/src/core/tokens/xref-table-entry-token.ts +65 -0
- package/packages/pdf-lite/src/core/tokens/xref-table-section-start-token.ts +31 -0
- package/packages/pdf-lite/src/core/tokens/xref-table-start-token.ts +13 -0
- package/packages/pdf-lite/src/crypto/ciphers/aes128.ts +63 -0
- package/packages/pdf-lite/src/crypto/ciphers/aes256.ts +50 -0
- package/packages/pdf-lite/src/crypto/ciphers/rc4.ts +82 -0
- package/packages/pdf-lite/src/crypto/constants.ts +10 -0
- package/packages/pdf-lite/src/crypto/key-derivation/key-derivation-aes256.ts +213 -0
- package/packages/pdf-lite/src/crypto/key-derivation/key-derivation.ts +122 -0
- package/packages/pdf-lite/src/crypto/key-gen/key-gen-aes256.ts +79 -0
- package/packages/pdf-lite/src/crypto/key-gen/key-gen-rc4-128.ts +190 -0
- package/packages/pdf-lite/src/crypto/key-gen/key-gen-rc4-40.ts +129 -0
- package/packages/pdf-lite/src/crypto/types.ts +6 -0
- package/packages/pdf-lite/src/crypto/utils.ts +81 -0
- package/packages/pdf-lite/src/filters/ascii85.ts +128 -0
- package/packages/pdf-lite/src/filters/asciihex.ts +55 -0
- package/packages/pdf-lite/src/filters/flate.ts +39 -0
- package/packages/pdf-lite/src/filters/lzw.ts +144 -0
- package/packages/pdf-lite/src/filters/pass-through.ts +37 -0
- package/packages/pdf-lite/src/filters/runlength.ts +92 -0
- package/packages/pdf-lite/src/filters/types.ts +21 -0
- package/packages/pdf-lite/src/index.ts +4 -0
- package/packages/pdf-lite/src/pdf/errors.ts +5 -0
- package/packages/pdf-lite/src/pdf/index.ts +4 -0
- package/packages/pdf-lite/src/pdf/pdf-document.ts +924 -0
- package/packages/pdf-lite/src/pdf/pdf-reader.ts +57 -0
- package/packages/pdf-lite/src/pdf/pdf-revision.ts +234 -0
- package/packages/pdf-lite/src/pdf/pdf-xref-lookup.ts +527 -0
- package/packages/pdf-lite/src/security/crypt-filters/aesv2.ts +58 -0
- package/packages/pdf-lite/src/security/crypt-filters/aesv3.ts +56 -0
- package/packages/pdf-lite/src/security/crypt-filters/base.ts +140 -0
- package/packages/pdf-lite/src/security/crypt-filters/identity.ts +40 -0
- package/packages/pdf-lite/src/security/crypt-filters/v2.ts +59 -0
- package/packages/pdf-lite/src/security/handlers/base.ts +625 -0
- package/packages/pdf-lite/src/security/handlers/pubSec.ts +413 -0
- package/packages/pdf-lite/src/security/handlers/utils.ts +304 -0
- package/packages/pdf-lite/src/security/handlers/v1.ts +225 -0
- package/packages/pdf-lite/src/security/handlers/v2.ts +128 -0
- package/packages/pdf-lite/src/security/handlers/v4.ts +379 -0
- package/packages/pdf-lite/src/security/handlers/v5.ts +298 -0
- package/packages/pdf-lite/src/security/types.ts +158 -0
- package/packages/pdf-lite/src/signing/document-security-store.ts +224 -0
- package/packages/pdf-lite/src/signing/index.ts +3 -0
- package/packages/pdf-lite/src/signing/signatures/adbe-pkcs7-detached.ts +154 -0
- package/packages/pdf-lite/src/signing/signatures/adbe-pkcs7-sha1.ts +161 -0
- package/packages/pdf-lite/src/signing/signatures/adbe-x509-rsa-sha1.ts +106 -0
- package/packages/pdf-lite/src/signing/signatures/base.ts +229 -0
- package/packages/pdf-lite/src/signing/signatures/etsi-cades-detached.ts +229 -0
- package/packages/pdf-lite/src/signing/signatures/etsi-rfc3161.ts +92 -0
- package/packages/pdf-lite/src/signing/signatures/index.ts +6 -0
- package/packages/pdf-lite/src/signing/signer.ts +120 -0
- package/packages/pdf-lite/src/signing/types.ts +86 -0
- package/packages/pdf-lite/src/signing/utils.ts +71 -0
- package/packages/pdf-lite/src/types.ts +44 -0
- package/packages/pdf-lite/src/utils/IterableReadableStream.ts +30 -0
- package/packages/pdf-lite/src/utils/algos.ts +446 -0
- package/packages/pdf-lite/src/utils/assert.ts +42 -0
- package/packages/pdf-lite/src/utils/bytesToHex.ts +18 -0
- package/packages/pdf-lite/src/utils/bytesToHexBytes.ts +27 -0
- package/packages/pdf-lite/src/utils/bytesToString.ts +17 -0
- package/packages/pdf-lite/src/utils/concatUint8Arrays.ts +26 -0
- package/packages/pdf-lite/src/utils/escapeString.ts +49 -0
- package/packages/pdf-lite/src/utils/hexBytesToBytes.ts +22 -0
- package/packages/pdf-lite/src/utils/hexBytesToString.ts +21 -0
- package/packages/pdf-lite/src/utils/hexToBytes.ts +18 -0
- package/packages/pdf-lite/src/utils/padBytes.ts +25 -0
- package/packages/pdf-lite/src/utils/predictors.ts +332 -0
- package/packages/pdf-lite/src/utils/replaceInBuffer.ts +56 -0
- package/packages/pdf-lite/src/utils/stringToBytes.ts +22 -0
- package/packages/pdf-lite/src/utils/stringToHexBytes.ts +23 -0
- package/packages/pdf-lite/src/utils/unescapeString.ts +123 -0
- package/packages/pdf-lite/test/acceptance/__snapshots__/versions.node.test.ts.snap +60766 -0
- package/packages/pdf-lite/test/acceptance/fixtures/1.3/basic.pdf +0 -0
- package/packages/pdf-lite/test/acceptance/fixtures/1.4/basic-aes-128.pdf +0 -0
- package/packages/pdf-lite/test/acceptance/fixtures/1.4/basic-aes-256.pdf +0 -0
- package/packages/pdf-lite/test/acceptance/fixtures/1.4/basic-rc4-128.pdf +0 -0
- package/packages/pdf-lite/test/acceptance/fixtures/1.4/basic-rc4-40.pdf +0 -0
- package/packages/pdf-lite/test/acceptance/fixtures/1.4/basic.pdf +0 -0
- package/packages/pdf-lite/test/acceptance/fixtures/1.5/basic.pdf +0 -0
- package/packages/pdf-lite/test/acceptance/fixtures/1.6/basic.pdf +0 -0
- package/packages/pdf-lite/test/acceptance/fixtures/1.7/basic.pdf +0 -0
- package/packages/pdf-lite/test/acceptance/fixtures/2.0/basic-aes-128.pdf +43 -0
- package/packages/pdf-lite/test/acceptance/fixtures/2.0/basic-aes-256.pdf +43 -0
- package/packages/pdf-lite/test/acceptance/fixtures/2.0/basic-rc4-128.pdf +43 -0
- package/packages/pdf-lite/test/acceptance/fixtures/2.0/basic-rc4-40.pdf +44 -0
- package/packages/pdf-lite/test/acceptance/fixtures/2.0/basic.pdf +79 -0
- package/packages/pdf-lite/test/acceptance/versions.node.test.ts +41 -0
- package/packages/pdf-lite/test/unit/__snapshots__/decoder.node.test.ts.snap +86947 -0
- package/packages/pdf-lite/test/unit/__snapshots__/tokeniser.node.test.ts.snap +131829 -0
- package/packages/pdf-lite/test/unit/ciphers.test.ts +61 -0
- package/packages/pdf-lite/test/unit/decoder.node.test.ts +21 -0
- package/packages/pdf-lite/test/unit/decoder.test.ts +567 -0
- package/packages/pdf-lite/test/unit/filters.test.ts +67 -0
- package/packages/pdf-lite/test/unit/fixtures/basic.pdf +0 -0
- package/packages/pdf-lite/test/unit/fixtures/encrypted_v1/basic-aes-128.pdf +0 -0
- package/packages/pdf-lite/test/unit/fixtures/encrypted_v1/basic-aes-256.pdf +0 -0
- package/packages/pdf-lite/test/unit/fixtures/encrypted_v1/basic-rc4-128.pdf +0 -0
- package/packages/pdf-lite/test/unit/fixtures/encrypted_v1/basic-rc4-40.pdf +43 -0
- package/packages/pdf-lite/test/unit/fixtures/protectedAdobeLivecycle.pdf +0 -0
- package/packages/pdf-lite/test/unit/fixtures/rsa-2048/index.ts +187 -0
- package/packages/pdf-lite/test/unit/fixtures/template.pdf +0 -0
- package/packages/pdf-lite/test/unit/incremental-update.test.ts +0 -0
- package/packages/pdf-lite/test/unit/objects.test.ts +0 -0
- package/packages/pdf-lite/test/unit/pdf-document-signing.test.ts +0 -0
- package/packages/pdf-lite/test/unit/pdf-revision.test.ts +195 -0
- package/packages/pdf-lite/test/unit/pdf.browser.test.ts +0 -0
- package/packages/pdf-lite/test/unit/predictors.test.ts +226 -0
- package/packages/pdf-lite/test/unit/ref.test.ts +158 -0
- package/packages/pdf-lite/test/unit/security-handlers.test.ts +645 -0
- package/packages/pdf-lite/test/unit/serializer.test.ts +81 -0
- package/packages/pdf-lite/test/unit/signature-objects.test.ts +814 -0
- package/packages/pdf-lite/test/unit/string-escaping.test.ts +84 -0
- package/packages/pdf-lite/test/unit/tokeniser.node.test.ts +38 -0
- package/packages/pdf-lite/test/unit/tokeniser.test.ts +1213 -0
- package/packages/pdf-lite/test/unit/utils.test.ts +248 -0
- package/packages/pdf-lite/test/unit/xref-lookup.test.ts +72 -0
- package/packages/pdf-lite/tsconfig.json +4 -0
- package/packages/pdf-lite/tsconfig.prod.json +8 -0
- package/packages/pdf-lite/typedoc.json +14 -0
- package/packages/pdf-lite/vitest.config.ts +43 -0
- package/pnpm-workspace.yaml +2 -0
- package/renovate.json +34 -0
- package/scripts/build-examples.ts +30 -0
- package/scripts/bump-version.sh +56 -0
- package/scripts/gen-html-docs.sh +21 -0
- package/scripts/gen-md-docs.sh +15 -0
- package/scripts/prepare-release.sh +33 -0
- package/tsconfig.json +22 -0
- package/tsconfig.prod.json +12 -0
- package/typedoc.json +34 -0
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pdf-lite",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "A lightweight PDF library for Node.js and the browser",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"compile": "pnpm -r run compile && pnpm compile:examples && pnpm compile:test",
|
|
10
|
+
"compile:examples": "tsx ./scripts/build-examples.ts && prettier --write EXAMPLES.md",
|
|
11
|
+
"compile:test": "tsc --noEmit",
|
|
12
|
+
"prepublish": "pnpm -r run compile",
|
|
13
|
+
"test": "pnpm -r --no-sort run test",
|
|
14
|
+
"prepare": "husky || true",
|
|
15
|
+
"lint:staged": "lint-staged --allow-empty",
|
|
16
|
+
"lint:commit": "commitlint --edit $1",
|
|
17
|
+
"format": "prettier --write '**/*.{js,ts,json,md,yaml,yml}'",
|
|
18
|
+
"docs:gen": "sh -c 'pnpm docs:gen:md \"$@\" && pnpm docs:gen:html \"$@\"' --",
|
|
19
|
+
"docs:gen:md": "./scripts/gen-md-docs.sh",
|
|
20
|
+
"docs:gen:html": "./scripts/gen-html-docs.sh"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"pdf",
|
|
24
|
+
"typescript",
|
|
25
|
+
"browser",
|
|
26
|
+
"nodejs",
|
|
27
|
+
"zero-dependency",
|
|
28
|
+
"type-safe",
|
|
29
|
+
"pdf-parser",
|
|
30
|
+
"pdf-reader",
|
|
31
|
+
"pdf-writer",
|
|
32
|
+
"digital-signature",
|
|
33
|
+
"encryption",
|
|
34
|
+
"decryption"
|
|
35
|
+
],
|
|
36
|
+
"author": "Jacob Shirley",
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git+https://github.com/jacobshirley/pdf-lite.git"
|
|
41
|
+
},
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/jacobshirley/pdf-lite/issues"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://jacobshirley.github.io/pdf-lite",
|
|
46
|
+
"packageManager": "pnpm@10.23.0",
|
|
47
|
+
"lint-staged": {
|
|
48
|
+
"*.{js,ts,json,md}": "pnpm format"
|
|
49
|
+
},
|
|
50
|
+
"pnpm": {
|
|
51
|
+
"overrides": {
|
|
52
|
+
"playwright": "$playwright",
|
|
53
|
+
"typescript": "$typescript"
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@commitlint/cli": "^20.1.0",
|
|
58
|
+
"@commitlint/config-conventional": "^20.0.0",
|
|
59
|
+
"@types/node": "^24",
|
|
60
|
+
"husky": "^9.1.7",
|
|
61
|
+
"lint-staged": "^16.2.7",
|
|
62
|
+
"playwright": "^1.56.1",
|
|
63
|
+
"prettier": "^3.6.2",
|
|
64
|
+
"tsx": "^4.20.6",
|
|
65
|
+
"typedoc": "^0.28.14",
|
|
66
|
+
"typedoc-plugin-markdown": "^4.9.0",
|
|
67
|
+
"typescript": "5.9.3"
|
|
68
|
+
},
|
|
69
|
+
"dependencies": {
|
|
70
|
+
"pdf-lite": "^1.0.0"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pdf-lite",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"default": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"./*": {
|
|
12
|
+
"types": "./dist/*.d.ts",
|
|
13
|
+
"default": "./dist/*"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test:acceptance": "vitest run test/acceptance",
|
|
18
|
+
"test:unit": "vitest run test/unit",
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"compile": "pnpm compile:validate && tsc -p tsconfig.prod.json",
|
|
21
|
+
"compile:validate": "tsc -p tsconfig.json --noEmit",
|
|
22
|
+
"watch": "tsc -watch -p tsconfig.prod.json",
|
|
23
|
+
"watch:all": "tsc -watch -p tsconfig.json",
|
|
24
|
+
"prepack": "cp ../../README.md . && cp ../../EXAMPLES.md ."
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"pdf",
|
|
28
|
+
"typescript",
|
|
29
|
+
"browser",
|
|
30
|
+
"nodejs",
|
|
31
|
+
"zero-dependency",
|
|
32
|
+
"type-safe",
|
|
33
|
+
"pdf-parser",
|
|
34
|
+
"pdf-reader",
|
|
35
|
+
"pdf-writer",
|
|
36
|
+
"digital-signature",
|
|
37
|
+
"encryption",
|
|
38
|
+
"decryption"
|
|
39
|
+
],
|
|
40
|
+
"author": "Jacob Shirley",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^20.19.17",
|
|
44
|
+
"@types/pako": "^2.0.4",
|
|
45
|
+
"@vitest/browser-playwright": "^4.0.14",
|
|
46
|
+
"@vitest/coverage-v8": "^4.0.14",
|
|
47
|
+
"playwright": "^1.56.1",
|
|
48
|
+
"typescript": "5.9.3",
|
|
49
|
+
"vitest": "^4.0.14"
|
|
50
|
+
},
|
|
51
|
+
"repository": {
|
|
52
|
+
"type": "git",
|
|
53
|
+
"url": "git+ssh://git@github.com/jacobshirley/pdf-lite.git"
|
|
54
|
+
},
|
|
55
|
+
"bugs": {
|
|
56
|
+
"url": "https://github.com/jacobshirley/pdf-lite/issues"
|
|
57
|
+
},
|
|
58
|
+
"homepage": "https://jacobshirley.github.io/pdf-lite",
|
|
59
|
+
"description": "A low-level, zero-dependency, type-safe PDF library for Node.js and the browser",
|
|
60
|
+
"files": [
|
|
61
|
+
"dist"
|
|
62
|
+
],
|
|
63
|
+
"dependencies": {
|
|
64
|
+
"pako": "2.1.0",
|
|
65
|
+
"pki-lite": "^1.0.10",
|
|
66
|
+
"pki-lite-crypto-extended": "^1.0.10"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
set -e
|
|
4
|
+
|
|
5
|
+
if [ $# -ne 2 ]; then
|
|
6
|
+
echo "Usage: $0 <input-pdf> <output-dir>"
|
|
7
|
+
exit 1
|
|
8
|
+
fi
|
|
9
|
+
|
|
10
|
+
INPUT_PDF="$1"
|
|
11
|
+
OUTPUT_DIR="$2"
|
|
12
|
+
|
|
13
|
+
if [ ! -f "$INPUT_PDF" ]; then
|
|
14
|
+
echo "Error: input file '$INPUT_PDF' does not exist."
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
rm -rf "$OUTPUT_DIR"
|
|
19
|
+
mkdir -p "$OUTPUT_DIR"
|
|
20
|
+
|
|
21
|
+
USER_PWD="userpass"
|
|
22
|
+
OWNER_PWD="ownerpass"
|
|
23
|
+
|
|
24
|
+
# Format: "<suffix> <qpdf encryption args...> --"
|
|
25
|
+
CONFIGS=(
|
|
26
|
+
"rc4-40 --encrypt \"$USER_PWD\" \"$OWNER_PWD\" 40 --"
|
|
27
|
+
"rc4-128 --encrypt \"$USER_PWD\" \"$OWNER_PWD\" 128 --"
|
|
28
|
+
"aes-128 --encrypt \"$USER_PWD\" \"$OWNER_PWD\" 128 --"
|
|
29
|
+
"aes-256 --encrypt \"$USER_PWD\" \"$OWNER_PWD\" 256 --"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
for config in "${CONFIGS[@]}"; do
|
|
33
|
+
SUFFIX=$(echo "$config" | awk '{print $1}')
|
|
34
|
+
ARGS=$(echo "$config" | cut -d' ' -f2-)
|
|
35
|
+
echo $ARGS
|
|
36
|
+
OUTPUT="$OUTPUT_DIR/$(basename "${INPUT_PDF%.pdf}")-$SUFFIX.pdf"
|
|
37
|
+
echo "🔐 Creating $OUTPUT"
|
|
38
|
+
eval qpdf "\"$INPUT_PDF\"" "\"$OUTPUT\"" $ARGS --allow-weak-crypto
|
|
39
|
+
done
|
|
40
|
+
|
|
41
|
+
echo "✅ Done. Encrypted PDFs saved to: $OUTPUT_DIR"
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# Generate CA key and certificate
|
|
2
|
+
#
|
|
3
|
+
# This script generates a CA key and certificate, along with a signer key and certificate.
|
|
4
|
+
# It also creates an OCSP responder certificate and response.
|
|
5
|
+
# All outputs are converted to PEM format and a TypeScript file is generated with the PEM contents.
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# ./gen.sh --output-dir=./output [options]
|
|
9
|
+
#
|
|
10
|
+
# Examples:
|
|
11
|
+
# ./gen.sh --output-dir=./output # Generate RSA keys with default settings
|
|
12
|
+
# ./gen.sh --output-dir=./output --key-type=ec --key-size=p256 # Generate EC keys with P-256 curve
|
|
13
|
+
# ./gen.sh --output-dir=./output --key-type=ed25519 # Generate ED25519 keys
|
|
14
|
+
|
|
15
|
+
# Default values
|
|
16
|
+
KEY_TYPE="rsa"
|
|
17
|
+
KEY_SIZE="2048"
|
|
18
|
+
EXPORT_PREFIX="rsa"
|
|
19
|
+
CA_KEY_SIZE="4096"
|
|
20
|
+
OUTPUT_DIR=""
|
|
21
|
+
|
|
22
|
+
# Parse command line arguments
|
|
23
|
+
while [[ $# -gt 0 ]]; do
|
|
24
|
+
case $1 in
|
|
25
|
+
--key-type=*)
|
|
26
|
+
KEY_TYPE="${1#*=}"
|
|
27
|
+
shift
|
|
28
|
+
;;
|
|
29
|
+
--key-size=*)
|
|
30
|
+
KEY_SIZE="${1#*=}"
|
|
31
|
+
shift
|
|
32
|
+
;;
|
|
33
|
+
--ca-key-size=*)
|
|
34
|
+
CA_KEY_SIZE="${1#*=}"
|
|
35
|
+
shift
|
|
36
|
+
;;
|
|
37
|
+
--output-dir=*)
|
|
38
|
+
OUTPUT_DIR="${1#*=}"
|
|
39
|
+
shift
|
|
40
|
+
;;
|
|
41
|
+
--help)
|
|
42
|
+
echo "Usage: $0 [options]"
|
|
43
|
+
echo "Options:"
|
|
44
|
+
echo " --key-type=TYPE Key type to generate (rsa, ec, ed25519) [default: rsa]"
|
|
45
|
+
echo " --key-size=SIZE Key size for RSA or curve for EC (p256, p384, p521) [default: 2048 for RSA, p256 for EC]"
|
|
46
|
+
echo " --ca-key-size=SIZE Size for CA key [default: 4096 for RSA]"
|
|
47
|
+
echo " --output-dir=DIR Output directory [REQUIRED]"
|
|
48
|
+
echo " --help Show this help message and exit"
|
|
49
|
+
exit 0
|
|
50
|
+
;;
|
|
51
|
+
*)
|
|
52
|
+
echo "Unknown parameter: $1"
|
|
53
|
+
exit 1
|
|
54
|
+
;;
|
|
55
|
+
esac
|
|
56
|
+
done
|
|
57
|
+
|
|
58
|
+
# Validate required parameters
|
|
59
|
+
if [ -z "$OUTPUT_DIR" ]; then
|
|
60
|
+
echo "Error: --output-dir parameter is required"
|
|
61
|
+
echo "Run '$0 --help' for usage information"
|
|
62
|
+
exit 1
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# Create output directory if it doesn't exist
|
|
66
|
+
mkdir -p "$OUTPUT_DIR"
|
|
67
|
+
cd "$OUTPUT_DIR" || exit 1
|
|
68
|
+
|
|
69
|
+
# Create demoCA directory if it doesn't exist
|
|
70
|
+
mkdir -p demoCA
|
|
71
|
+
touch demoCA/index.txt
|
|
72
|
+
echo "01" > demoCA/serial
|
|
73
|
+
echo "01" > demoCA/crlnumber
|
|
74
|
+
|
|
75
|
+
# Set export prefix for TypeScript variable naming
|
|
76
|
+
if [ "$KEY_TYPE" == "ec" ]; then
|
|
77
|
+
if [ "$KEY_SIZE" == "p256" ] || [ "$KEY_SIZE" == "prime256v1" ]; then
|
|
78
|
+
EXPORT_PREFIX="ecP256"
|
|
79
|
+
elif [ "$KEY_SIZE" == "p384" ] || [ "$KEY_SIZE" == "secp384r1" ]; then
|
|
80
|
+
EXPORT_PREFIX="ecP384"
|
|
81
|
+
elif [ "$KEY_SIZE" == "p521" ] || [ "$KEY_SIZE" == "secp521r1" ]; then
|
|
82
|
+
EXPORT_PREFIX="ecP521"
|
|
83
|
+
else
|
|
84
|
+
EXPORT_PREFIX="ec"
|
|
85
|
+
fi
|
|
86
|
+
elif [ "$KEY_TYPE" == "ed25519" ]; then
|
|
87
|
+
EXPORT_PREFIX="ed25519"
|
|
88
|
+
else
|
|
89
|
+
EXPORT_PREFIX="rsa"
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
echo "Generating $KEY_TYPE keys with size/curve $KEY_SIZE..."
|
|
93
|
+
|
|
94
|
+
# Generate CA key and certificate
|
|
95
|
+
if [ "$KEY_TYPE" == "rsa" ]; then
|
|
96
|
+
openssl genrsa -out rootCA.key $CA_KEY_SIZE
|
|
97
|
+
openssl genrsa -out signer.key $KEY_SIZE
|
|
98
|
+
elif [ "$KEY_TYPE" == "ec" ]; then
|
|
99
|
+
# Map p256, p384, p521 to proper curve names
|
|
100
|
+
CURVE="$KEY_SIZE"
|
|
101
|
+
if [ "$KEY_SIZE" == "p256" ]; then
|
|
102
|
+
CURVE="prime256v1"
|
|
103
|
+
elif [ "$KEY_SIZE" == "p384" ]; then
|
|
104
|
+
CURVE="secp384r1"
|
|
105
|
+
elif [ "$KEY_SIZE" == "p521" ]; then
|
|
106
|
+
CURVE="secp521r1"
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
# Generate EC keys in PKCS#8 format for compatibility with PrivateKeyInfo
|
|
110
|
+
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:$CURVE -out rootCA.key
|
|
111
|
+
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:$CURVE -out signer.key
|
|
112
|
+
elif [ "$KEY_TYPE" == "ed25519" ]; then
|
|
113
|
+
openssl genpkey -algorithm ed25519 -out rootCA.key
|
|
114
|
+
openssl genpkey -algorithm ed25519 -out signer.key
|
|
115
|
+
else
|
|
116
|
+
echo "Unsupported key type: $KEY_TYPE"
|
|
117
|
+
exit 1
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
echo """
|
|
121
|
+
basicConstraints = CA:FALSE
|
|
122
|
+
keyUsage = digitalSignature, nonRepudiation
|
|
123
|
+
extendedKeyUsage = emailProtection, codeSigning
|
|
124
|
+
crlDistributionPoints = URI:http://localhost:8080/ca.crl
|
|
125
|
+
authorityInfoAccess = @aia_info
|
|
126
|
+
|
|
127
|
+
[ aia_info ]
|
|
128
|
+
caIssuers;URI.0 = http://localhost:8080/ca.crt
|
|
129
|
+
OCSP;URI.0 = http://localhost:8080/ocsp
|
|
130
|
+
OCSP;URI.1 = http://localhost:8080/ocsp-backup
|
|
131
|
+
""" > cert-ext.cnf
|
|
132
|
+
|
|
133
|
+
# Generate certificates
|
|
134
|
+
openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 3650 -out rootCA.crt -subj "/C=US/ST=Test/L=Local/O=MyOrg/OU=CA/CN=MyRootCA"
|
|
135
|
+
openssl req -new -key signer.key -out signer.csr -subj "/C=US/ST=Test/L=Local/O=MyOrg/OU=Signing/CN=John Doe"
|
|
136
|
+
openssl x509 -req -in signer.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out signer.crt -days 365 -sha256 -extfile cert-ext.cnf
|
|
137
|
+
|
|
138
|
+
# Create OCSP responder certificate
|
|
139
|
+
if [ "$KEY_TYPE" == "rsa" ]; then
|
|
140
|
+
openssl genrsa -out ocsp.key 2048
|
|
141
|
+
elif [ "$KEY_TYPE" == "ec" ]; then
|
|
142
|
+
# Generate EC key in PKCS#8 format for compatibility with PrivateKeyInfo
|
|
143
|
+
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:$CURVE -out ocsp.key
|
|
144
|
+
elif [ "$KEY_TYPE" == "ed25519" ]; then
|
|
145
|
+
openssl genpkey -algorithm ed25519 -out ocsp.key
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
echo """
|
|
149
|
+
basicConstraints = CA:FALSE
|
|
150
|
+
keyUsage = digitalSignature
|
|
151
|
+
extendedKeyUsage = OCSPSigning
|
|
152
|
+
""" > ocsp-ext.cnf
|
|
153
|
+
|
|
154
|
+
openssl req -new -key ocsp.key -out ocsp.csr -subj "/C=US/ST=Test/L=Local/O=MyOrg/OU=OCSP/CN=OCSP Responder"
|
|
155
|
+
openssl x509 -req -in ocsp.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial \
|
|
156
|
+
-out ocsp.crt -days 10000 -sha256 -extfile ocsp-ext.cnf
|
|
157
|
+
|
|
158
|
+
# Get CRL from CA
|
|
159
|
+
openssl ca -gencrl -keyfile rootCA.key -cert rootCA.crt -out ca.crl
|
|
160
|
+
|
|
161
|
+
# create an OCSP request for signer.crt
|
|
162
|
+
openssl ocsp \
|
|
163
|
+
-issuer rootCA.crt \
|
|
164
|
+
-cert signer.crt \
|
|
165
|
+
-reqout ocsp.req
|
|
166
|
+
|
|
167
|
+
printf 'V\t360101000000Z\t76E6504D08122D5D3EA3A4A6D540BE38D1B1FB99\tunknown\t/C=US/ST=Test/L=Local/O=MyOrg/OU=Signing/CN=John Doe' > demoCA/index.txt
|
|
168
|
+
|
|
169
|
+
# create OCSP response: use the responder key (ocsp.key) and the CA index file
|
|
170
|
+
openssl ocsp \
|
|
171
|
+
-reqin ocsp.req \
|
|
172
|
+
-index "$(pwd)/demoCA/index.txt" \
|
|
173
|
+
-CA rootCA.crt \
|
|
174
|
+
-rsigner ocsp.crt \
|
|
175
|
+
-rkey ocsp.key \
|
|
176
|
+
-respout ocsp-response.der \
|
|
177
|
+
-text
|
|
178
|
+
|
|
179
|
+
# Convert DER to PEM format
|
|
180
|
+
echo "Converting files to PEM format..."
|
|
181
|
+
|
|
182
|
+
# Convert CRL to PEM format (if not already in PEM)
|
|
183
|
+
openssl crl -inform DER -in ca.crl -outform PEM -out ca.pem 2>/dev/null || cp ca.crl ca.pem
|
|
184
|
+
|
|
185
|
+
# Convert OCSP response to PEM format
|
|
186
|
+
openssl base64 -in ocsp-response.der -out ocsp-response-base64.tmp
|
|
187
|
+
echo "-----BEGIN OCSP RESPONSE-----" > ocsp-response.pem
|
|
188
|
+
cat ocsp-response-base64.tmp >> ocsp-response.pem
|
|
189
|
+
echo "-----END OCSP RESPONSE-----" >> ocsp-response.pem
|
|
190
|
+
rm ocsp-response-base64.tmp
|
|
191
|
+
|
|
192
|
+
# Create combined PEM files for easier use
|
|
193
|
+
cat signer.crt rootCA.crt > cert-chain.pem
|
|
194
|
+
cat signer.key signer.crt rootCA.crt > fullchain.pem
|
|
195
|
+
|
|
196
|
+
echo "PEM conversion complete. Generated files:"
|
|
197
|
+
echo "- ca.pem (CRL in PEM format)"
|
|
198
|
+
echo "- ocsp-response.pem (OCSP response in PEM format)"
|
|
199
|
+
echo "- cert-chain.pem (Certificate chain)"
|
|
200
|
+
echo "- fullchain.pem (Private key + certificate chain)"
|
|
201
|
+
|
|
202
|
+
# Generate TypeScript file with all PEM contents
|
|
203
|
+
echo "Generating TypeScript file..."
|
|
204
|
+
|
|
205
|
+
# Function to get PEM content as is, preserving line breaks
|
|
206
|
+
get_pem_content() {
|
|
207
|
+
cat "$1" | sed 's/\\/\\\\/g' | sed 's/`/\\`/g' | sed 's/\$/\\$/g'
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
# Get the PEM strings
|
|
211
|
+
PRIVATE_KEY=$(get_pem_content signer.key)
|
|
212
|
+
CERT=$(get_pem_content signer.crt)
|
|
213
|
+
CA_CERT=$(get_pem_content rootCA.crt)
|
|
214
|
+
CA_CRL=$(get_pem_content ca.pem)
|
|
215
|
+
OCSP_RESPONSE=$(get_pem_content ocsp-response.pem)
|
|
216
|
+
|
|
217
|
+
# Get base64 of the binary file (ocsp-response.der)
|
|
218
|
+
OCSP_RESPONSE_BASE64=$(cat ocsp-response.der | base64)
|
|
219
|
+
|
|
220
|
+
# Set variable names based on key type
|
|
221
|
+
if [ "$KEY_TYPE" == "rsa" ]; then
|
|
222
|
+
KEY_VAR="${EXPORT_PREFIX}PrivateKeyPem"
|
|
223
|
+
elif [ "$KEY_TYPE" == "ec" ]; then
|
|
224
|
+
KEY_VAR="${EXPORT_PREFIX}PrivateKeyPem"
|
|
225
|
+
elif [ "$KEY_TYPE" == "ed25519" ]; then
|
|
226
|
+
KEY_VAR="${EXPORT_PREFIX}PrivateKeyPem"
|
|
227
|
+
else
|
|
228
|
+
KEY_VAR="privateKeyPem"
|
|
229
|
+
fi
|
|
230
|
+
|
|
231
|
+
CERT_VAR="${EXPORT_PREFIX}Cert"
|
|
232
|
+
CA_CERT_VAR="${EXPORT_PREFIX}CaCert"
|
|
233
|
+
CA_CRL_VAR="${EXPORT_PREFIX}CaCrl"
|
|
234
|
+
OCSP_RESPONSE_VAR="${EXPORT_PREFIX}OcspResponse"
|
|
235
|
+
EXPORT_NAME="${EXPORT_PREFIX}SigningKeys"
|
|
236
|
+
|
|
237
|
+
# Create the TypeScript file
|
|
238
|
+
cat > "index.ts" << EOL
|
|
239
|
+
const ${KEY_VAR} = \`${PRIVATE_KEY}\`
|
|
240
|
+
|
|
241
|
+
const ${CERT_VAR} = \`${CERT}\`
|
|
242
|
+
|
|
243
|
+
const ${CA_CERT_VAR} = \`${CA_CERT}\`
|
|
244
|
+
|
|
245
|
+
const ${CA_CRL_VAR} = \`${CA_CRL}\`
|
|
246
|
+
|
|
247
|
+
const ${OCSP_RESPONSE_VAR} = \`${OCSP_RESPONSE}\`
|
|
248
|
+
|
|
249
|
+
function pemToUint8Array(pem: string | Uint8Array<ArrayBuffer>): Uint8Array<ArrayBuffer> {
|
|
250
|
+
pem = typeof pem === 'string' ? pem : new TextDecoder().decode(pem)
|
|
251
|
+
|
|
252
|
+
// Remove all headers, footers, and whitespace
|
|
253
|
+
const b64 = pem
|
|
254
|
+
.trim()
|
|
255
|
+
.replace(/-----BEGIN [^-]+-----/, '')
|
|
256
|
+
.replace(/-----END [^-]+-----/, '')
|
|
257
|
+
.replace(/\s+/g, '')
|
|
258
|
+
|
|
259
|
+
const binary = atob(b64)
|
|
260
|
+
const bytes = new Uint8Array(binary.length)
|
|
261
|
+
for (let i = 0; i < binary.length; i++) {
|
|
262
|
+
bytes[i] = binary.charCodeAt(i)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return bytes
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export const ${EXPORT_NAME} = {
|
|
269
|
+
privateKey: pemToUint8Array(${KEY_VAR}),
|
|
270
|
+
cert: pemToUint8Array(${CERT_VAR}),
|
|
271
|
+
caCert: pemToUint8Array(${CA_CERT_VAR}),
|
|
272
|
+
caCrl: pemToUint8Array(${CA_CRL_VAR}),
|
|
273
|
+
ocspResponse: pemToUint8Array(${OCSP_RESPONSE_VAR}),
|
|
274
|
+
privateKeyPem: ${KEY_VAR},
|
|
275
|
+
certPem: ${CERT_VAR},
|
|
276
|
+
caCertPem: ${CA_CERT_VAR},
|
|
277
|
+
caCrlPem: ${CA_CRL_VAR},
|
|
278
|
+
ocspResponsePem: ${OCSP_RESPONSE_VAR}
|
|
279
|
+
}
|
|
280
|
+
EOL
|
|
281
|
+
|
|
282
|
+
echo "TypeScript file (index.ts) generated successfully!"
|
|
283
|
+
|
|
284
|
+
# Clean up non-essential files
|
|
285
|
+
echo "Cleaning up temporary files..."
|
|
286
|
+
find . -type f -not -name "gen.sh" -not -name "index.ts" -delete
|
|
287
|
+
# Delete empty directories
|
|
288
|
+
find . -type d -empty -delete
|
|
289
|
+
|
|
290
|
+
echo "All done!"
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Generate all types of signing keys by calling the gen-signing-keys.sh script
|
|
3
|
+
#
|
|
4
|
+
# This script generates various types of keys:
|
|
5
|
+
# - RSA keys with different sizes (2048, 3072, 4096)
|
|
6
|
+
# - EC keys with different curves (p256, p384, p521)
|
|
7
|
+
# - ED25519 keys
|
|
8
|
+
#
|
|
9
|
+
# The keys are stored in separate directories named after their types.
|
|
10
|
+
|
|
11
|
+
# Set the base directory where all keys will be stored
|
|
12
|
+
BASE_DIR="./signing-keys"
|
|
13
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
14
|
+
GEN_SCRIPT="${SCRIPT_DIR}/gen-signing-keys.sh"
|
|
15
|
+
|
|
16
|
+
# Make sure the base directory exists
|
|
17
|
+
mkdir -p "$BASE_DIR"
|
|
18
|
+
|
|
19
|
+
echo "=== Generating all types of signing keys ==="
|
|
20
|
+
echo "Using script: $GEN_SCRIPT"
|
|
21
|
+
echo "Output directory: $BASE_DIR"
|
|
22
|
+
|
|
23
|
+
# Function to generate keys and handle errors
|
|
24
|
+
generate_keys() {
|
|
25
|
+
local key_type=$1
|
|
26
|
+
local key_size=$2
|
|
27
|
+
local output_dir="${BASE_DIR}/${key_type}"
|
|
28
|
+
|
|
29
|
+
if [ -n "$key_size" ]; then
|
|
30
|
+
output_dir="${output_dir}-${key_size}"
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
echo "Generating $key_type keys${key_size:+ with size/curve $key_size}..."
|
|
34
|
+
|
|
35
|
+
mkdir -p "$output_dir"
|
|
36
|
+
|
|
37
|
+
if [ -n "$key_size" ]; then
|
|
38
|
+
"$GEN_SCRIPT" --output-dir="$output_dir" --key-type="$key_type" --key-size="$key_size"
|
|
39
|
+
else
|
|
40
|
+
"$GEN_SCRIPT" --output-dir="$output_dir" --key-type="$key_type"
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
if [ $? -eq 0 ]; then
|
|
44
|
+
echo "✅ Successfully generated $key_type keys${key_size:+ with size/curve $key_size} in $output_dir"
|
|
45
|
+
else
|
|
46
|
+
echo "❌ Failed to generate $key_type keys${key_size:+ with size/curve $key_size}"
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
echo ""
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# Generate RSA keys with different sizes
|
|
53
|
+
generate_keys "rsa" "2048"
|
|
54
|
+
generate_keys "rsa" "3072"
|
|
55
|
+
generate_keys "rsa" "4096"
|
|
56
|
+
|
|
57
|
+
# Generate EC keys with different curves
|
|
58
|
+
generate_keys "ec" "p256"
|
|
59
|
+
generate_keys "ec" "p384"
|
|
60
|
+
generate_keys "ec" "p521"
|
|
61
|
+
|
|
62
|
+
# Generate ED25519 keys
|
|
63
|
+
generate_keys "ed25519"
|
|
64
|
+
|
|
65
|
+
echo "=== Key generation complete ==="
|
|
66
|
+
echo "All keys have been generated in $BASE_DIR"
|
|
67
|
+
echo "Summary of generated keys:"
|
|
68
|
+
find "$BASE_DIR" -maxdepth 1 -type d -not -path "$BASE_DIR" | sort | while read -r dir; do
|
|
69
|
+
echo "- $(basename "$dir")"
|
|
70
|
+
done
|