@mostajs/pdf-sign-ui 0.3.3 → 0.5.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/CHANGELOG.md +31 -0
- package/README.md +22 -0
- package/llms.txt +6 -0
- package/package.json +3 -3
- package/src/detached-html.js +277 -0
- package/src/html.js +105 -0
- package/src/index.js +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,37 @@
|
|
|
4
4
|
|
|
5
5
|
Le format suit [Keep a Changelog](https://keepachangelog.com/fr/) ; versionnage [SemVer](https://semver.org/lang/fr/).
|
|
6
6
|
|
|
7
|
+
|
|
8
|
+
## 0.5.0 — 12/10/2026
|
|
9
|
+
|
|
10
|
+
- **La vue de la signature séparée (XAdES), en sept langues** — celle du Journal officiel de l'UE, dont les PDF ne
|
|
11
|
+
portent aucune signature : elle est dans un XML à côté.
|
|
12
|
+
- `detachedFormHtml({ lang, action })` : le formulaire de dépôt (`signature` : le XML ; `documents` : un ou
|
|
13
|
+
plusieurs fichiers à confronter) ;
|
|
14
|
+
- `detachedReportHtml(résultat, { lang })` : le verdict, le signataire (et ce que son certificat déclare :
|
|
15
|
+
qualifié, cachet ou personne, QSCD — en disant que la liste de confiance de l'UE n'est pas consultée), les dates
|
|
16
|
+
déclarée et attestée, chaque horodatage (signature, archive), les données de longue durée, chaque document
|
|
17
|
+
déposé (couvert ou non, et sous quel nom), la liste des documents couverts ;
|
|
18
|
+
- `detachedText`, `DETACHED_TEXTS`.
|
|
19
|
+
- Un jeton d'horodatage qui ne porte pas sur la signature n'est jamais affiché « valide ».
|
|
20
|
+
- La vue attend le résultat de `verifyDetached` (@mostajs/pdf-sign ≥ 0.8.0) ; mêmes classes que `VERIFY_CSS`.
|
|
21
|
+
- Dépendance « pair » `@mostajs/pdf-sign` élargie jusqu'à ^0.8.0 (elle s'arrêtait à ^0.5.0 alors que 0.4.0 sert
|
|
22
|
+
avec 0.7.0).
|
|
23
|
+
- Essais D-1 à D-4 ; `html.test.mjs` (0.4.0), qui n'avait pas été commité, l'est.
|
|
24
|
+
|
|
25
|
+
## 0.4.0 — 11/10/2026
|
|
26
|
+
|
|
27
|
+
- **La vue HTML de la vérification d'un PDF, en sept langues** (fr, en, ar, de, es, it, zh) :
|
|
28
|
+
- `verifyFormHtml({ lang, action })` donne le formulaire de dépôt (`pdf`, `motdepasse`) ;
|
|
29
|
+
- `verifyReportHtml(résultat, { lang })` donne le rapport : le fichier, puis chaque signature et chaque
|
|
30
|
+
horodatage de document, avec les dates déclarée et attestée, l'horodateur et le certificat ;
|
|
31
|
+
- `verifyText`, `VERIFY_TEXTS`, `VERIFY_LANGS` donnent les textes ;
|
|
32
|
+
- `VERIFY_CSS` est une feuille minimale.
|
|
33
|
+
- Tout est échappé. « Présente » n'est jamais affiché « valide ».
|
|
34
|
+
- La vue attend le résultat de `verifyPdfBytes` (@mostajs/pdf-sign ≥ 0.7.0).
|
|
35
|
+
- Écrite d'abord dans mdseal (fr, en, ar), demandée par qatrax.dev : elle descend ici (R-G).
|
|
36
|
+
- Essais V-1 à V-4 ; `npm test` lance aussi `html.test.mjs`.
|
|
37
|
+
|
|
7
38
|
## [0.3.3] — 2026-10-07
|
|
8
39
|
|
|
9
40
|
### Modifié
|
package/README.md
CHANGED
|
@@ -90,3 +90,25 @@ Le `service` est le même contrat que le contrôleur (`signPdf`/`verifyPdf`/`det
|
|
|
90
90
|
npm test # logique headless (@mostajs/mjs-unit)
|
|
91
91
|
npm run test:react # rendu SSR des composants (react + react-dom requis)
|
|
92
92
|
```
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
## Vue HTML de la vérification (0.4.0)
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
import { verifyPdfBytes } from "@mostajs/pdf-sign"; // ≥ 0.7.0
|
|
99
|
+
import { verifyFormHtml, verifyReportHtml, VERIFY_CSS } from "@mostajs/pdf-sign-ui";
|
|
100
|
+
const corps = `<style>${VERIFY_CSS}</style>${verifyFormHtml({ lang: "zh" })}${verifyReportHtml(verifyPdfBytes(octets), { lang: "zh" })}`;
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Sept langues (fr, en, ar, de, es, it, zh) ; fragments sans gabarit, tout échappé.
|
|
104
|
+
|
|
105
|
+
## Vue de la signature séparée — XAdES (0.5.0)
|
|
106
|
+
|
|
107
|
+
Le Journal officiel de l'UE publie ses PDF sans signature : elle est dans un XML à côté.
|
|
108
|
+
|
|
109
|
+
```js
|
|
110
|
+
import { verifyDetached } from "@mostajs/pdf-sign"; // ≥ 0.8.0
|
|
111
|
+
import { detachedFormHtml, detachedReportHtml, VERIFY_CSS } from "@mostajs/pdf-sign-ui";
|
|
112
|
+
const r = verifyDetached({ xml, files: [{ name: "reglement.pdf", bytes: pdf }] });
|
|
113
|
+
const corps = `<style>${VERIFY_CSS}</style>${detachedFormHtml({ lang: "fr" })}${detachedReportHtml(r, { lang: "fr" })}`;
|
|
114
|
+
```
|
package/llms.txt
CHANGED
|
@@ -54,3 +54,9 @@ Composants en React.createElement (pas de JSX à transpiler ; bundler pour les .
|
|
|
54
54
|
- buildSignRequest (0.3.2) : transmet timestampUrl ET les identifiants de l horodateur — timestampHeaders
|
|
55
55
|
(ex. { "X-API-KEY": cle }), timestampAuth { user, password }, timestampTimeout (s). Jeu de clés FERMÉ :
|
|
56
56
|
toute option absente est PERDUE (avertissement), d où leur ajout explicite.
|
|
57
|
+
|
|
58
|
+
## 0.5.0 — SIGNATURE SÉPARÉE (XAdES)
|
|
59
|
+
- detachedFormHtml({ lang, action }) : multipart `signature` (XML) + `documents` (multiple).
|
|
60
|
+
- detachedReportHtml(verifyDetached(...), { lang }) : verdict (altéré si une partie SIGNÉE a changé, invalide si la
|
|
61
|
+
valeur de signature ne correspond pas), signataire + QCStatements DÉCLARÉS, dates, jetons (imprintOk false ⇒ NON
|
|
62
|
+
valide), documents couverts (coveredAs : le nom dans la signature), manifeste. DETACHED_TEXTS : 7 langues.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mostajs/pdf-sign-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Couche de présentation de @mostajs/pdf-sign — logique headless (validation du formulaire de signature, préréglages de documents officiels/SIH, view-models de vérification, contrôleur). Framework-agnostique, zéro dépendance.",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"author": "Dr Hamid MADANI <drmdh@msn.com>",
|
|
@@ -17,12 +17,12 @@
|
|
|
17
17
|
"CHANGELOG.md"
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
|
-
"test": "node test-scripts/headless.test.mjs",
|
|
20
|
+
"test": "node test-scripts/headless.test.mjs && node test-scripts/html.test.mjs && node test-scripts/detached-html.test.mjs",
|
|
21
21
|
"test:react": "node --experimental-loader ./test-scripts/jsx-loader.mjs test-scripts/react.test.mjs",
|
|
22
22
|
"example": "node examples/verify-viewmodel.mjs"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
|
-
"@mostajs/pdf-sign": "^0.3.0 || ^0.4.0 || ^0.5.0",
|
|
25
|
+
"@mostajs/pdf-sign": "^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0 || ^0.8.0",
|
|
26
26
|
"react": ">=18"
|
|
27
27
|
},
|
|
28
28
|
"peerDependenciesMeta": {
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
// @mostajs/pdf-sign-ui 0.5.0 — LA VUE DE LA SIGNATURE SÉPARÉE (XAdES), en sept langues (12/10/2026).
|
|
2
|
+
// Le Journal officiel de l'UE publie ses PDF sans signature : elle est dans un XML à côté, dont le manifeste nomme
|
|
3
|
+
// chaque document par son empreinte. Cette vue rend le résultat de `verifyDetached` (@mostajs/pdf-sign ≥ 0.8.0).
|
|
4
|
+
// Fragments HTML sans gabarit, tout échappé ; mêmes classes que la vue d'un PDF (VERIFY_CSS).
|
|
5
|
+
// Author: Dr Hamid MADANI <drmdh@msn.com> · AGPL-3.0-or-later
|
|
6
|
+
|
|
7
|
+
/** Les textes, par langue (le français fait référence). */
|
|
8
|
+
export const DETACHED_TEXTS = Object.freeze({
|
|
9
|
+
fr: {
|
|
10
|
+
titre: "Vérifier une signature séparée (XML)",
|
|
11
|
+
aide: "Certains documents officiels — le Journal officiel de l’UE, par exemple — sont publiés sans signature dans le PDF : elle est dans un fichier XML à côté. Déposez ce XML et, si vous le souhaitez, le ou les documents : chacun est confronté aux empreintes que la signature couvre. Rien n’est conservé.",
|
|
12
|
+
signatureXml: "Signature (fichier XML)", documents: "Documents à confronter (facultatif, plusieurs possibles)", bouton: "Vérifier",
|
|
13
|
+
verdict_valide: "Signature valide : rien de ce qu’elle couvre n’a changé", verdict_altere: "ALTÉRÉE : une partie signée a été modifiée",
|
|
14
|
+
verdict_invalide: "Invalide : la signature ne correspond pas à son contenu", niveau: "Niveau", format: "Format",
|
|
15
|
+
signataire: "Signataire", emetteur: "Émetteur", validite: "Validité", numero: "Numéro de série",
|
|
16
|
+
qualifieOui: "se déclare qualifié (eIDAS)", qscd: "clé sur dispositif qualifié (QSCD)", type_eseal: "cachet d’organisation",
|
|
17
|
+
type_esign: "signature de personne", type_web: "authentification de site",
|
|
18
|
+
qualifieAide: "déclaration du certificat ; la liste de confiance de l’UE n’est pas consultée ici",
|
|
19
|
+
autoSigne: "auto-signé : personne d’autre que le signataire ne se porte garant de son identité",
|
|
20
|
+
certificatAnnonce: "Certificat annoncé par la signature", concorde: "concorde", concordePas: "NE concorde PAS",
|
|
21
|
+
dateDeclaree: "Date déclarée", dateDeclareeAide: "par le signataire lui-même — sans horodatage, elle peut être antidatée",
|
|
22
|
+
dateAttestee: "Date attestée", dateAttesteeAide: "par un horodateur tiers (RFC 3161)",
|
|
23
|
+
horodatageSignature: "Horodatage de la signature", horodatageArchive: "Horodatage d’archive (longue durée)", horodatageAutre: "Autre horodatage",
|
|
24
|
+
horodateur: "Horodateur", politique: "Politique d’horodatage (OID)", jeton: "Jeton",
|
|
25
|
+
jetonOk: "intact, valide, et porte bien sur la signature", jetonSansPortee: "intact et valide (portée non recalculée)", jetonKo: "NON valide",
|
|
26
|
+
aucunHorodatage: "Aucun horodatage : la date de signature n’est attestée par personne.",
|
|
27
|
+
longueDuree: "Données de validation à long terme", longueDureeValeur: "{c} certificat(s), {o} réponse(s) OCSP, {l} liste(s) de révocation",
|
|
28
|
+
documentsFournis: "Documents déposés", taille: "Taille", octets: "{n} octets", empreinte: "Empreinte SHA-256",
|
|
29
|
+
couvert: "COUVERT par la signature, sous le nom « {nom} »",
|
|
30
|
+
nonCouvert: "NON couvert : ce fichier n’est pas l’un des documents signés, ou il a été modifié",
|
|
31
|
+
aucunDocument: "Aucun document déposé : seule la signature a été vérifiée. Déposez le document pour savoir s’il est couvert.",
|
|
32
|
+
manifeste: "Documents couverts par la signature : {n}",
|
|
33
|
+
pasXml: "Ce fichier n’est pas une signature XML lisible.", aucunFichier: "Aucun fichier de signature fourni.",
|
|
34
|
+
moteurAbsent: "Vérification indisponible sur ce serveur (moteur Python absent).", erreur: "Motif du refus",
|
|
35
|
+
},
|
|
36
|
+
en: {
|
|
37
|
+
titre: "Verify a detached signature (XML)",
|
|
38
|
+
aide: "Some official documents — the EU Official Journal, for instance — are published without a signature inside the PDF: it sits in an XML file alongside. Upload that XML and, if you wish, the document(s): each one is matched against the fingerprints the signature covers. Nothing is kept.",
|
|
39
|
+
signatureXml: "Signature (XML file)", documents: "Documents to match (optional, several allowed)", bouton: "Verify",
|
|
40
|
+
verdict_valide: "Valid signature: nothing it covers has changed", verdict_altere: "ALTERED: a signed part has been modified",
|
|
41
|
+
verdict_invalide: "Invalid: the signature does not match its content", niveau: "Level", format: "Format",
|
|
42
|
+
signataire: "Signer", emetteur: "Issuer", validite: "Validity", numero: "Serial number",
|
|
43
|
+
qualifieOui: "declares itself qualified (eIDAS)", qscd: "key on a qualified device (QSCD)", type_eseal: "organisation seal",
|
|
44
|
+
type_esign: "personal signature", type_web: "website authentication",
|
|
45
|
+
qualifieAide: "as declared by the certificate; the EU trusted list is not consulted here",
|
|
46
|
+
autoSigne: "self-signed: nobody but the signer vouches for their identity",
|
|
47
|
+
certificatAnnonce: "Certificate announced by the signature", concorde: "matches", concordePas: "does NOT match",
|
|
48
|
+
dateDeclaree: "Declared date", dateDeclareeAide: "by the signer themself — without a timestamp, it can be backdated",
|
|
49
|
+
dateAttestee: "Attested date", dateAttesteeAide: "by a third-party timestamping authority (RFC 3161)",
|
|
50
|
+
horodatageSignature: "Signature timestamp", horodatageArchive: "Archive timestamp (long term)", horodatageAutre: "Other timestamp",
|
|
51
|
+
horodateur: "Timestamping authority", politique: "Timestamp policy (OID)", jeton: "Token",
|
|
52
|
+
jetonOk: "intact, valid, and covering the signature", jetonSansPortee: "intact and valid (coverage not recomputed)", jetonKo: "NOT valid",
|
|
53
|
+
aucunHorodatage: "No timestamp: nobody attests the signing date.",
|
|
54
|
+
longueDuree: "Long-term validation data", longueDureeValeur: "{c} certificate(s), {o} OCSP response(s), {l} revocation list(s)",
|
|
55
|
+
documentsFournis: "Uploaded documents", taille: "Size", octets: "{n} bytes", empreinte: "SHA-256 fingerprint",
|
|
56
|
+
couvert: "COVERED by the signature, under the name “{nom}”",
|
|
57
|
+
nonCouvert: "NOT covered: this file is not one of the signed documents, or it has been modified",
|
|
58
|
+
aucunDocument: "No document uploaded: only the signature was checked. Upload the document to find out whether it is covered.",
|
|
59
|
+
manifeste: "Documents covered by the signature: {n}",
|
|
60
|
+
pasXml: "This file is not a readable XML signature.", aucunFichier: "No signature file provided.",
|
|
61
|
+
moteurAbsent: "Verification is unavailable on this server (Python engine missing).", erreur: "Reason for rejection",
|
|
62
|
+
},
|
|
63
|
+
ar: {
|
|
64
|
+
titre: "التحقّق من توقيع منفصل (XML)",
|
|
65
|
+
aide: "تُنشر بعض الوثائق الرسمية — كالجريدة الرسمية للاتحاد الأوروبي — دون توقيع داخل ملف PDF: يوجد التوقيع في ملف XML مرافق. أودِع ملف XML هذا، وإن شئت الوثيقة أو الوثائق: تُقارَن كلّ واحدة بالبصمات التي يغطّيها التوقيع. لا يُحتفظ بشيء.",
|
|
66
|
+
signatureXml: "التوقيع (ملف XML)", documents: "الوثائق المراد مقارنتها (اختياري، يمكن أكثر من واحدة)", bouton: "تحقّق",
|
|
67
|
+
verdict_valide: "توقيع صالح: لم يتغيّر شيء ممّا يغطّيه", verdict_altere: "مُعدَّل: عُدّل جزء موقَّع",
|
|
68
|
+
verdict_invalide: "غير صالح: التوقيع لا يطابق محتواه", niveau: "المستوى", format: "الصيغة",
|
|
69
|
+
signataire: "الموقِّع", emetteur: "المُصدِر", validite: "الصلاحية", numero: "الرقم التسلسلي",
|
|
70
|
+
qualifieOui: "تُصرّح بأنها مؤهَّلة (eIDAS)", qscd: "مفتاح على جهاز مؤهَّل (QSCD)", type_eseal: "ختم مؤسّسة",
|
|
71
|
+
type_esign: "توقيع شخص", type_web: "مصادقة موقع",
|
|
72
|
+
qualifieAide: "وفق تصريح الشهادة؛ لا تُراجَع هنا قائمة الثقة الأوروبية",
|
|
73
|
+
autoSigne: "موقَّعة ذاتيًّا: لا أحد غير الموقِّع يضمن هويّته",
|
|
74
|
+
certificatAnnonce: "الشهادة المعلَنة في التوقيع", concorde: "مطابقة", concordePas: "غير مطابقة",
|
|
75
|
+
dateDeclaree: "التاريخ المُصرَّح به", dateDeclareeAide: "من الموقِّع نفسه — دون ختم زمني يمكن تقديمه",
|
|
76
|
+
dateAttestee: "التاريخ المشهود", dateAttesteeAide: "من جهة ختم زمني مستقلّة (RFC 3161)",
|
|
77
|
+
horodatageSignature: "الختم الزمني للتوقيع", horodatageArchive: "ختم زمني أرشيفي (طويل الأمد)", horodatageAutre: "ختم زمني آخر",
|
|
78
|
+
horodateur: "جهة الختم الزمني", politique: "سياسة الختم الزمني (OID)", jeton: "الرمز",
|
|
79
|
+
jetonOk: "سليم وصالح، ويغطّي التوقيع فعلًا", jetonSansPortee: "سليم وصالح (لم يُعَد حساب ما يغطّيه)", jetonKo: "غير صالح",
|
|
80
|
+
aucunHorodatage: "لا ختم زمني: لا أحد يشهد على تاريخ التوقيع.",
|
|
81
|
+
longueDuree: "بيانات التحقّق طويل الأمد", longueDureeValeur: "{c} شهادة، {o} ردّ OCSP، {l} قائمة إلغاء",
|
|
82
|
+
documentsFournis: "الوثائق المودَعة", taille: "الحجم", octets: "{n} بايت", empreinte: "بصمة SHA-256",
|
|
83
|
+
couvert: "يغطّيه التوقيع، باسم «{nom}»",
|
|
84
|
+
nonCouvert: "غير مغطّى: هذا الملف ليس من الوثائق الموقَّعة، أو أنه عُدّل",
|
|
85
|
+
aucunDocument: "لم تُودَع أيّ وثيقة: تمّ التحقّق من التوقيع وحده. أودِع الوثيقة لتعرف إن كان التوقيع يغطّيها.",
|
|
86
|
+
manifeste: "الوثائق التي يغطّيها التوقيع: {n}",
|
|
87
|
+
pasXml: "هذا الملف ليس توقيع XML مقروءًا.", aucunFichier: "لم يُقدَّم أيّ ملف توقيع.",
|
|
88
|
+
moteurAbsent: "التحقّق غير متاح على هذا الخادم (محرّك Python غائب).", erreur: "سبب الرفض",
|
|
89
|
+
},
|
|
90
|
+
de: {
|
|
91
|
+
titre: "Eine abgesetzte Signatur prüfen (XML)",
|
|
92
|
+
aide: "Manche amtlichen Dokumente — etwa das Amtsblatt der EU — werden ohne Signatur im PDF veröffentlicht: Sie liegt in einer XML-Datei daneben. Laden Sie diese XML-Datei hoch und, wenn Sie möchten, das oder die Dokumente: Jedes wird mit den Fingerabdrücken abgeglichen, die die Signatur abdeckt. Nichts wird gespeichert.",
|
|
93
|
+
signatureXml: "Signatur (XML-Datei)", documents: "Abzugleichende Dokumente (optional, mehrere möglich)", bouton: "Prüfen",
|
|
94
|
+
verdict_valide: "Gültige Signatur: Nichts von dem, was sie abdeckt, wurde geändert", verdict_altere: "VERÄNDERT: Ein signierter Teil wurde geändert",
|
|
95
|
+
verdict_invalide: "Ungültig: Die Signatur passt nicht zu ihrem Inhalt", niveau: "Stufe", format: "Format",
|
|
96
|
+
signataire: "Unterzeichner", emetteur: "Aussteller", validite: "Gültigkeit", numero: "Seriennummer",
|
|
97
|
+
qualifieOui: "erklärt sich als qualifiziert (eIDAS)", qscd: "Schlüssel auf qualifizierter Einheit (QSCD)", type_eseal: "Siegel einer Organisation",
|
|
98
|
+
type_esign: "Signatur einer Person", type_web: "Website-Authentifizierung",
|
|
99
|
+
qualifieAide: "laut Zertifikat; die EU-Vertrauensliste wird hier nicht abgefragt",
|
|
100
|
+
autoSigne: "selbstsigniert: Niemand außer dem Unterzeichner bürgt für seine Identität",
|
|
101
|
+
certificatAnnonce: "Von der Signatur angekündigtes Zertifikat", concorde: "stimmt überein", concordePas: "stimmt NICHT überein",
|
|
102
|
+
dateDeclaree: "Angegebenes Datum", dateDeclareeAide: "vom Unterzeichner selbst — ohne Zeitstempel kann es rückdatiert sein",
|
|
103
|
+
dateAttestee: "Bestätigtes Datum", dateAttesteeAide: "durch einen unabhängigen Zeitstempeldienst (RFC 3161)",
|
|
104
|
+
horodatageSignature: "Zeitstempel der Signatur", horodatageArchive: "Archivzeitstempel (Langzeit)", horodatageAutre: "Weiterer Zeitstempel",
|
|
105
|
+
horodateur: "Zeitstempeldienst", politique: "Zeitstempelrichtlinie (OID)", jeton: "Token",
|
|
106
|
+
jetonOk: "unversehrt, gültig und auf die Signatur bezogen", jetonSansPortee: "unversehrt und gültig (Bezug nicht nachgerechnet)", jetonKo: "NICHT gültig",
|
|
107
|
+
aucunHorodatage: "Kein Zeitstempel: Niemand bestätigt das Signaturdatum.",
|
|
108
|
+
longueDuree: "Daten zur Langzeitvalidierung", longueDureeValeur: "{c} Zertifikat(e), {o} OCSP-Antwort(en), {l} Sperrliste(n)",
|
|
109
|
+
documentsFournis: "Hochgeladene Dokumente", taille: "Größe", octets: "{n} Bytes", empreinte: "SHA-256-Fingerabdruck",
|
|
110
|
+
couvert: "Von der Signatur ABGEDECKT, unter dem Namen „{nom}“",
|
|
111
|
+
nonCouvert: "NICHT abgedeckt: Diese Datei gehört nicht zu den signierten Dokumenten oder wurde geändert",
|
|
112
|
+
aucunDocument: "Kein Dokument hochgeladen: Nur die Signatur wurde geprüft. Laden Sie das Dokument hoch, um zu erfahren, ob es abgedeckt ist.",
|
|
113
|
+
manifeste: "Von der Signatur abgedeckte Dokumente: {n}",
|
|
114
|
+
pasXml: "Diese Datei ist keine lesbare XML-Signatur.", aucunFichier: "Keine Signaturdatei übermittelt.",
|
|
115
|
+
moteurAbsent: "Die Prüfung ist auf diesem Server nicht verfügbar (Python-Modul fehlt).", erreur: "Ablehnungsgrund",
|
|
116
|
+
},
|
|
117
|
+
es: {
|
|
118
|
+
titre: "Verificar una firma separada (XML)",
|
|
119
|
+
aide: "Algunos documentos oficiales — el Diario Oficial de la UE, por ejemplo — se publican sin firma dentro del PDF: está en un archivo XML aparte. Cargue ese XML y, si lo desea, el documento o los documentos: cada uno se coteja con las huellas que cubre la firma. No se conserva nada.",
|
|
120
|
+
signatureXml: "Firma (archivo XML)", documents: "Documentos que cotejar (opcional, se admiten varios)", bouton: "Verificar",
|
|
121
|
+
verdict_valide: "Firma válida: nada de lo que cubre ha cambiado", verdict_altere: "ALTERADA: se ha modificado una parte firmada",
|
|
122
|
+
verdict_invalide: "No válida: la firma no corresponde a su contenido", niveau: "Nivel", format: "Formato",
|
|
123
|
+
signataire: "Firmante", emetteur: "Emisor", validite: "Validez", numero: "Número de serie",
|
|
124
|
+
qualifieOui: "se declara cualificado (eIDAS)", qscd: "clave en dispositivo cualificado (QSCD)", type_eseal: "sello de organización",
|
|
125
|
+
type_esign: "firma de persona", type_web: "autenticación de sitio web",
|
|
126
|
+
qualifieAide: "según declara el certificado; aquí no se consulta la lista de confianza de la UE",
|
|
127
|
+
autoSigne: "autofirmado: nadie salvo el firmante responde de su identidad",
|
|
128
|
+
certificatAnnonce: "Certificado anunciado por la firma", concorde: "coincide", concordePas: "NO coincide",
|
|
129
|
+
dateDeclaree: "Fecha declarada", dateDeclareeAide: "por el propio firmante — sin sello de tiempo, puede estar antedatada",
|
|
130
|
+
dateAttestee: "Fecha acreditada", dateAttesteeAide: "por una autoridad de sellado de tiempo independiente (RFC 3161)",
|
|
131
|
+
horodatageSignature: "Sello de tiempo de la firma", horodatageArchive: "Sello de tiempo de archivo (largo plazo)", horodatageAutre: "Otro sello de tiempo",
|
|
132
|
+
horodateur: "Autoridad de sellado de tiempo", politique: "Política de sellado de tiempo (OID)", jeton: "Sello",
|
|
133
|
+
jetonOk: "íntegro, válido y referido a la firma", jetonSansPortee: "íntegro y válido (alcance no recalculado)", jetonKo: "NO válido",
|
|
134
|
+
aucunHorodatage: "Sin sello de tiempo: nadie acredita la fecha de la firma.",
|
|
135
|
+
longueDuree: "Datos de validación a largo plazo", longueDureeValeur: "{c} certificado(s), {o} respuesta(s) OCSP, {l} lista(s) de revocación",
|
|
136
|
+
documentsFournis: "Documentos cargados", taille: "Tamaño", octets: "{n} bytes", empreinte: "Huella SHA-256",
|
|
137
|
+
couvert: "CUBIERTO por la firma, con el nombre «{nom}»",
|
|
138
|
+
nonCouvert: "NO cubierto: este archivo no es uno de los documentos firmados, o ha sido modificado",
|
|
139
|
+
aucunDocument: "No se ha cargado ningún documento: solo se ha verificado la firma. Cargue el documento para saber si está cubierto.",
|
|
140
|
+
manifeste: "Documentos cubiertos por la firma: {n}",
|
|
141
|
+
pasXml: "Este archivo no es una firma XML legible.", aucunFichier: "No se ha enviado ningún archivo de firma.",
|
|
142
|
+
moteurAbsent: "La verificación no está disponible en este servidor (falta el motor Python).", erreur: "Motivo del rechazo",
|
|
143
|
+
},
|
|
144
|
+
it: {
|
|
145
|
+
titre: "Verificare una firma separata (XML)",
|
|
146
|
+
aide: "Alcuni documenti ufficiali — la Gazzetta ufficiale dell’UE, per esempio — sono pubblicati senza firma all’interno del PDF: si trova in un file XML a parte. Carichi quel file XML e, se vuole, il documento o i documenti: ciascuno viene confrontato con le impronte coperte dalla firma. Nulla viene conservato.",
|
|
147
|
+
signatureXml: "Firma (file XML)", documents: "Documenti da confrontare (facoltativo, anche più di uno)", bouton: "Verifica",
|
|
148
|
+
verdict_valide: "Firma valida: nulla di ciò che copre è cambiato", verdict_altere: "ALTERATA: una parte firmata è stata modificata",
|
|
149
|
+
verdict_invalide: "Non valida: la firma non corrisponde al suo contenuto", niveau: "Livello", format: "Formato",
|
|
150
|
+
signataire: "Firmatario", emetteur: "Emittente", validite: "Validità", numero: "Numero di serie",
|
|
151
|
+
qualifieOui: "si dichiara qualificato (eIDAS)", qscd: "chiave su dispositivo qualificato (QSCD)", type_eseal: "sigillo di organizzazione",
|
|
152
|
+
type_esign: "firma di persona", type_web: "autenticazione di sito web",
|
|
153
|
+
qualifieAide: "secondo quanto dichiara il certificato; qui non si consulta l’elenco di fiducia dell’UE",
|
|
154
|
+
autoSigne: "autofirmato: nessuno oltre al firmatario garantisce la sua identità",
|
|
155
|
+
certificatAnnonce: "Certificato annunciato dalla firma", concorde: "corrisponde", concordePas: "NON corrisponde",
|
|
156
|
+
dateDeclaree: "Data dichiarata", dateDeclareeAide: "dal firmatario stesso — senza marca temporale può essere retrodatata",
|
|
157
|
+
dateAttestee: "Data attestata", dateAttesteeAide: "da un’autorità di marcatura temporale indipendente (RFC 3161)",
|
|
158
|
+
horodatageSignature: "Marca temporale della firma", horodatageArchive: "Marca temporale di archivio (lungo termine)", horodatageAutre: "Altra marca temporale",
|
|
159
|
+
horodateur: "Autorità di marcatura temporale", politique: "Politica di marcatura temporale (OID)", jeton: "Marca",
|
|
160
|
+
jetonOk: "integra, valida e riferita alla firma", jetonSansPortee: "integra e valida (copertura non ricalcolata)", jetonKo: "NON valida",
|
|
161
|
+
aucunHorodatage: "Nessuna marca temporale: nessuno attesta la data della firma.",
|
|
162
|
+
longueDuree: "Dati di convalida a lungo termine", longueDureeValeur: "{c} certificato/i, {o} risposta/e OCSP, {l} elenco/i di revoca",
|
|
163
|
+
documentsFournis: "Documenti caricati", taille: "Dimensione", octets: "{n} byte", empreinte: "Impronta SHA-256",
|
|
164
|
+
couvert: "COPERTO dalla firma, con il nome «{nom}»",
|
|
165
|
+
nonCouvert: "NON coperto: questo file non è uno dei documenti firmati, oppure è stato modificato",
|
|
166
|
+
aucunDocument: "Nessun documento caricato: è stata verificata solo la firma. Carichi il documento per sapere se è coperto.",
|
|
167
|
+
manifeste: "Documenti coperti dalla firma: {n}",
|
|
168
|
+
pasXml: "Questo file non è una firma XML leggibile.", aucunFichier: "Nessun file di firma fornito.",
|
|
169
|
+
moteurAbsent: "La verifica non è disponibile su questo server (motore Python assente).", erreur: "Motivo del rifiuto",
|
|
170
|
+
},
|
|
171
|
+
zh: {
|
|
172
|
+
titre: "核验分离式签名(XML)",
|
|
173
|
+
aide: "部分官方文件(例如欧盟《官方公报》)发布时 PDF 内不含签名:签名位于随附的 XML 文件中。请上传该 XML,如有需要,再上传一份或多份文件:每份文件都将与签名所覆盖的指纹逐一比对。不会保存任何内容。",
|
|
174
|
+
signatureXml: "签名(XML 文件)", documents: "待比对的文件(可选,可多份)", bouton: "核验",
|
|
175
|
+
verdict_valide: "签名有效:其覆盖的内容均未改动", verdict_altere: "已被篡改:某个已签名部分被修改",
|
|
176
|
+
verdict_invalide: "无效:签名与其内容不符", niveau: "级别", format: "格式",
|
|
177
|
+
signataire: "签名人", emetteur: "签发机构", validite: "有效期", numero: "序列号",
|
|
178
|
+
qualifieOui: "声明为合格证书(eIDAS)", qscd: "密钥位于合格设备(QSCD)", type_eseal: "机构印章",
|
|
179
|
+
type_esign: "个人签名", type_web: "网站认证",
|
|
180
|
+
qualifieAide: "依据证书自身的声明;此处未查询欧盟可信列表",
|
|
181
|
+
autoSigne: "自签名:除签名人自己外,无人为其身份担保",
|
|
182
|
+
certificatAnnonce: "签名中声明的证书", concorde: "一致", concordePas: "不一致",
|
|
183
|
+
dateDeclaree: "声明日期", dateDeclareeAide: "由签名人自行声明——没有时间戳时可能被倒签",
|
|
184
|
+
dateAttestee: "证明日期", dateAttesteeAide: "由独立的时间戳机构证明(RFC 3161)",
|
|
185
|
+
horodatageSignature: "签名时间戳", horodatageArchive: "归档时间戳(长期)", horodatageAutre: "其他时间戳",
|
|
186
|
+
horodateur: "时间戳机构", politique: "时间戳策略(OID)", jeton: "时间戳",
|
|
187
|
+
jetonOk: "完整、有效,且确实覆盖该签名", jetonSansPortee: "完整且有效(覆盖范围未重新计算)", jetonKo: "无效",
|
|
188
|
+
aucunHorodatage: "无时间戳:签名日期无人证明。",
|
|
189
|
+
longueDuree: "长期验证数据", longueDureeValeur: "{c} 个证书,{o} 个 OCSP 响应,{l} 个吊销列表",
|
|
190
|
+
documentsFournis: "已上传的文件", taille: "大小", octets: "{n} 字节", empreinte: "SHA-256 指纹",
|
|
191
|
+
couvert: "已被签名覆盖,签名中的名称为“{nom}”",
|
|
192
|
+
nonCouvert: "未被覆盖:该文件不是已签名文件之一,或已被修改",
|
|
193
|
+
aucunDocument: "未上传文件:仅核验了签名本身。请上传文件,以确认其是否被签名覆盖。",
|
|
194
|
+
manifeste: "签名覆盖的文件:{n} 份",
|
|
195
|
+
pasXml: "该文件不是可读的 XML 签名。", aucunFichier: "未提供签名文件。",
|
|
196
|
+
moteurAbsent: "本服务器无法进行核验(缺少 Python 引擎)。", erreur: "拒绝原因",
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const esc = (s = "") => String(s).replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
201
|
+
const dateUtc = (iso) => (iso ? `${new Date(iso).toISOString().replace("T", " ").replace(/\.\d+Z$/, "")} UTC` : "—");
|
|
202
|
+
const ligne = (libelle, valeur, aide = "") => (valeur == null || valeur === "" ? "" : `<tr><th scope="row">${esc(libelle)}</th><td>${valeur}${aide ? `<br><span class="pdfv-aide">${esc(aide)}</span>` : ""}</td></tr>`);
|
|
203
|
+
|
|
204
|
+
/** Un texte dans la langue (repli : le français), `{x}` rempli. */
|
|
205
|
+
export function detachedText(lang, cle, vars = {}) {
|
|
206
|
+
const d = DETACHED_TEXTS[lang] ?? DETACHED_TEXTS.fr;
|
|
207
|
+
return String(d[cle] ?? DETACHED_TEXTS.fr[cle] ?? cle).replace(/\{(\w+)\}/g, (m, k) => (k in vars ? String(vars[k]) : m));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Même règle que `detachedVerdict` de @mostajs/pdf-sign : une partie signée changée, puis la valeur de signature. */
|
|
211
|
+
const verdictDe = (r) => {
|
|
212
|
+
const internes = (r.references ?? []).filter((x) => x.type !== "document");
|
|
213
|
+
if (internes.some((x) => x.ok === false) || r.signedPropertiesOk === false || r.signingCertificateOk === false) return "altere";
|
|
214
|
+
return r.signatureValid === true ? "valide" : "invalide";
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
/** Le formulaire de dépôt (multipart : `signature`, `documents` — plusieurs). */
|
|
218
|
+
export function detachedFormHtml({ lang = "fr", action = "/verifier" } = {}) {
|
|
219
|
+
const t = (k) => esc(detachedText(lang, k));
|
|
220
|
+
return `<form class="pdfv-form" method="post" action="${esc(action)}" enctype="multipart/form-data">
|
|
221
|
+
<fieldset><legend>${t("signatureXml")}</legend><input type="file" name="signature" accept=".xml,application/xml,text/xml" required></fieldset>
|
|
222
|
+
<fieldset><legend>${t("documents")}</legend><input type="file" name="documents" multiple></fieldset>
|
|
223
|
+
<button type="submit">${t("bouton")}</button>
|
|
224
|
+
</form>`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function blocJeton(lang, j) {
|
|
228
|
+
const t = (k) => detachedText(lang, k);
|
|
229
|
+
const titre = t(j.kind === "archive" ? "horodatageArchive" : j.kind === "signature" ? "horodatageSignature" : "horodatageAutre");
|
|
230
|
+
const etat = j.intact && j.valid && j.imprintOk !== false ? (j.imprintOk === true ? "jetonOk" : "jetonSansPortee") : "jetonKo";
|
|
231
|
+
return `<h4>${esc(titre)}</h4><table class="pdfv-table"><tbody>
|
|
232
|
+
${ligne(t("dateAttestee"), `<b>${esc(dateUtc(j.genTime))}</b>`, t("dateAttesteeAide"))}
|
|
233
|
+
${ligne(t("horodateur"), esc(j.tsa?.subject ?? ""))}
|
|
234
|
+
${ligne(t("politique"), j.policy ? `<code>${esc(j.policy)}</code>` : "")}
|
|
235
|
+
${ligne(t("jeton"), esc(t(etat)))}
|
|
236
|
+
</tbody></table>`;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Le rapport de `verifyDetached` : verdict, signataire, dates, horodatages, documents déposés, documents couverts. */
|
|
240
|
+
export function detachedReportHtml(r, { lang = "fr" } = {}) {
|
|
241
|
+
if (!r) return "";
|
|
242
|
+
const t = (k, v) => detachedText(lang, k, v);
|
|
243
|
+
const v = verdictDe(r);
|
|
244
|
+
const c = r.signerCertificate;
|
|
245
|
+
const qualite = c?.qualified
|
|
246
|
+
? [t("qualifieOui"), ...(c.qcType ?? []).filter((x) => DETACHED_TEXTS.fr[`type_${x}`]).map((x) => t(`type_${x}`)), ...(c.qscd ? [t("qscd")] : [])].join(" · ")
|
|
247
|
+
: "";
|
|
248
|
+
const lt = r.longTerm && (r.longTerm.certificates || r.longTerm.ocsp || r.longTerm.crl)
|
|
249
|
+
? t("longueDureeValeur", { c: r.longTerm.certificates, o: r.longTerm.ocsp, l: r.longTerm.crl }) : "";
|
|
250
|
+
const docs = (r.documents ?? []).length
|
|
251
|
+
? `<h3>${esc(t("documentsFournis"))}</h3>${r.documents.map((d) => `<section class="pdfv-signature pdfv-${d.covered ? "valide" : "altere"}">
|
|
252
|
+
<p class="pdfv-verdict">${esc(d.name)} — ${esc(d.covered ? t("couvert", { nom: d.coveredAs ?? d.name }) : t("nonCouvert"))}</p>
|
|
253
|
+
<table class="pdfv-table"><tbody>${ligne(t("taille"), esc(t("octets", { n: d.bytes })))}${ligne(t("empreinte"), `<code class="pdfv-empreinte">${esc(d.sha256)}</code>`)}</tbody></table></section>`).join("")}`
|
|
254
|
+
: `<p class="pdfv-msg">${esc(t("aucunDocument"))}</p>`;
|
|
255
|
+
const manifeste = (r.manifest ?? []).length
|
|
256
|
+
? `<details><summary>${esc(t("manifeste", { n: r.manifest.length }))}</summary><ul>${r.manifest.map((m) => `<li><code>${esc(m.uri)}</code>${m.matchedFile ? ` ← ${esc(m.matchedFile)}` : ""}</li>`).join("")}</ul></details>`
|
|
257
|
+
: "";
|
|
258
|
+
return `<div class="pdfv">
|
|
259
|
+
<section class="pdfv-signature pdfv-${v}">
|
|
260
|
+
<p class="pdfv-verdict">${esc(t(`verdict_${v}`))}</p>
|
|
261
|
+
<table class="pdfv-table"><tbody>
|
|
262
|
+
${ligne(t("niveau"), esc(r.level ?? ""))}
|
|
263
|
+
${ligne(t("signataire"), c ? `<b>${esc(c.subject)}</b>` : "", qualite ? t("qualifieAide") : "")}
|
|
264
|
+
${qualite ? `<tr><td colspan="2">${esc(qualite)}</td></tr>` : ""}
|
|
265
|
+
${ligne(t("emetteur"), esc(c?.issuer ?? ""), c?.selfSigned ? t("autoSigne") : "")}
|
|
266
|
+
${ligne(t("validite"), c?.notBefore ? `${esc(dateUtc(c.notBefore))} → ${esc(dateUtc(c.notAfter))}` : "")}
|
|
267
|
+
${ligne(t("numero"), c?.serial ? `<code>${esc(c.serial)}</code>` : "")}
|
|
268
|
+
${r.signingCertificateOk == null ? "" : ligne(t("certificatAnnonce"), esc(t(r.signingCertificateOk ? "concorde" : "concordePas")))}
|
|
269
|
+
${ligne(t("dateDeclaree"), r.signingTime ? esc(dateUtc(r.signingTime)) : "", t("dateDeclareeAide"))}
|
|
270
|
+
${ligne(t("longueDuree"), esc(lt))}
|
|
271
|
+
</tbody></table>
|
|
272
|
+
${(r.timestamps ?? []).length ? r.timestamps.map((j) => blocJeton(lang, j)).join("") : `<p class="pdfv-msg">${esc(t("aucunHorodatage"))}</p>`}
|
|
273
|
+
</section>
|
|
274
|
+
${docs}
|
|
275
|
+
${manifeste}
|
|
276
|
+
</div>`;
|
|
277
|
+
}
|
package/src/html.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// @mostajs/pdf-sign-ui 0.4.0 — LA VUE HTML DE LA VÉRIFICATION D'UN PDF, en sept langues (11/10/2026).
|
|
2
|
+
// Écrite d'abord dans mdseal (page /verifier, 07/10/2026, fr/en/ar), demandée ensuite par qatrax.dev : la vue et ses
|
|
3
|
+
// textes descendent ici (R-G). Fragments HTML sans gabarit : l'application les pose dans sa page. Tout est échappé.
|
|
4
|
+
// Le résultat attendu est celui de `verifyPdfBytes` de @mostajs/pdf-sign ≥ 0.7.0 ({ file, signed, signatures, dss }).
|
|
5
|
+
// Author: Dr Hamid MADANI <drmdh@msn.com> · AGPL-3.0-or-later
|
|
6
|
+
|
|
7
|
+
export const VERIFY_LANGS = Object.freeze(["fr", "en", "ar", "de", "es", "it", "zh"]);
|
|
8
|
+
|
|
9
|
+
/** Les textes, par langue (le français fait référence ; les clés sont celles de mdseal). */
|
|
10
|
+
export const VERIFY_TEXTS = Object.freeze({
|
|
11
|
+
fr: {"titre": "Vérifier un document signé", "aide": "Déposez un PDF : chaque signature est vérifiée, avec ses dates, ses horodatages et ses certificats. Le fichier n’est pas conservé.", "fichier": "Document PDF", "motDePasse": "Mot de passe d’ouverture (si le document est verrouillé)", "bouton": "Vérifier", "fichierInfo": "Fichier vérifié", "taille": "Taille", "octets": "{n} octets", "empreinte": "Empreinte SHA-256", "nonSigne": "Ce document ne porte aucune signature électronique.", "nbSignatures": "Signatures trouvées : {n}", "signature": "Signature {n}", "horodatageDocument": "Horodatage de document {n}", "verdict_confiance": "Intacte, valide, et identité reconnue", "verdict_valide": "Intacte et valide", "verdict_altere": "ALTÉRÉE : le document a été modifié après la signature", "verdict_invalide": "Invalide", "niveau": "Niveau", "nomAffiche": "Nom affiché par le lecteur", "nomAfficheAide": "déclaratif : seul le certificat engage", "motif": "Motif", "lieu": "Lieu", "couverture": "Portée", "couverture_ENTIRE_FILE": "tout le fichier", "couverture_ENTIRE_REVISION": "la révision signée — des ajouts ont suivi", "algorithme": "Algorithme d’empreinte", "format": "Format", "dateDeclaree": "Date déclarée", "dateDeclareeAide": "par le signataire lui-même — sans horodatage, elle peut être antidatée", "dateAttestee": "Date attestée", "dateAttesteeAide": "par un horodateur tiers (RFC 3161)", "aucunHorodatage": "Aucun horodatage : la date de signature n’est attestée par personne.", "horodateur": "Horodateur", "politique": "Politique d’horodatage (OID)", "numeroJeton": "Numéro du jeton", "chaine": "Chaîne embarquée", "jeton": "Jeton", "jetonIntact": "intact et valide", "jetonAltere": "NON valide", "certificat": "Certificat du signataire", "emetteur": "Émetteur", "validite": "Validité", "numero": "Numéro de série", "autoSigne": "auto-signé : personne d’autre que le signataire ne se porte garant de son identité", "confianceNon": "identité non établie par ce vérificateur (chaîne non reconnue)", "confianceOui": "identité reconnue", "erreur": "Motif du refus", "dss": "Données de validation à long terme (DSS)", "oui": "présentes", "non": "absentes", "frein": "Trop de vérifications depuis cette adresse : réessayez dans une heure.", "moteurAbsent": "Vérification complète indisponible sur ce serveur (moteur pyhanko absent).", "pasPdf": "Ce fichier n’est pas un PDF (en-tête %PDF- absent).", "aucunFichier": "Aucun fichier fourni."},
|
|
12
|
+
en: {"titre": "Verify a signed document", "aide": "Upload a PDF: every signature is checked, with its dates, timestamps and certificates. The file is not kept.", "fichier": "PDF document", "motDePasse": "Opening password (if the document is locked)", "bouton": "Verify", "fichierInfo": "File checked", "taille": "Size", "octets": "{n} bytes", "empreinte": "SHA-256 fingerprint", "nonSigne": "This document carries no electronic signature.", "nbSignatures": "Signatures found: {n}", "signature": "Signature {n}", "horodatageDocument": "Document timestamp {n}", "verdict_confiance": "Intact, valid, and identity recognised", "verdict_valide": "Intact and valid", "verdict_altere": "ALTERED: the document was modified after signing", "verdict_invalide": "Invalid", "niveau": "Level", "nomAffiche": "Name shown by the reader", "nomAfficheAide": "declarative: only the certificate binds", "motif": "Reason", "lieu": "Location", "couverture": "Scope", "couverture_ENTIRE_FILE": "the whole file", "couverture_ENTIRE_REVISION": "the signed revision — additions followed", "algorithme": "Digest algorithm", "format": "Format", "dateDeclaree": "Declared date", "dateDeclareeAide": "by the signer themself — without a timestamp, it can be backdated", "dateAttestee": "Attested date", "dateAttesteeAide": "by a third-party timestamping authority (RFC 3161)", "aucunHorodatage": "No timestamp: nobody attests the signing date.", "horodateur": "Timestamping authority", "politique": "Timestamp policy (OID)", "numeroJeton": "Token serial number", "chaine": "Embedded chain", "jeton": "Token", "jetonIntact": "intact and valid", "jetonAltere": "NOT valid", "certificat": "Signer certificate", "emetteur": "Issuer", "validite": "Validity", "numero": "Serial number", "autoSigne": "self-signed: nobody but the signer vouches for their identity", "confianceNon": "identity not established by this verifier (chain not recognised)", "confianceOui": "identity recognised", "erreur": "Reason for rejection", "dss": "Long-term validation data (DSS)", "oui": "present", "non": "absent", "frein": "Too many verifications from this address: try again in an hour.", "moteurAbsent": "Full verification is unavailable on this server (pyhanko engine missing).", "pasPdf": "This file is not a PDF (%PDF- header missing).", "aucunFichier": "No file provided."},
|
|
13
|
+
ar: {"titre": "التحقّق من وثيقة موقَّعة", "aide": "أودِع ملف PDF: يُتحقَّق من كلّ توقيع، مع تواريخه وأختامه الزمنية وشهاداته. لا يُحتفظ بالملف.", "fichier": "وثيقة PDF", "motDePasse": "كلمة مرور الفتح (إن كانت الوثيقة مقفلة)", "bouton": "تحقّق", "fichierInfo": "الملف المُتحقَّق منه", "taille": "الحجم", "octets": "{n} بايت", "empreinte": "بصمة SHA-256", "nonSigne": "لا تحمل هذه الوثيقة أيّ توقيع إلكتروني.", "nbSignatures": "التواقيع الموجودة: {n}", "signature": "التوقيع {n}", "horodatageDocument": "الختم الزمني للوثيقة {n}", "verdict_confiance": "سليم وصالح، والهويّة معترَف بها", "verdict_valide": "سليم وصالح", "verdict_altere": "مُعدَّل: عُدّلت الوثيقة بعد التوقيع", "verdict_invalide": "غير صالح", "niveau": "المستوى", "nomAffiche": "الاسم الذي يعرضه القارئ", "nomAfficheAide": "تصريحي: الشهادة وحدها مُلزِمة", "motif": "السبب", "lieu": "المكان", "couverture": "النطاق", "couverture_ENTIRE_FILE": "الملف كلّه", "couverture_ENTIRE_REVISION": "المراجعة الموقَّعة — تلتها إضافات", "algorithme": "خوارزمية البصمة", "format": "الصيغة", "dateDeclaree": "التاريخ المُصرَّح به", "dateDeclareeAide": "من الموقِّع نفسه — دون ختم زمني يمكن تقديمه", "dateAttestee": "التاريخ المشهود", "dateAttesteeAide": "من جهة ختم زمني مستقلّة (RFC 3161)", "aucunHorodatage": "لا ختم زمني: لا أحد يشهد على تاريخ التوقيع.", "horodateur": "جهة الختم الزمني", "politique": "سياسة الختم الزمني (OID)", "numeroJeton": "رقم الرمز", "chaine": "السلسلة المضمَّنة", "jeton": "الرمز", "jetonIntact": "سليم وصالح", "jetonAltere": "غير صالح", "certificat": "شهادة الموقِّع", "emetteur": "المُصدِر", "validite": "الصلاحية", "numero": "الرقم التسلسلي", "autoSigne": "موقَّعة ذاتيًّا: لا أحد غير الموقِّع يضمن هويّته", "confianceNon": "لم يُثبت هذا المُتحقِّق الهويّة (سلسلة غير معترَف بها)", "confianceOui": "الهويّة معترَف بها", "erreur": "سبب الرفض", "dss": "بيانات التحقّق طويل الأمد (DSS)", "oui": "موجودة", "non": "غائبة", "frein": "تحقّقات كثيرة من هذا العنوان: أعِد المحاولة بعد ساعة.", "moteurAbsent": "التحقّق الكامل غير متاح على هذا الخادم (محرّك pyhanko غائب).", "pasPdf": "هذا الملف ليس PDF (ترويسة %PDF- غائبة).", "aucunFichier": "لم يُقدَّم أيّ ملف."},
|
|
14
|
+
de: {"titre": "Ein signiertes Dokument prüfen", "aide": "Laden Sie ein PDF hoch: Jede Signatur wird geprüft, mit ihren Daten, Zeitstempeln und Zertifikaten. Die Datei wird nicht gespeichert.", "fichier": "PDF-Dokument", "motDePasse": "Öffnungspasswort (falls das Dokument gesperrt ist)", "bouton": "Prüfen", "fichierInfo": "Geprüfte Datei", "taille": "Größe", "octets": "{n} Bytes", "empreinte": "SHA-256-Fingerabdruck", "nonSigne": "Dieses Dokument trägt keine elektronische Signatur.", "nbSignatures": "Gefundene Signaturen: {n}", "signature": "Signatur {n}", "horodatageDocument": "Dokumentzeitstempel {n}", "verdict_confiance": "Unversehrt, gültig, Identität anerkannt", "verdict_valide": "Unversehrt und gültig", "verdict_altere": "VERÄNDERT: Das Dokument wurde nach der Signatur geändert", "verdict_invalide": "Ungültig", "niveau": "Stufe", "nomAffiche": "Vom Leseprogramm angezeigter Name", "nomAfficheAide": "nur deklarativ: verbindlich ist allein das Zertifikat", "motif": "Grund", "lieu": "Ort", "couverture": "Umfang", "couverture_ENTIRE_FILE": "die gesamte Datei", "couverture_ENTIRE_REVISION": "die signierte Revision — danach folgten Ergänzungen", "algorithme": "Hash-Algorithmus", "format": "Format", "dateDeclaree": "Angegebenes Datum", "dateDeclareeAide": "vom Unterzeichner selbst — ohne Zeitstempel kann es rückdatiert sein", "dateAttestee": "Bestätigtes Datum", "dateAttesteeAide": "durch einen unabhängigen Zeitstempeldienst (RFC 3161)", "aucunHorodatage": "Kein Zeitstempel: Niemand bestätigt das Signaturdatum.", "horodateur": "Zeitstempeldienst", "politique": "Zeitstempelrichtlinie (OID)", "numeroJeton": "Seriennummer des Tokens", "chaine": "Eingebettete Kette", "jeton": "Token", "jetonIntact": "unversehrt und gültig", "jetonAltere": "NICHT gültig", "certificat": "Zertifikat des Unterzeichners", "emetteur": "Aussteller", "validite": "Gültigkeit", "numero": "Seriennummer", "autoSigne": "selbstsigniert: Niemand außer dem Unterzeichner bürgt für seine Identität", "confianceNon": "Identität von dieser Prüfung nicht bestätigt (Kette nicht anerkannt)", "confianceOui": "Identität anerkannt", "erreur": "Ablehnungsgrund", "dss": "Daten zur Langzeitvalidierung (DSS)", "oui": "vorhanden", "non": "nicht vorhanden", "frein": "Zu viele Prüfungen von dieser Adresse: Versuchen Sie es in einer Stunde erneut.", "moteurAbsent": "Die vollständige Prüfung ist auf diesem Server nicht verfügbar (pyhanko fehlt).", "pasPdf": "Diese Datei ist kein PDF (Kopfzeile %PDF- fehlt).", "aucunFichier": "Keine Datei übermittelt."},
|
|
15
|
+
es: {"titre": "Verificar un documento firmado", "aide": "Cargue un PDF: se verifica cada firma, con sus fechas, sellos de tiempo y certificados. El archivo no se conserva.", "fichier": "Documento PDF", "motDePasse": "Contraseña de apertura (si el documento está bloqueado)", "bouton": "Verificar", "fichierInfo": "Archivo verificado", "taille": "Tamaño", "octets": "{n} bytes", "empreinte": "Huella SHA-256", "nonSigne": "Este documento no lleva ninguna firma electrónica.", "nbSignatures": "Firmas encontradas: {n}", "signature": "Firma {n}", "horodatageDocument": "Sello de tiempo del documento {n}", "verdict_confiance": "Íntegra, válida e identidad reconocida", "verdict_valide": "Íntegra y válida", "verdict_altere": "ALTERADA: el documento se modificó después de la firma", "verdict_invalide": "No válida", "niveau": "Nivel", "nomAffiche": "Nombre mostrado por el lector", "nomAfficheAide": "declarativo: solo el certificado compromete", "motif": "Motivo", "lieu": "Lugar", "couverture": "Alcance", "couverture_ENTIRE_FILE": "todo el archivo", "couverture_ENTIRE_REVISION": "la revisión firmada — siguieron añadidos", "algorithme": "Algoritmo de huella", "format": "Formato", "dateDeclaree": "Fecha declarada", "dateDeclareeAide": "por el propio firmante — sin sello de tiempo, puede estar antedatada", "dateAttestee": "Fecha acreditada", "dateAttesteeAide": "por una autoridad de sellado de tiempo independiente (RFC 3161)", "aucunHorodatage": "Sin sello de tiempo: nadie acredita la fecha de la firma.", "horodateur": "Autoridad de sellado de tiempo", "politique": "Política de sellado de tiempo (OID)", "numeroJeton": "Número de serie del sello", "chaine": "Cadena incorporada", "jeton": "Sello", "jetonIntact": "íntegro y válido", "jetonAltere": "NO válido", "certificat": "Certificado del firmante", "emetteur": "Emisor", "validite": "Validez", "numero": "Número de serie", "autoSigne": "autofirmado: nadie salvo el firmante responde de su identidad", "confianceNon": "identidad no establecida por este verificador (cadena no reconocida)", "confianceOui": "identidad reconocida", "erreur": "Motivo del rechazo", "dss": "Datos de validación a largo plazo (DSS)", "oui": "presentes", "non": "ausentes", "frein": "Demasiadas verificaciones desde esta dirección: inténtelo de nuevo dentro de una hora.", "moteurAbsent": "La verificación completa no está disponible en este servidor (falta el motor pyhanko).", "pasPdf": "Este archivo no es un PDF (falta la cabecera %PDF-).", "aucunFichier": "No se ha enviado ningún archivo."},
|
|
16
|
+
it: {"titre": "Verificare un documento firmato", "aide": "Carichi un PDF: ogni firma viene verificata, con le sue date, marche temporali e certificati. Il file non viene conservato.", "fichier": "Documento PDF", "motDePasse": "Password di apertura (se il documento è bloccato)", "bouton": "Verifica", "fichierInfo": "File verificato", "taille": "Dimensione", "octets": "{n} byte", "empreinte": "Impronta SHA-256", "nonSigne": "Questo documento non reca alcuna firma elettronica.", "nbSignatures": "Firme trovate: {n}", "signature": "Firma {n}", "horodatageDocument": "Marca temporale del documento {n}", "verdict_confiance": "Integra, valida e identità riconosciuta", "verdict_valide": "Integra e valida", "verdict_altere": "ALTERATA: il documento è stato modificato dopo la firma", "verdict_invalide": "Non valida", "niveau": "Livello", "nomAffiche": "Nome mostrato dal lettore", "nomAfficheAide": "dichiarativo: impegna solo il certificato", "motif": "Motivo", "lieu": "Luogo", "couverture": "Ambito", "couverture_ENTIRE_FILE": "l’intero file", "couverture_ENTIRE_REVISION": "la revisione firmata — sono seguite aggiunte", "algorithme": "Algoritmo di impronta", "format": "Formato", "dateDeclaree": "Data dichiarata", "dateDeclareeAide": "dal firmatario stesso — senza marca temporale può essere retrodatata", "dateAttestee": "Data attestata", "dateAttesteeAide": "da un’autorità di marcatura temporale indipendente (RFC 3161)", "aucunHorodatage": "Nessuna marca temporale: nessuno attesta la data della firma.", "horodateur": "Autorità di marcatura temporale", "politique": "Politica di marcatura temporale (OID)", "numeroJeton": "Numero di serie della marca", "chaine": "Catena incorporata", "jeton": "Marca", "jetonIntact": "integra e valida", "jetonAltere": "NON valida", "certificat": "Certificato del firmatario", "emetteur": "Emittente", "validite": "Validità", "numero": "Numero di serie", "autoSigne": "autofirmato: nessuno oltre al firmatario garantisce la sua identità", "confianceNon": "identità non stabilita da questo verificatore (catena non riconosciuta)", "confianceOui": "identità riconosciuta", "erreur": "Motivo del rifiuto", "dss": "Dati di convalida a lungo termine (DSS)", "oui": "presenti", "non": "assenti", "frein": "Troppe verifiche da questo indirizzo: riprovi tra un’ora.", "moteurAbsent": "La verifica completa non è disponibile su questo server (motore pyhanko assente).", "pasPdf": "Questo file non è un PDF (intestazione %PDF- assente).", "aucunFichier": "Nessun file fornito."},
|
|
17
|
+
zh: {"titre": "核验已签名文件", "aide": "上传一份 PDF:逐一核验每个签名及其日期、时间戳和证书。文件不会被保存。", "fichier": "PDF 文件", "motDePasse": "打开密码(如文件已加密)", "bouton": "核验", "fichierInfo": "已核验的文件", "taille": "大小", "octets": "{n} 字节", "empreinte": "SHA-256 指纹", "nonSigne": "该文件不含任何电子签名。", "nbSignatures": "找到的签名:{n} 个", "signature": "签名 {n}", "horodatageDocument": "文件时间戳 {n}", "verdict_confiance": "完整、有效,且身份已获认可", "verdict_valide": "完整且有效", "verdict_altere": "已被篡改:文件在签名后被修改", "verdict_invalide": "无效", "niveau": "级别", "nomAffiche": "阅读器显示的名称", "nomAfficheAide": "仅为声明:只有证书具有约束力", "motif": "原因", "lieu": "地点", "couverture": "覆盖范围", "couverture_ENTIRE_FILE": "整个文件", "couverture_ENTIRE_REVISION": "已签名的修订版——其后有追加内容", "algorithme": "摘要算法", "format": "格式", "dateDeclaree": "声明日期", "dateDeclareeAide": "由签名人自行声明——没有时间戳时可能被倒签", "dateAttestee": "证明日期", "dateAttesteeAide": "由独立的时间戳机构证明(RFC 3161)", "aucunHorodatage": "无时间戳:签名日期无人证明。", "horodateur": "时间戳机构", "politique": "时间戳策略(OID)", "numeroJeton": "时间戳序列号", "chaine": "内嵌证书链", "jeton": "时间戳", "jetonIntact": "完整且有效", "jetonAltere": "无效", "certificat": "签名人证书", "emetteur": "签发机构", "validite": "有效期", "numero": "序列号", "autoSigne": "自签名:除签名人自己外,无人为其身份担保", "confianceNon": "本核验未能确认身份(证书链未获认可)", "confianceOui": "身份已获认可", "erreur": "拒绝原因", "dss": "长期验证数据(DSS)", "oui": "存在", "non": "不存在", "frein": "该地址的核验次数过多:请一小时后再试。", "moteurAbsent": "本服务器无法进行完整核验(缺少 pyhanko 引擎)。", "pasPdf": "该文件不是 PDF(缺少 %PDF- 文件头)。", "aucunFichier": "未提供文件。"},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const esc = (s = "") => String(s).replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
21
|
+
|
|
22
|
+
/** Un texte dans la langue (repli : le français), `{x}` rempli. */
|
|
23
|
+
export function verifyText(lang, cle, vars = {}) {
|
|
24
|
+
const d = VERIFY_TEXTS[lang] ?? VERIFY_TEXTS.fr;
|
|
25
|
+
return String(d[cle] ?? VERIFY_TEXTS.fr[cle] ?? cle).replace(/\{(\w+)\}/g, (m, k) => (k in vars ? String(vars[k]) : m));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Le verdict d'une signature : l'intégrité d'abord, la confiance ensuite (même règle que `signatureVerdict` de pdf-sign). */
|
|
29
|
+
const verdictDe = (s) => (s.intact === false ? "altere" : s.intact === true && s.valid === true ? (s.trusted ? "confiance" : "valide") : "invalide");
|
|
30
|
+
const dateUtc = (iso) => (iso ? `${new Date(iso).toISOString().replace("T", " ").replace(/\.\d+Z$/, "")} UTC` : "—");
|
|
31
|
+
const ligne = (libelle, valeur, aide = "") => (valeur == null || valeur === "" ? "" : `<tr><th scope="row">${esc(libelle)}</th><td>${valeur}${aide ? `<br><span class="pdfv-aide">${esc(aide)}</span>` : ""}</td></tr>`);
|
|
32
|
+
|
|
33
|
+
/** Le formulaire de dépôt (multipart : `pdf`, `motdepasse`). */
|
|
34
|
+
export function verifyFormHtml({ lang = "fr", action = "/verifier" } = {}) {
|
|
35
|
+
const t = (k) => esc(verifyText(lang, k));
|
|
36
|
+
return `<form class="pdfv-form" method="post" action="${esc(action)}" enctype="multipart/form-data">
|
|
37
|
+
<fieldset><legend>${t("fichier")}</legend><input type="file" name="pdf" accept=".pdf,application/pdf" required></fieldset>
|
|
38
|
+
<p><label>${t("motDePasse")}<br><input type="password" name="motdepasse" autocomplete="off"></label></p>
|
|
39
|
+
<button type="submit">${t("bouton")}</button>
|
|
40
|
+
</form>`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function blocCertificat(lang, c, trusted) {
|
|
44
|
+
if (!c) return "";
|
|
45
|
+
const t = (k) => verifyText(lang, k);
|
|
46
|
+
return `<table class="pdfv-table"><tbody>
|
|
47
|
+
${ligne(t("certificat"), `<b>${esc(c.subject)}</b>`)}
|
|
48
|
+
${ligne(t("emetteur"), esc(c.issuer ?? ""), c.selfSigned ? t("autoSigne") : "")}
|
|
49
|
+
${ligne(t("validite"), c.notBefore ? `${esc(dateUtc(c.notBefore))} → ${esc(dateUtc(c.notAfter))}` : "")}
|
|
50
|
+
${ligne(t("numero"), c.serial ? `<code>${esc(c.serial)}</code>` : "")}
|
|
51
|
+
${trusted == null ? "" : `<tr><td colspan="2">${esc(t(trusted ? "confianceOui" : "confianceNon"))}</td></tr>`}
|
|
52
|
+
</tbody></table>`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function blocSignature(lang, s, n) {
|
|
56
|
+
const t = (k, v) => verifyText(lang, k, v);
|
|
57
|
+
const v = verdictDe(s);
|
|
58
|
+
const ts = s.timestamp;
|
|
59
|
+
const couverture = s.coverage ? (VERIFY_TEXTS.fr[`couverture_${s.coverage}`] ? t(`couverture_${s.coverage}`) : s.coverage) : "";
|
|
60
|
+
return `<section class="pdfv-signature pdfv-${v}">
|
|
61
|
+
<h3>${esc(t(s.type === "horodatage-document" ? "horodatageDocument" : "signature", { n }))}</h3>
|
|
62
|
+
<p class="pdfv-verdict">${esc(t(`verdict_${v}`))}</p>
|
|
63
|
+
<table class="pdfv-table"><tbody>
|
|
64
|
+
${ligne(t("niveau"), esc(s.level ?? ""))}
|
|
65
|
+
${ligne(t("nomAffiche"), esc(s.name ?? ""), s.name ? t("nomAfficheAide") : "")}
|
|
66
|
+
${ligne(t("motif"), esc(s.reason ?? ""))}
|
|
67
|
+
${ligne(t("lieu"), esc(s.location ?? ""))}
|
|
68
|
+
${ligne(t("dateDeclaree"), s.claimedSigningTime ? esc(dateUtc(s.claimedSigningTime)) : "", t("dateDeclareeAide"))}
|
|
69
|
+
${ts ? ligne(t("dateAttestee"), `<b>${esc(dateUtc(ts.genTime))}</b>`, t("dateAttesteeAide")) : ""}
|
|
70
|
+
${ligne(t("couverture"), esc(couverture))}
|
|
71
|
+
${ligne(t("algorithme"), esc(s.digestAlgorithm ?? ""))}
|
|
72
|
+
${ligne(t("format"), s.subFilter ? `<code>${esc(s.subFilter)}</code>` : "")}
|
|
73
|
+
${ligne(t("erreur"), esc(s.error ?? ""))}
|
|
74
|
+
</tbody></table>
|
|
75
|
+
${ts ? `<h4>${esc(t("horodateur"))}</h4><table class="pdfv-table"><tbody>
|
|
76
|
+
${ligne(t("horodateur"), `<b>${esc(ts.tsa?.subject ?? "")}</b>`)}
|
|
77
|
+
${ligne(t("emetteur"), esc(ts.tsa?.issuer ?? ""))}
|
|
78
|
+
${ligne(t("politique"), ts.policy ? `<code>${esc(ts.policy)}</code>` : "")}
|
|
79
|
+
${ligne(t("numeroJeton"), ts.serial ? `<code>${esc(ts.serial)}</code>` : "")}
|
|
80
|
+
${ligne(t("jeton"), esc(t(ts.intact && ts.valid ? "jetonIntact" : "jetonAltere")))}
|
|
81
|
+
${ligne(t("chaine"), ts.chain?.length ? ts.chain.map((c) => esc(c)).join("<br>") : "")}
|
|
82
|
+
</tbody></table>` : (s.type === "signature" ? `<p class="pdfv-msg">${esc(t("aucunHorodatage"))}</p>` : "")}
|
|
83
|
+
${s.type === "signature" && s.signerCertificate ? `<h4>${esc(t("certificat"))}</h4>${blocCertificat(lang, s.signerCertificate, s.trusted)}` : ""}
|
|
84
|
+
</section>`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Le rapport d'une vérification : le fichier, puis chaque signature et chaque horodatage de document. */
|
|
88
|
+
export function verifyReportHtml(r, { lang = "fr" } = {}) {
|
|
89
|
+
if (!r) return "";
|
|
90
|
+
const t = (k, v) => verifyText(lang, k, v);
|
|
91
|
+
const fichier = `<h2>${esc(t("fichierInfo"))}</h2><table class="pdfv-table"><tbody>
|
|
92
|
+
${ligne(t("taille"), esc(t("octets", { n: r.file?.bytes ?? 0 })))}
|
|
93
|
+
${ligne(t("empreinte"), `<code class="pdfv-empreinte">${esc(r.file?.sha256 ?? "")}</code>`)}
|
|
94
|
+
${r.signed ? ligne(t("dss"), esc(t(r.dss ? "oui" : "non"))) : ""}
|
|
95
|
+
</tbody></table>`;
|
|
96
|
+
if (!r.signed) return `<div class="pdfv">${fichier}<p class="pdfv-msg">${esc(t("nonSigne"))}</p></div>`;
|
|
97
|
+
return `<div class="pdfv">${fichier}<p><b>${esc(t("nbSignatures", { n: r.signatures.length }))}</b></p>${r.signatures.map((s, i) => blocSignature(lang, s, i + 1)).join("")}</div>`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Une feuille de style minimale, à poser une fois dans la page (les couleurs suivent les variables du thème). */
|
|
101
|
+
export const VERIFY_CSS = `.pdfv-table{border-collapse:collapse;width:100%;margin:.4em 0 1em}.pdfv-table th,.pdfv-table td{text-align:start;vertical-align:top;padding:.35em .6em;border-bottom:1px solid var(--line,#e2e8f0)}
|
|
102
|
+
.pdfv-table th{width:34%;font-weight:600}.pdfv-aide{color:var(--doux,#475569);font-size:.9em}.pdfv-empreinte{word-break:break-all}
|
|
103
|
+
.pdfv-signature{border:1px solid var(--line,#e2e8f0);border-radius:8px;padding:.6em 1em;margin:1em 0}.pdfv-verdict{font-weight:700}
|
|
104
|
+
.pdfv-confiance .pdfv-verdict,.pdfv-valide .pdfv-verdict{color:#166534}.pdfv-altere .pdfv-verdict,.pdfv-invalide .pdfv-verdict{color:#991b1b}
|
|
105
|
+
.pdfv-msg{background:var(--fond-doux,#f1f5f9);border-radius:6px;padding:.5em .8em}.pdfv-form fieldset{border:1px solid var(--line,#e2e8f0);border-radius:8px}`;
|
package/src/index.js
CHANGED
|
@@ -8,3 +8,7 @@ export {
|
|
|
8
8
|
createPdfSignUiController,
|
|
9
9
|
} from "./headless.js";
|
|
10
10
|
export { DOCUMENT_PRESETS, listPresets, getPreset } from "./presets.js";
|
|
11
|
+
// 0.4.0 — la vue HTML de la vérification d'un PDF, en sept langues (descendue de mdseal).
|
|
12
|
+
export { VERIFY_LANGS, VERIFY_TEXTS, VERIFY_CSS, verifyText, verifyFormHtml, verifyReportHtml } from "./html.js";
|
|
13
|
+
// 0.5.0 — la vue de la signature séparée (XAdES), comme au Journal officiel de l'UE (pdf-sign ≥ 0.8.0).
|
|
14
|
+
export { DETACHED_TEXTS, detachedText, detachedFormHtml, detachedReportHtml } from "./detached-html.js";
|