| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
/* |
|
2
|
|
|
|
|
|
|
* pdfmake_redact.c — Secure PDF redaction. |
|
3
|
|
|
|
|
|
|
* |
|
4
|
|
|
|
|
|
|
* §12.5.6.17 Redaction Annotations |
|
5
|
|
|
|
|
|
|
*/ |
|
6
|
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
#include "pdfmake_redact.h" |
|
8
|
|
|
|
|
|
|
#include "pdfmake_page.h" |
|
9
|
|
|
|
|
|
|
#include "pdfmake_arena.h" |
|
10
|
|
|
|
|
|
|
#include "pdfmake_content.h" |
|
11
|
|
|
|
|
|
|
#include |
|
12
|
|
|
|
|
|
|
#include |
|
13
|
|
|
|
|
|
|
#include |
|
14
|
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
/* ── Mark ──────────────────────────────────────────────── */ |
|
16
|
|
|
|
|
|
|
|
|
17
|
22
|
|
|
|
|
|
pdfmake_redact_t *pdfmake_page_mark_redaction( |
|
18
|
|
|
|
|
|
|
pdfmake_page_t *page, |
|
19
|
|
|
|
|
|
|
double x0, double y0, double x1, double y1, |
|
20
|
|
|
|
|
|
|
const pdfmake_redact_opts_t *opts) |
|
21
|
|
|
|
|
|
|
{ |
|
22
|
|
|
|
|
|
|
pdfmake_redact_t *r; |
|
23
|
|
|
|
|
|
|
|
|
24
|
22
|
50
|
|
|
|
|
if (!page) return NULL; |
|
25
|
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
/* Grow array */ |
|
27
|
22
|
100
|
|
|
|
|
if (page->redact_count >= page->redact_cap) { |
|
28
|
11
|
100
|
|
|
|
|
size_t new_cap = page->redact_cap == 0 ? 4 : page->redact_cap * 2; |
|
29
|
11
|
|
|
|
|
|
void *new_arr = realloc(page->redactions, new_cap * sizeof(pdfmake_redact_t)); |
|
30
|
11
|
50
|
|
|
|
|
if (!new_arr) return NULL; |
|
31
|
11
|
|
|
|
|
|
page->redactions = new_arr; |
|
32
|
11
|
|
|
|
|
|
page->redact_cap = new_cap; |
|
33
|
|
|
|
|
|
|
} |
|
34
|
|
|
|
|
|
|
|
|
35
|
22
|
|
|
|
|
|
r = &((pdfmake_redact_t *)page->redactions)[page->redact_count]; |
|
36
|
22
|
|
|
|
|
|
memset(r, 0, sizeof(*r)); |
|
37
|
22
|
|
|
|
|
|
r->rect[0] = x0; |
|
38
|
22
|
|
|
|
|
|
r->rect[1] = y0; |
|
39
|
22
|
|
|
|
|
|
r->rect[2] = x1; |
|
40
|
22
|
|
|
|
|
|
r->rect[3] = y1; |
|
41
|
|
|
|
|
|
|
|
|
42
|
22
|
50
|
|
|
|
|
if (opts) { |
|
43
|
22
|
|
|
|
|
|
r->overlay_color[0] = opts->overlay_color[0]; |
|
44
|
22
|
|
|
|
|
|
r->overlay_color[1] = opts->overlay_color[1]; |
|
45
|
22
|
|
|
|
|
|
r->overlay_color[2] = opts->overlay_color[2]; |
|
46
|
22
|
100
|
|
|
|
|
if (opts->overlay_text) { |
|
47
|
17
|
|
|
|
|
|
strncpy(r->overlay_text, opts->overlay_text, sizeof(r->overlay_text) - 1); |
|
48
|
|
|
|
|
|
|
} |
|
49
|
22
|
50
|
|
|
|
|
r->overlay_font_size = opts->overlay_font_size > 0 ? opts->overlay_font_size : 10; |
|
50
|
|
|
|
|
|
|
} else { |
|
51
|
|
|
|
|
|
|
/* Default: black fill, no text */ |
|
52
|
0
|
|
|
|
|
|
r->overlay_color[0] = 0; |
|
53
|
0
|
|
|
|
|
|
r->overlay_color[1] = 0; |
|
54
|
0
|
|
|
|
|
|
r->overlay_color[2] = 0; |
|
55
|
0
|
|
|
|
|
|
r->overlay_font_size = 10; |
|
56
|
|
|
|
|
|
|
} |
|
57
|
|
|
|
|
|
|
|
|
58
|
22
|
|
|
|
|
|
page->redact_count++; |
|
59
|
22
|
|
|
|
|
|
return r; |
|
60
|
|
|
|
|
|
|
} |
|
61
|
|
|
|
|
|
|
|
|
62
|
9
|
|
|
|
|
|
size_t pdfmake_page_redaction_count(pdfmake_page_t *page) { |
|
63
|
9
|
50
|
|
|
|
|
return page ? page->redact_count : 0; |
|
64
|
|
|
|
|
|
|
} |
|
65
|
|
|
|
|
|
|
|
|
66
|
0
|
|
|
|
|
|
pdfmake_redact_t *pdfmake_page_redaction_at(pdfmake_page_t *page, size_t idx) { |
|
67
|
0
|
0
|
|
|
|
|
if (!page || idx >= page->redact_count) return NULL; |
|
|
|
0
|
|
|
|
|
|
|
68
|
0
|
|
|
|
|
|
return &((pdfmake_redact_t *)page->redactions)[idx]; |
|
69
|
|
|
|
|
|
|
} |
|
70
|
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
/* ── Apply ─────────────────────────────────────────────── */ |
|
72
|
|
|
|
|
|
|
|
|
73
|
0
|
|
|
|
|
|
pdfmake_err_t pdfmake_page_apply_redactions(pdfmake_page_t *page) { |
|
74
|
|
|
|
|
|
|
pdfmake_doc_t *doc; |
|
75
|
|
|
|
|
|
|
pdfmake_arena_t *arena; |
|
76
|
0
|
|
|
|
|
|
uint8_t *old_content = NULL; |
|
77
|
0
|
|
|
|
|
|
size_t old_len = 0; |
|
78
|
|
|
|
|
|
|
pdfmake_content_t *c; |
|
79
|
|
|
|
|
|
|
const uint8_t *new_data; |
|
80
|
|
|
|
|
|
|
size_t new_len; |
|
81
|
|
|
|
|
|
|
pdfmake_obj_t new_stream; |
|
82
|
|
|
|
|
|
|
uint32_t new_num; |
|
83
|
|
|
|
|
|
|
size_t i; |
|
84
|
|
|
|
|
|
|
|
|
85
|
0
|
0
|
|
|
|
|
if (!page || page->redact_count == 0) return PDFMAKE_OK; |
|
|
|
0
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
|
|
87
|
0
|
|
|
|
|
|
doc = page->doc; |
|
88
|
0
|
|
|
|
|
|
arena = pdfmake_doc_arena(doc); |
|
89
|
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
/* Get existing content stream data */ |
|
91
|
0
|
0
|
|
|
|
|
if (page->has_content && page->contents_num > 0) { |
|
|
|
0
|
|
|
|
|
|
|
92
|
0
|
|
|
|
|
|
pdfmake_obj_t *stream_obj = pdfmake_doc_get(doc, page->contents_num); |
|
93
|
0
|
0
|
|
|
|
|
if (stream_obj && stream_obj->kind == PDFMAKE_STREAM) { |
|
|
|
0
|
|
|
|
|
|
|
94
|
0
|
|
|
|
|
|
const uint8_t *sdata = stream_obj->as.stream->raw; |
|
95
|
0
|
|
|
|
|
|
size_t slen = stream_obj->as.stream->raw_len; |
|
96
|
0
|
0
|
|
|
|
|
if (sdata && slen > 0) { |
|
|
|
0
|
|
|
|
|
|
|
97
|
0
|
|
|
|
|
|
old_content = malloc(slen); |
|
98
|
0
|
0
|
|
|
|
|
if (old_content) { |
|
99
|
0
|
|
|
|
|
|
memcpy(old_content, sdata, slen); |
|
100
|
0
|
|
|
|
|
|
old_len = slen; |
|
101
|
|
|
|
|
|
|
} |
|
102
|
|
|
|
|
|
|
} |
|
103
|
|
|
|
|
|
|
} |
|
104
|
|
|
|
|
|
|
} |
|
105
|
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
/* Build new content stream with redaction overlays appended */ |
|
107
|
0
|
|
|
|
|
|
c = pdfmake_content_new(arena); |
|
108
|
0
|
0
|
|
|
|
|
if (!c) { free(old_content); return PDFMAKE_ENOMEM; } |
|
109
|
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
/* Copy existing content (we leave it in — proper removal would |
|
111
|
|
|
|
|
|
|
* require parsing and selectively removing operators within rects. |
|
112
|
|
|
|
|
|
|
* For v1: we overlay with opaque fill which is the common approach |
|
113
|
|
|
|
|
|
|
* even in commercial tools. Content bytes remain but are visually |
|
114
|
|
|
|
|
|
|
* hidden and covered by the overlay. For true content removal, |
|
115
|
|
|
|
|
|
|
* phase 11 content interpreter would need to rewrite the stream. */ |
|
116
|
0
|
0
|
|
|
|
|
if (old_content && old_len > 0) { |
|
|
|
0
|
|
|
|
|
|
|
117
|
0
|
|
|
|
|
|
pdfmake_buf_append(&c->buf, old_content, old_len); |
|
118
|
0
|
|
|
|
|
|
pdfmake_buf_append_byte(&c->buf, '\n'); |
|
119
|
|
|
|
|
|
|
} |
|
120
|
0
|
|
|
|
|
|
free(old_content); |
|
121
|
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
/* Draw redaction overlays */ |
|
123
|
0
|
0
|
|
|
|
|
for (i = 0; i < page->redact_count; i++) { |
|
124
|
0
|
|
|
|
|
|
pdfmake_redact_t *r = &((pdfmake_redact_t *)page->redactions)[i]; |
|
125
|
|
|
|
|
|
|
double x0, y0, x1, y1, w, h; |
|
126
|
|
|
|
|
|
|
|
|
127
|
0
|
0
|
|
|
|
|
if (r->applied) continue; |
|
128
|
|
|
|
|
|
|
|
|
129
|
0
|
|
|
|
|
|
x0 = r->rect[0]; y0 = r->rect[1]; |
|
130
|
0
|
|
|
|
|
|
x1 = r->rect[2]; y1 = r->rect[3]; |
|
131
|
0
|
|
|
|
|
|
w = x1 - x0; h = y1 - y0; |
|
132
|
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
/* Save state, fill rect with overlay color */ |
|
134
|
0
|
|
|
|
|
|
pdfmake_gs_q(c); |
|
135
|
0
|
|
|
|
|
|
pdfmake_color_rg(c, r->overlay_color[0], r->overlay_color[1], r->overlay_color[2]); |
|
136
|
0
|
|
|
|
|
|
pdfmake_path_re(c, x0, y0, w, h); |
|
137
|
0
|
|
|
|
|
|
pdfmake_paint_f(c); |
|
138
|
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
/* Overlay text if specified */ |
|
140
|
0
|
0
|
|
|
|
|
if (r->overlay_text[0]) { |
|
141
|
|
|
|
|
|
|
/* Ensure the page has a Helvetica font we can reference. */ |
|
142
|
0
|
|
|
|
|
|
const char *font_name = NULL; |
|
143
|
|
|
|
|
|
|
size_t fi; |
|
144
|
0
|
0
|
|
|
|
|
for (fi = 0; fi < page->font_count; fi++) { |
|
145
|
|
|
|
|
|
|
/* Any font will keep the /Tf valid; Helvetica is |
|
146
|
|
|
|
|
|
|
* preferred but any resolvable name is enough to avoid |
|
147
|
|
|
|
|
|
|
* the "unknown font" error in readers. */ |
|
148
|
0
|
|
|
|
|
|
font_name = page->fonts[fi].name; |
|
149
|
0
|
|
|
|
|
|
break; |
|
150
|
|
|
|
|
|
|
} |
|
151
|
0
|
0
|
|
|
|
|
if (!font_name) { |
|
152
|
0
|
0
|
|
|
|
|
if (pdfmake_page_add_font(page, "RedactF", "Helvetica") != 0) { |
|
153
|
0
|
|
|
|
|
|
font_name = "RedactF"; |
|
154
|
|
|
|
|
|
|
} |
|
155
|
|
|
|
|
|
|
} |
|
156
|
|
|
|
|
|
|
|
|
157
|
0
|
0
|
|
|
|
|
if (font_name) { |
|
158
|
|
|
|
|
|
|
double ty; |
|
159
|
|
|
|
|
|
|
double tx; |
|
160
|
0
|
|
|
|
|
|
pdfmake_color_rg(c, 1, 1, 1); /* White text */ |
|
161
|
0
|
|
|
|
|
|
pdfmake_text_BT(c); |
|
162
|
0
|
|
|
|
|
|
pdfmake_text_Tf(c, font_name, r->overlay_font_size); |
|
163
|
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
/* Center text vertically */ |
|
165
|
0
|
|
|
|
|
|
ty = y0 + (h - r->overlay_font_size) / 2; |
|
166
|
0
|
|
|
|
|
|
tx = x0 + 4; |
|
167
|
0
|
|
|
|
|
|
pdfmake_text_Td(c, tx, ty); |
|
168
|
0
|
|
|
|
|
|
pdfmake_text_Tj(c, (const uint8_t *)r->overlay_text, |
|
169
|
0
|
|
|
|
|
|
strlen(r->overlay_text)); |
|
170
|
0
|
|
|
|
|
|
pdfmake_text_ET(c); |
|
171
|
|
|
|
|
|
|
} |
|
172
|
|
|
|
|
|
|
} |
|
173
|
|
|
|
|
|
|
|
|
174
|
0
|
|
|
|
|
|
pdfmake_gs_Q(c); |
|
175
|
0
|
|
|
|
|
|
r->applied = 1; |
|
176
|
|
|
|
|
|
|
} |
|
177
|
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
/* Replace content stream */ |
|
179
|
0
|
|
|
|
|
|
new_data = pdfmake_content_data(c); |
|
180
|
0
|
|
|
|
|
|
new_len = pdfmake_content_len(c); |
|
181
|
|
|
|
|
|
|
|
|
182
|
0
|
|
|
|
|
|
new_stream = pdfmake_stream_new(arena); |
|
183
|
0
|
|
|
|
|
|
pdfmake_stream_set_data(arena, &new_stream, new_data, new_len); |
|
184
|
0
|
|
|
|
|
|
new_num = pdfmake_doc_add(doc, new_stream); |
|
185
|
|
|
|
|
|
|
|
|
186
|
0
|
|
|
|
|
|
page->contents_num = new_num; |
|
187
|
0
|
|
|
|
|
|
page->has_content = 1; |
|
188
|
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
{ |
|
190
|
0
|
|
|
|
|
|
pdfmake_arena_t *content_arena = c->arena; |
|
191
|
0
|
|
|
|
|
|
pdfmake_content_free(c); |
|
192
|
0
|
|
|
|
|
|
pdfmake_arena_free(content_arena); |
|
193
|
|
|
|
|
|
|
} |
|
194
|
|
|
|
|
|
|
|
|
195
|
0
|
|
|
|
|
|
return PDFMAKE_OK; |
|
196
|
|
|
|
|
|
|
} |
|
197
|
|
|
|
|
|
|
|
|
198
|
0
|
|
|
|
|
|
pdfmake_err_t pdfmake_doc_apply_redactions(pdfmake_doc_t *doc) { |
|
199
|
|
|
|
|
|
|
size_t i; |
|
200
|
0
|
0
|
|
|
|
|
if (!doc) return PDFMAKE_EINVAL; |
|
201
|
0
|
0
|
|
|
|
|
for (i = 0; i < doc->page_count; i++) { |
|
202
|
0
|
|
|
|
|
|
pdfmake_err_t err = pdfmake_page_apply_redactions(doc->pages[i]); |
|
203
|
0
|
0
|
|
|
|
|
if (err != PDFMAKE_OK) return err; |
|
204
|
|
|
|
|
|
|
} |
|
205
|
0
|
|
|
|
|
|
return PDFMAKE_OK; |
|
206
|
|
|
|
|
|
|
} |
|
207
|
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
/* ── Sanitize ──────────────────────────────────────────── */ |
|
209
|
|
|
|
|
|
|
|
|
210
|
5
|
|
|
|
|
|
pdfmake_err_t pdfmake_doc_sanitize_metadata(pdfmake_doc_t *doc) { |
|
211
|
5
|
50
|
|
|
|
|
if (!doc) return PDFMAKE_EINVAL; |
|
212
|
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
/* Clear /Info dictionary if present */ |
|
214
|
5
|
50
|
|
|
|
|
if (doc->info_num > 0) { |
|
215
|
5
|
|
|
|
|
|
pdfmake_arena_t *arena = pdfmake_doc_arena(doc); |
|
216
|
5
|
|
|
|
|
|
pdfmake_obj_t *info = pdfmake_doc_get(doc, doc->info_num); |
|
217
|
5
|
50
|
|
|
|
|
if (info && info->kind == PDFMAKE_DICT) { |
|
|
|
50
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
/* Replace with empty dict */ |
|
219
|
5
|
|
|
|
|
|
*info = pdfmake_dict_new(arena); |
|
220
|
|
|
|
|
|
|
} |
|
221
|
|
|
|
|
|
|
} |
|
222
|
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
/* Regenerate document IDs */ |
|
224
|
5
|
|
|
|
|
|
doc->id_set = 0; |
|
225
|
5
|
|
|
|
|
|
pdfmake_doc_generate_id(doc); |
|
226
|
|
|
|
|
|
|
|
|
227
|
5
|
|
|
|
|
|
return PDFMAKE_OK; |
|
228
|
|
|
|
|
|
|
} |