files: PDFs, text, code, tables and certificates open in place

The file pane decided what to show by MIME type, and uploads keep a
type only when it cannot execute, so a .md, a .json or a .pem arrived
as application/octet-stream and got "no preview". It now goes by name
first (preview.ts), and a file with a name it does not know has its
first 8 KiB looked at: no NUL bytes and valid UTF-8 means text.

- text and code: the first 256 KiB with line numbers, as before
- markdown: rendered through the same sanitiser as documents, with a
  source toggle
- CSV/TSV: a table of the first 500 rows, quotes respected
- PDF: the browser's own viewer in a frame. Only a PDF is served
  without the sandbox CSP (Chrome's viewer will not start under it),
  with frame-ancestors 'self' and X-Frame-Options SAMEORIGIN; the app
  CSP allows same-origin frames.
- certificates, keys, keystores: POST /api/nodes/{id}/inspect reads
  PEM (chains, keys, requests, public keys), DER and PKCS#12 on the
  server, in pure Rust, including the RC2/3DES keystores that
  openssl -legacy and older keychains write. It answers with subject,
  issuer, validity, key, names, usage and fingerprints, and pairs each
  private key with its certificate by public key. A password is asked
  for only when needed, used for that one answer and never stored;
  no key material is ever sent back. Sealed files are read through the
  vault, and only while it is open.

The tests read throwaway fixtures under src/routes/testdata, made with
the openssl commands listed beside them; .gitignore lets their .pem
files past a global *.pem rule.
This commit is contained in:
nvrl committed 2026-10-20 09:57:09 +02:00
1 parent 9749c500cf
commit 1ae0665206
21 files changed
+1571 -22

No files matched your search

Generated
+249 -2
View File
@@ -154,6 +154,45 @@ dependencies = [
"password-hash 0.6.1",
]
[[package]]
name = "asn1-rs"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8"
dependencies = [
"asn1-rs-derive",
"asn1-rs-impl",
"displaydoc",
"nom 7.1.3",
"num-traits",
"rusticata-macros",
"thiserror 2.0.20",
"time",
]
[[package]]
name = "asn1-rs-derive"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
"synstructure",
]
[[package]]
name = "asn1-rs-impl"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "async-broadcast"
version = "0.7.2"
@@ -772,6 +811,18 @@ version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a"
[[package]]
name = "cms"
version = "0.3.0-pre.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "758d5272932ff167e96ec9641a04d96ab1901cccc5ea9f47c15b4cbaa6f9ea53"
dependencies = [
"const-oid 0.10.2",
"der 0.8.2",
"spki 0.8.0",
"x509-cert",
]
[[package]]
name = "combine"
version = "4.6.8"
@@ -1186,10 +1237,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a878c850e9e421b20262e9b41f9c860e4785fa07541c266b62ff9d1ef998a80a"
dependencies = [
"const-oid 0.10.2",
"der_derive",
"flagset",
"pem-rfc7468 1.0.0",
"zeroize",
]
[[package]]
name = "der-parser"
version = "10.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6"
dependencies = [
"asn1-rs",
"displaydoc",
"nom 7.1.3",
"num-bigint 0.4.8",
"num-traits",
"rusticata-macros",
]
[[package]]
name = "der_derive"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59600e2c2d636fde9b65e99cc6445ac770c63d3628195ff39932b8d6d7409903"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "deranged"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e9de72ce2ad1f90dc62fa25f0f430ef85eb4b0d8fa0be4f30373bc40a21d28e"
[[package]]
name = "derive_arbitrary"
version = "1.4.2"
@@ -1662,6 +1746,12 @@ version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d"
[[package]]
name = "flagset"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe"
[[package]]
name = "flate2"
version = "1.1.10"
@@ -2520,7 +2610,7 @@ dependencies = [
"httpdate",
"idna",
"mime",
"nom",
"nom 8.0.0",
"percent-encoding",
"quoted_printable",
"rustls",
@@ -2685,6 +2775,12 @@ dependencies = [
"unicase",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.9.1"
@@ -2766,6 +2862,8 @@ dependencies = [
"keyring",
"lettre",
"mime_guess",
"p12-keystore",
"pkcs8 0.11.0",
"proton-drive-rs",
"proton-sdk",
"rand 0.10.2",
@@ -2775,8 +2873,10 @@ dependencies = [
"russh",
"russh-sftp",
"rust-embed",
"sec1 0.8.1",
"serde",
"serde_json",
"sha1 0.10.7",
"sha2 0.10.9",
"sqlx",
"subtle",
@@ -2788,6 +2888,7 @@ dependencies = [
"tracing",
"tracing-subscriber",
"uuid",
"x509-parser",
"zeroize",
"zip",
]
@@ -2817,6 +2918,16 @@ dependencies = [
"libc",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "nom"
version = "8.0.0"
@@ -2897,6 +3008,12 @@ dependencies = [
"num-traits",
]
[[package]]
name = "num-conv"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
[[package]]
name = "num-integer"
version = "0.1.47"
@@ -2970,6 +3087,15 @@ dependencies = [
"subtle",
]
[[package]]
name = "oid-registry"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7"
dependencies = [
"asn1-rs",
]
[[package]]
name = "once_cell"
version = "1.21.4"
@@ -3004,6 +3130,29 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "p12-keystore"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c2ed4fa0ec7ec58b657d4184680c57da9e203e97771d29a566028869a5193e0"
dependencies = [
"cbc 0.2.1",
"cms",
"der 0.8.2",
"des 0.9.0",
"hex",
"hmac 0.13.0",
"pkcs12",
"pkcs5",
"pkcs8 0.11.0",
"rand 0.10.2",
"rc2",
"sha1 0.11.0",
"sha2 0.11.0",
"thiserror 2.0.20",
"x509-parser",
]
[[package]]
name = "p256"
version = "0.13.2"
@@ -3231,7 +3380,7 @@ dependencies = [
"log",
"md-5",
"memchr",
"nom",
"nom 8.0.0",
"num-bigint-dig",
"num-traits",
"num_enum",
@@ -3322,6 +3471,21 @@ dependencies = [
"spki 0.8.0",
]
[[package]]
name = "pkcs12"
version = "0.2.0-pre.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8059bd79345d1d1c125656025814704bab5cdd204b1cefd5fab59b0fdd7476d2"
dependencies = [
"cms",
"const-oid 0.10.2",
"der 0.8.2",
"digest 0.11.3",
"spki 0.8.0",
"x509-cert",
"zeroize",
]
[[package]]
name = "pkcs5"
version = "0.8.1"
@@ -3332,6 +3496,7 @@ dependencies = [
"aes-gcm 0.11.1",
"cbc 0.2.1",
"der 0.8.2",
"des 0.9.0",
"pbkdf2",
"rand_core 0.10.1",
"scrypt",
@@ -3431,6 +3596,12 @@ dependencies = [
"zerovec",
]
[[package]]
name = "powerfmt"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a6394b9e965e73d0a289ee54f589087e2c676aedf60885baf52c76b771e4958"
[[package]]
name = "ppv-lite86"
version = "0.2.21"
@@ -3695,6 +3866,15 @@ dependencies = [
"rand_core 0.10.1",
]
[[package]]
name = "rc2"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ceda21af1ae61033b63175653a1af86cae399d79cd03ca80ba347eb3a6c4a7fe"
dependencies = [
"cipher 0.5.2",
]
[[package]]
name = "redox_syscall"
version = "0.5.18"
@@ -4070,6 +4250,15 @@ dependencies = [
"semver",
]
[[package]]
name = "rusticata-macros"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632"
dependencies = [
"nom 7.1.3",
]
[[package]]
name = "rustix"
version = "1.1.4"
@@ -5034,6 +5223,36 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "time"
version = "0.3.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134"
dependencies = [
"deranged",
"num-conv",
"powerfmt",
"serde_core",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
[[package]]
name = "time-macros"
version = "0.2.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85"
dependencies = [
"num-conv",
"time-core",
]
[[package]]
name = "tinystr"
version = "0.8.4"
@@ -5953,6 +6172,34 @@ dependencies = [
"zeroize",
]
[[package]]
name = "x509-cert"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "105ef4642d9cb137ef83d623d0e4bf08b8adf69e9918ca904a174adb6d3d038b"
dependencies = [
"const-oid 0.10.2",
"der 0.8.2",
"spki 0.8.0",
]
[[package]]
name = "x509-parser"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202"
dependencies = [
"asn1-rs",
"data-encoding",
"der-parser",
"lazy_static",
"nom 7.1.3",
"oid-registry",
"rusticata-macros",
"thiserror 2.0.20",
"time",
]
[[package]]
name = "xdg-home"
version = "1.3.0"