1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830 | //! Writes a document reading software shows nothing of until a password is
//! typed in.
//!
//! Every string and every stream in the file is ciphertext, locked with AES
//! under a two-hundred-and-fifty-six-bit key. The user password is set, so
//! reading software asks for it before it shows a single page, and the password
//! is what the key is worked out from: reading software given the wrong one
//! shows nothing at all. The page states the password, so that the file can be
//! opened by whoever is handed it.
//!
//! Once it is open, the file grants printing, at the resolution the page was
//! drawn at, and reading aloud; it withholds copying, changing and taking pages
//! out — as requests reading software honours, not as locks. The author's
//! password lifts them and opens the file as well, and the page states that one
//! too.
//!
//! The document that opens with nothing typed is `write_restricted`, and this
//! page names it in words rather than by that name.
//!
//! The page is written in the language `HQF_PDF_LANG` names. The passwords are
//! not: they are strings typed into reading software, and a translated password
//! opens nothing.
//!
//! Usage: `cargo run --example write_locked -- tmp/locked.pdf [font.ttf]`
//! `HQF_PDF_LANG=fr cargo run --example write_locked`
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use hqf_pdf::content::Content;
use hqf_pdf::cos::Name;
use hqf_pdf::{Document, Encryption, Font, FontHandle, Page, Permissions};
#[path = "shared/out.rs"]
mod out;
#[path = "shared/licence.rs"]
mod licence;
#[path = "shared/language.rs"]
mod language;
#[path = "shared/failure.rs"]
mod failure;
use language::Language;
/// The font the example draws with when none is given on the command line: the
/// one committed for the tests, so that the example runs on any machine.
fn default_font() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("fonts")
.join("DejaVuSans.ttf")
}
/// How far in from the left edge of the sheet every line is set, in points.
const LEFT: f64 = 72.0;
/// The size the heading is set at, in points.
const HEADING_SIZE: f64 = 18.0;
/// Where the baseline of the first line of the heading sits, in points up from
/// the foot of the sheet.
const HEADING_BASELINE: f64 = 760.0;
/// How far one line of the heading sits below the one before it, in points.
const HEADING_LEADING: f64 = 22.0;
/// The size the body is set at, in points.
const BODY_SIZE: f64 = 11.0;
/// Where the baseline of the first line of the body sits, in points up from the
/// foot of the sheet.
const FIRST_BASELINE: f64 = 700.0;
/// How far one line of the body sits below the one before it, in points.
const LEADING: f64 = 18.0;
/// The size the heading over each closing section is set at, in points.
const SECTION_SIZE: f64 = 13.0;
/// Where the baseline of the heading over the other document sits, in points up
/// from the foot of the sheet.
const COMPANION_HEADING_BASELINE: f64 = 520.0;
/// Where the baseline of the first line about the other document sits, in
/// points up from the foot of the sheet.
const COMPANION_BASELINE: f64 = 492.0;
/// Where the baseline of the heading over the closing note sits, in points up
/// from the foot of the sheet.
const NOTE_HEADING_BASELINE: f64 = 420.0;
/// Where the baseline of the first line of the note sits, in points up from the
/// foot of the sheet.
const NOTE_BASELINE: f64 = 392.0;
/// The password reading software asks for before it shows a page, which the
/// page states so that the file can be opened at all. It stands outside the
/// words: a password is typed into reading software, and a translated one opens
/// nothing.
const USER_PASSWORD: &str = "the reader";
/// The author's password, which the page states so that what the file withholds
/// can be lifted. It opens the file as well: the encryption dictionary of a
/// locked document works the same key out of it that the other password gives.
/// It stands outside the words for the same reason the other one does.
const OWNER_PASSWORD: &str = "the owner";
/// The thirty-two bytes the file key is built from.
///
/// They are fixed here, so that the example writes the same file on every run
/// and one build can be compared with the last. **A program takes them from its
/// operating system** — `getrandom` in Rust, `os.urandom(32)` in Python. A file
/// locked under a seed anybody can read is a file anybody opens.
const SEED: [u8; 32] = [
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
0x0F, 0x1E, 0x2D, 0x3C, 0x4B, 0x5A, 0x69, 0x78, 0x87, 0x96, 0xA5, 0xB4, 0xC3, 0xD2, 0xE1, 0xF0,
];
/// The words the page is written in, one set per language.
///
/// The passwords are not among them: they are typed into reading software, not
/// translated.
#[derive(Debug)]
struct Words {
/// What the document is called, set over two lines at the head of the page
/// and joined by a space in what the file says of itself.
heading: [&'static str; 2],
/// The body of the page, one line to a line.
body: [&'static str; 7],
/// What stands before the password the file asks for.
user_password_label: &'static str,
/// What stands before the author's password.
password_label: &'static str,
/// What stands over the lines about the other document.
companion_heading: &'static str,
/// What the other document is, and how it differs from this one.
companion: [&'static str; 3],
/// What stands over the closing note.
note_heading: &'static str,
/// The closing note, which says where the key comes from.
note: [&'static str; 4],
}
impl Words {
/// The words the page is written in, in `language`.
fn of(language: Language) -> &'static Self {
language::pick(&WORDS, language)
}
/// What the file says of itself: the two lines of the heading, in a row.
fn title(&self) -> String {
self.heading.join(" ")
}
}
/// The page in English.
const ENGLISH: Words = Words {
heading: ["A document that asks for a password", "before it opens"],
body: [
"This file shows nothing until a password is typed in. Type the",
"wrong one and no page appears at all.",
"Everything written in it is scrambled, under a key of two",
"hundred and fifty-six bits, and the password is what that key",
"is worked out from.",
"Once it is open, it asks the reading software to let it be",
"printed and read out loud, but not copied or changed.",
],
user_password_label: "The password this document asks for:",
password_label: "The author's password opens it too, and lifts the request:",
companion_heading: "The other document of this pair",
companion: [
"A second document was written beside this one. That one opens",
"on its own, with nothing to type in, and only asks that it not",
"be copied or changed.",
],
note_heading: "Where the key comes from",
note: [
"The thirty-two bytes the key is built from are fixed in this",
"example, so that it writes the same file on every run and one",
"build can be compared with the last. A program takes them from",
"its operating system: a seed anybody can read locks nothing.",
],
};
/// The page in French.
const FRENCH: Words = Words {
heading: ["Un document qui demande un mot de passe", "pour s’ouvrir"],
body: [
"Ce fichier ne montre rien tant qu’un mot de passe n’a pas été",
"saisi. Saisissez-en un faux et aucune page ne s’affiche.",
"Tout ce qu’il contient est chiffré avec une clé de deux cent",
"cinquante-six bits, et c’est le mot de passe qui permet de",
"retrouver cette clé.",
"Une fois ouvert, il demande au logiciel de lecture de laisser",
"imprimer et lire à voix haute, mais ni copier ni modifier.",
],
user_password_label: "Le mot de passe que ce document demande :",
password_label: "Le mot de passe de l’auteur l’ouvre aussi, et lève les restrictions :",
companion_heading: "L’autre document de la paire",
companion: [
"Un second document a été créé avec celui-ci. Lui s’ouvre",
"tout seul, sans rien saisir, et demande seulement qu’on ne le",
"copie ni ne le modifie.",
],
note_heading: "D’où vient la clé",
note: [
"Les trente-deux octets dont la clé est tirée sont figés dans cet",
"exemple, pour qu’il produise le même fichier à chaque exécution et",
"qu’on puisse le comparer au précédent. Un programme les demande à son",
"système d’exploitation : une graine lisible par tous ne protège rien.",
],
};
/// The page in Dutch.
const DUTCH: Words = Words {
heading: [
"Een document dat om een wachtwoord vraagt",
"voordat het opengaat",
],
body: [
"Dit bestand toont niets tot er een wachtwoord is ingetypt.",
"Met het verkeerde verschijnt er geen enkele pagina.",
"Alles wat erin staat, is versleuteld, met een sleutel van",
"tweehonderdzesenvijftig bits, en die sleutel wordt uit het",
"wachtwoord berekend.",
"Eenmaal geopend vraagt het de leessoftware om afdrukken",
"en voorlezen toe te staan, maar kopiëren en wijzigen niet.",
],
user_password_label: "Het wachtwoord dat dit document vraagt:",
password_label: "Het wachtwoord van de auteur opent het ook, en heft het verzoek op:",
companion_heading: "Het andere document van dit paar",
companion: [
"Naast dit document is een tweede geschreven. Dat opent",
"vanzelf, zonder iets in te typen, en vraagt alleen dat het niet",
"gekopieerd of gewijzigd wordt.",
],
note_heading: "Waar de sleutel vandaan komt",
note: [
"De tweeëndertig bytes waaruit de sleutel wordt opgebouwd, liggen in dit",
"voorbeeld vast, zodat het bij elke run hetzelfde bestand schrijft en een",
"build met de vorige vergeleken kan worden. Een programma haalt ze uit",
"zijn besturingssysteem: een startwaarde die iedereen kan lezen, sluit niets af.",
],
};
/// The page in Portuguese.
const PORTUGUESE: Words = Words {
heading: ["Um documento que pede uma palavra-passe", "antes de abrir"],
body: [
"Este ficheiro não mostra nada até se escrever uma palavra-passe.",
"Com a errada não aparece nenhuma página.",
"Tudo o que nele está escrito está cifrado, com uma chave de duzentos",
"e cinquenta e seis bits, e a palavra-passe é aquilo a partir do qual",
"essa chave é calculada.",
"Uma vez aberto, pede ao programa de leitura que o deixe",
"imprimir e ler em voz alta, mas não copiar nem alterar.",
],
user_password_label: "A palavra-passe que este documento pede:",
password_label: "A palavra-passe do autor também o abre, e levanta o pedido:",
companion_heading: "O outro documento deste par",
companion: [
"Ao lado deste foi escrito um segundo documento. Esse abre",
"sem palavra-passe, e só pede que não seja copiado",
"nem alterado.",
],
note_heading: "De onde vem a chave",
note: [
"Os trinta e dois bytes a partir dos quais a chave é construída são fixos neste",
"exemplo, para que escreva o mesmo ficheiro a cada execução e uma",
"compilação possa ser comparada com a anterior. Um programa tira-os do",
"seu sistema operativo: uma semente que qualquer um pode ler não fecha nada.",
],
};
/// The page in Italian.
const ITALIAN: Words = Words {
heading: ["Un documento che chiede una password", "prima di aprirsi"],
body: [
"Questo file non mostra nulla finché non si digita una password.",
"Con quella sbagliata non appare nessuna pagina.",
"Tutto ciò che contiene è cifrato, con una chiave di",
"duecentocinquantasei bit, e la password è ciò da cui",
"quella chiave viene calcolata.",
"Una volta aperto, chiede al programma di lettura di lasciarlo",
"stampare e leggere ad alta voce, ma non copiare né modificare.",
],
user_password_label: "La password che chiede questo documento:",
password_label: "Anche la password dell’autore lo apre, e toglie la richiesta:",
companion_heading: "L’altro documento di questa coppia",
companion: [
"Accanto a questo è stato scritto un secondo documento. Quello si apre",
"da solo, senza digitare nulla, e chiede soltanto di non essere",
"copiato né modificato.",
],
note_heading: "Da dove viene la chiave",
note: [
"I trentadue byte da cui si costruisce la chiave sono fissi in questo",
"esempio, così scrive lo stesso file a ogni esecuzione e una",
"compilazione si può confrontare con la precedente. Un programma li prende dal",
"suo sistema operativo: un seme che chiunque può leggere non chiude nulla.",
],
};
/// The page in Spanish.
const SPANISH: Words = Words {
heading: ["Un documento que pide una contraseña", "antes de abrirse"],
body: [
"Este archivo no muestra nada hasta que se teclea una contraseña.",
"Con la equivocada no aparece ninguna página.",
"Todo lo escrito en él está cifrado, con una clave de doscientos",
"cincuenta y seis bits, y esa clave se calcula a partir",
"de la contraseña.",
"Una vez abierto, pide al programa de lectura que deje",
"imprimirlo y leerlo en voz alta, pero no copiarlo ni cambiarlo.",
],
user_password_label: "La contraseña que pide este documento:",
password_label: "La contraseña del autor también lo abre, y anula la petición:",
companion_heading: "El otro documento de esta pareja",
companion: [
"Junto a este se escribió un segundo documento. Ese se abre",
"solo, sin teclear nada, y solo pide que no se copie",
"ni se cambie.",
],
note_heading: "De dónde sale la clave",
note: [
"Los treinta y dos bytes con que se construye la clave son fijos en este",
"ejemplo, para que escriba el mismo archivo en cada ejecución y una",
"compilación pueda compararse con la anterior. Un programa real los toma de",
"su sistema operativo: una semilla que cualquiera puede leer no cierra nada.",
],
};
/// The page in German.
const GERMAN: Words = Words {
heading: [
"Ein Dokument, das nach einem Passwort fragt",
"bevor es sich öffnet",
],
body: [
"Diese Datei zeigt nichts, bis ein Passwort eingegeben ist. Wer",
"das falsche eingibt, sieht überhaupt keine Seite.",
"Alles darin ist verschlüsselt, unter einem Schlüssel von",
"zweihundertsechsundfünfzig Bit, und das Passwort ist das,",
"woraus dieser Schlüssel berechnet wird.",
"Einmal geöffnet, bittet es die Lesesoftware, es drucken und",
"vorlesen, aber nicht kopieren oder ändern zu lassen.",
],
user_password_label: "Das Passwort, das dieses Dokument verlangt:",
password_label: "Das Passwort des Autors öffnet es auch und hebt die Bitte auf:",
companion_heading: "Das andere Dokument dieses Paares",
companion: [
"Neben diesem wurde ein zweites Dokument geschrieben. Es öffnet",
"sich von selbst, ohne dass etwas einzugeben ist, und bittet nur",
"darum, nicht kopiert oder geändert zu werden.",
],
note_heading: "Woher der Schlüssel kommt",
note: [
"Die zweiunddreißig Bytes, aus denen der Schlüssel gebaut wird,",
"sind in diesem Beispiel fest, damit es bei jedem Lauf dieselbe",
"Datei schreibt. Ein Programm holt sie von seinem Betriebssystem:",
"Ein Startwert, den jeder lesen kann, schließt nichts ab.",
],
};
/// Every language the example is written in. A language is added by writing its
/// own set of words and naming it here.
static WORDS: [(Language, &Words); 7] = [
(Language::English, &ENGLISH),
(Language::French, &FRENCH),
(Language::German, &GERMAN),
(Language::Spanish, &SPANISH),
(Language::Italian, &ITALIAN),
(Language::Portuguese, &PORTUGUESE),
(Language::Dutch, &DUTCH),
];
/// Every line of the body, the last two of which state the password the file
/// asks for and the author's.
fn lines(words: &Words) -> Vec<String> {
let mut all: Vec<String> = words.body.iter().map(|line| (*line).to_owned()).collect();
all.push(format!("{} {USER_PASSWORD}", words.user_password_label));
all.push(format!("{} {OWNER_PASSWORD}", words.password_label));
all
}
/// What the document is locked with: the password reading software asks for,
/// the author's, and what reading software is asked to allow — printing at the
/// resolution the page was drawn at, and reading aloud.
fn encryption() -> Encryption {
Encryption::new(SEED)
.user_password(USER_PASSWORD)
.owner_password(OWNER_PASSWORD)
.permissions(Permissions::new().printing().printing_at_full_resolution())
}
/// Every line the page draws, in the order they are drawn: what it says, the
/// size it is set at, and where its baseline sits in points up from the foot of
/// the sheet.
fn drawing(words: &Words) -> Vec<(String, f64, f64)> {
let mut drawn = Vec::new();
let mut baseline = HEADING_BASELINE;
for line in words.heading {
drawn.push((line.to_owned(), HEADING_SIZE, baseline));
baseline -= HEADING_LEADING;
}
baseline = FIRST_BASELINE;
for line in lines(words) {
drawn.push((line, BODY_SIZE, baseline));
baseline -= LEADING;
}
drawn.push((
words.companion_heading.to_owned(),
SECTION_SIZE,
COMPANION_HEADING_BASELINE,
));
baseline = COMPANION_BASELINE;
for line in words.companion {
drawn.push((line.to_owned(), BODY_SIZE, baseline));
baseline -= LEADING;
}
drawn.push((
words.note_heading.to_owned(),
SECTION_SIZE,
NOTE_HEADING_BASELINE,
));
baseline = NOTE_BASELINE;
for line in words.note {
drawn.push((line.to_owned(), BODY_SIZE, baseline));
baseline -= LEADING;
}
drawn
}
/// The page: every line set by its own origin, each in an object of its own.
fn drawn(font: &FontHandle, lines: &[(String, f64, f64)]) -> Result<Content, hqf_pdf::Error> {
let mut content = Content::new();
for (line, size, baseline) in lines {
content.begin_text();
content.set_font(font, *size)?;
content.text_origin(LEFT, *baseline)?;
content.show_glyphs(&font.glyphs(line));
content.end_text();
}
Ok(content)
}
/// The document and its one page, before it is locked.
fn document(words: &Words, font: Font) -> Result<Document, hqf_pdf::Error> {
let mut doc = Document::new();
doc.set_license(licence::licensed());
doc.set_info(Name::new("Title"), &words.title());
let handle = doc.add_font(font);
let mut page = Page::a4();
page.content = drawn(&handle, &drawing(words))?.into_bytes();
doc.add_page(page)?;
Ok(doc)
}
/// Writes `bytes` to `path`, making the directory it goes in if it is not
/// there, and says what was written.
fn written(path: &str, bytes: &[u8]) -> Result<(), Box<dyn std::error::Error>> {
if let Some(parent) = Path::new(path).parent() {
fs::create_dir_all(parent)?;
}
fs::write(path, bytes)?;
println!("wrote {path}: {} bytes", bytes.len());
Ok(())
}
fn main() -> std::process::ExitCode {
failure::reported(run())
}
fn run() -> Result<(), Box<dyn std::error::Error>> {
let language = Language::from_environment()?;
let words = Words::of(language);
let mut args = env::args().skip(1);
// A named file is written as named; the default one carries the language,
// so the two languages do not overwrite each other in `tmp/`.
let out = args
.next()
.unwrap_or_else(|| language.file_name(&out::default_path("locked")));
let font_path = args.next().map_or_else(default_font, PathBuf::from);
let mut doc = document(words, Font::parse(fs::read(&font_path)?)?)?;
doc.set_encryption(encryption());
written(&out, &doc.to_bytes()?)
}
#[cfg(test)]
mod tests {
use std::path::{Path, PathBuf};
use std::process::Command;
use hqf_pdf::read::Reader;
use hqf_pdf::{Document, Encryption, Font};
use super::{
LEFT, OWNER_PASSWORD, USER_PASSWORD, WORDS, Words, default_font, document, drawing,
encryption, language,
};
/// The width of the sheet, in points: A4, which the page is.
const SHEET_WIDTH: f64 = 595.276;
/// The room a line set from the left margin has before it reaches the
/// margin on the other side of the sheet.
const PAPER: f64 = SHEET_WIDTH - 2.0 * LEFT;
/// The lines two languages are allowed to write the same way. There are
/// none: the passwords are held outside the words.
const SPARED: [&str; 0] = [];
/// The committed font, parsed.
fn a_font() -> Font {
Font::parse(std::fs::read(default_font()).expect("the committed font is there"))
.expect("the committed font parses")
}
/// The bytes the example writes for `words`, protected or in the clear.
fn written(words: &Words, protected: bool) -> Vec<u8> {
let mut doc = document(words, a_font()).expect("the document is built");
if protected {
doc.set_encryption(encryption());
}
doc.to_bytes().expect("the document writes")
}
#[test]
fn every_language_writes_the_page_in_its_own_words() {
let untranslated = language::untranslated_lines(&WORDS, &SPARED);
assert!(
untranslated.is_empty(),
"the page says these in more than one language: {untranslated:?}"
);
}
/// Nothing on the page is broken to a width: a line longer than the paper
/// runs off the edge of it.
#[test]
fn every_language_writes_lines_that_fit_the_room_they_have() {
let mut doc = Document::new();
let font = doc.add_font(a_font());
for (named, words) in WORDS {
for (line, size, _) in drawing(words) {
let measured = font.measure(&line, size);
assert!(
measured <= PAPER,
"the {} page draws {line:?} over {measured:.1} points, and \
it has {PAPER:.1}",
named.code()
);
}
}
}
/// The page draws no line over the one below it: every baseline sits lower
/// than the one before, by at least the size the line above is set at.
#[test]
fn the_page_draws_no_line_over_the_one_below_it() {
for (named, words) in WORDS {
for pair in drawing(words).windows(2) {
let (above, size, higher) = &pair[0];
let (below, _, lower) = &pair[1];
assert!(
higher - lower >= *size,
"the {} page sets {above:?} at {higher} and {below:?} at \
{lower}, and the first is {size} points tall",
named.code()
);
}
}
}
/// The bytes a file states its title as, however it spells them: plain
/// where the words are, sixteen bits to a letter where they are not.
fn stated_title(bytes: &[u8]) -> Vec<u8> {
let key = b"/Title (";
let at = bytes
.windows(key.len())
.position(|window| window == key)
.expect("the file states a title")
+ key.len();
let mut stated = Vec::new();
let mut escaped = false;
for byte in &bytes[at..] {
if escaped {
escaped = false;
} else if *byte == b'\\' {
escaped = true;
} else if *byte == b')' {
break;
}
stated.push(*byte);
}
stated
}
/// The file names the handler it was locked with and carries nothing of
/// what it states its title to be. The same document written in the clear
/// carries it plainly, which is what says the reading looks in the right
/// place.
#[test]
fn the_file_carries_nothing_it_draws_in_the_clear() {
for (named, words) in WORDS {
let code = named.code();
let plain = written(words, false);
let stated = stated_title(&plain);
assert!(
stated.len() > 8,
"the {code} document states its title where a file states one"
);
assert!(
!String::from_utf8_lossy(&plain).contains("/Encrypt"),
"and names no handler"
);
let locked = written(words, true);
let text = String::from_utf8_lossy(&locked);
assert!(text.contains("/Encrypt"), "the {code} file is protected");
assert!(text.contains("/AESV3"), "and names the cipher it used");
assert!(
!locked
.windows(stated.len())
.any(|window| window == stated.as_slice()),
"the {code} file carries its title in the clear"
);
}
}
/// The file does not open until the password its page states is given, and
/// the author's opens it too. What comes back out of it is what it says of
/// itself, which noise would not spell.
#[test]
fn the_file_opens_only_under_a_password_its_page_states() {
for (named, words) in WORDS {
let code = named.code();
let bytes = written(words, true);
assert!(
Reader::with_password(bytes.clone(), "").is_err(),
"the {code} file opened on nothing"
);
assert!(
Reader::with_password(bytes.clone(), "not the password").is_err(),
"the {code} file opened on another password"
);
for password in [USER_PASSWORD, OWNER_PASSWORD] {
let reader =
Reader::with_password(bytes.clone(), password).unwrap_or_else(|error| {
panic!("the {code} file opens on {password:?}: {error}")
});
assert_eq!(
reader
.information()
.expect("the file states what it is")
.title
.unwrap_or_default(),
words.title(),
"the {code} file states the title its page carries"
);
}
}
}
/// The page says printing and reading aloud are allowed and the rest is
/// withheld; the file states the same thing, as the flags of table 22 with
/// every reserved bit set — 0xFFFFF0C0, which is -3904 — plus bit 3 for
/// printing, which is 4, bit 10 for reading aloud, which is 512, and bit 12
/// for printing at the resolution the page was drawn at, which is 2048. A
/// file granting bit 3 without bit 12 is printed as a coarse picture.
#[test]
fn what_the_page_says_is_allowed_is_what_the_file_states() {
let bytes = written(Words::of(language::Language::English), true);
assert_eq!(-3904 + 4 + 512 + 2048, -1340, "the flags add up");
assert!(
String::from_utf8_lossy(&bytes).contains("/P -1340"),
"the file states what it grants"
);
}
/// The same document written twice is the same file, which is what lets one
/// build be compared with the last; another key writes another file.
#[test]
fn the_same_seed_writes_the_same_file_and_another_writes_another() {
let words = Words::of(language::Language::English);
assert_eq!(
written(words, true),
written(words, true),
"a protected document is still the same file"
);
let mut other = document(words, a_font()).expect("the document is built");
other.set_encryption(
Encryption::new([1u8; 32])
.user_password(USER_PASSWORD)
.owner_password(OWNER_PASSWORD),
);
assert_ne!(
written(words, true),
other.to_bytes().expect("the document writes"),
"another key writes another file"
);
}
/// Where reading software that is not ours is handed what it is to read.
fn scratch(name: &str) -> PathBuf {
let mut at = std::env::temp_dir();
at.push(format!(
"hqf-pdf-write-locked-{}-{name}",
std::process::id()
));
at
}
/// Whether a program is on the machine.
fn installed(program: &str) -> bool {
Command::new(program)
.arg("-v")
.output()
.is_ok_and(|out| out.status.success() || !out.stderr.is_empty())
}
/// What `program` printed about `file`, and whether it managed to read it.
fn asked(program: &str, file: &Path, arguments: &[&str]) -> (bool, String) {
let out = Command::new(program)
.args(arguments)
.arg(file)
.output()
.unwrap_or_else(|error| panic!("{program} runs: {error}"));
(
out.status.success(),
String::from_utf8_lossy(&out.stdout).into_owned(),
)
}
/// What poppler gets off the pages of `file` under the passwords
/// `arguments` names, and whether it managed to read it at all.
fn text_of(file: &Path, arguments: &[&str]) -> (bool, String) {
let out = Command::new("pdftotext")
.args(arguments)
.arg(file)
.arg("-")
.output()
.expect("pdftotext runs");
(
out.status.success(),
String::from_utf8_lossy(&out.stdout).into_owned(),
)
}
/// Poppler, which shares no code with this library, is refused the file
/// until it is given the password the page states, and is refused it under
/// any other password. That is the page's own claim measured from outside:
/// our own reader agreeing with our own writer proves only that the two
/// were written from one reading of one clause.
#[test]
fn reading_software_that_is_not_ours_is_asked_for_the_password_the_page_states() {
if !installed("pdfinfo") {
println!("skipped: poppler is not on this machine");
return;
}
let words = Words::of(language::Language::English);
let at = scratch("asks.pdf");
std::fs::write(&at, written(words, true)).expect("the file is written");
let (opened, said) = asked("pdfinfo", &at, &[]);
assert!(
!opened,
"poppler opened the file with nothing typed, and said {said}"
);
let (opened, said) = asked("pdfinfo", &at, &["-upw", "not the password"]);
assert!(
!opened,
"poppler opened the file under another password, and said {said}"
);
let (opened, said) = asked("pdfinfo", &at, &["-upw", USER_PASSWORD]);
assert!(
opened,
"poppler opened the file once it was given the password"
);
assert!(
said.contains(&format!("Title: {}", words.title())),
"and read its title back, and said {said}"
);
assert!(
said.contains("Encrypted: yes"),
"and says the file is protected, and said {said}"
);
if installed("pdftotext") {
let drawn = words.body[0];
let (read, got) = text_of(&at, &[]);
assert!(
!read || !got.contains(drawn),
"poppler got the page off the file with nothing typed, and got \
{got:?}"
);
let (read, got) = text_of(&at, &["-upw", USER_PASSWORD]);
assert!(
read && got.contains(drawn),
"poppler got the page off it once it was given the password, \
and got {got:?}"
);
} else {
println!("skipped: pdftotext is not on this machine");
}
std::fs::remove_file(&at).ok();
}
}
|