| 1 | #include "gl-app.h" |
| 2 | |
| 3 | #include <string.h> |
| 4 | #include <stdio.h> |
| 5 | |
| 6 | #ifndef PATH_MAX |
| 7 | #define PATH_MAX 2048 |
| 8 | #endif |
| 9 | |
| 10 | #include "mupdf/helpers/pkcs7-check.h" |
| 11 | #include "mupdf/helpers/pkcs7-openssl.h" |
| 12 | |
| 13 | static pdf_widget *sig_widget; |
| 14 | static char sig_designated_name[500]; |
| 15 | static enum pdf_signature_error sig_cert_error; |
| 16 | static enum pdf_signature_error sig_digest_error; |
| 17 | static int sig_subsequent_edits; |
| 18 | |
| 19 | static char cert_filename[PATH_MAX]; |
| 20 | static struct input cert_password; |
| 21 | |
| 22 | static void do_sign(void) |
| 23 | { |
| 24 | pdf_pkcs7_signer *signer = NULL; |
| 25 | |
| 26 | fz_var(signer); |
| 27 | |
| 28 | fz_try(ctx) |
| 29 | { |
| 30 | signer = pkcs7_openssl_read_pfx(ctx, cert_filename, cert_password.text); |
| 31 | pdf_sign_signature(ctx, pdf, sig_widget, signer); |
| 32 | ui_show_warning_dialog("Signed document successfully." ); |
| 33 | } |
| 34 | fz_always(ctx) |
| 35 | { |
| 36 | if (signer) |
| 37 | signer->drop(signer); |
| 38 | } |
| 39 | fz_catch(ctx) |
| 40 | ui_show_warning_dialog("%s" , fz_caught_message(ctx)); |
| 41 | |
| 42 | if (pdf_update_page(ctx, sig_widget->page)) |
| 43 | render_page(); |
| 44 | } |
| 45 | |
| 46 | static void do_clear_signature(void) |
| 47 | { |
| 48 | fz_try(ctx) |
| 49 | { |
| 50 | pdf_clear_signature(ctx, pdf, sig_widget); |
| 51 | ui_show_warning_dialog("Signature cleared successfully." ); |
| 52 | } |
| 53 | fz_catch(ctx) |
| 54 | ui_show_warning_dialog("%s" , fz_caught_message(ctx)); |
| 55 | |
| 56 | if (pdf_update_page(ctx, sig_widget->page)) |
| 57 | render_page(); |
| 58 | } |
| 59 | |
| 60 | static void cert_password_dialog(void) |
| 61 | { |
| 62 | int is; |
| 63 | ui_dialog_begin(400, (ui.gridsize+4)*3); |
| 64 | { |
| 65 | ui_layout(T, X, NW, 2, 2); |
| 66 | ui_label("Password:" ); |
| 67 | is = ui_input(&cert_password, 200, 1); |
| 68 | |
| 69 | ui_layout(B, X, NW, 2, 2); |
| 70 | ui_panel_begin(0, ui.gridsize, 0, 0, 0); |
| 71 | { |
| 72 | ui_layout(R, NONE, S, 0, 0); |
| 73 | if (ui_button("Cancel" )) |
| 74 | ui.dialog = NULL; |
| 75 | ui_spacer(); |
| 76 | if (ui_button("Okay" ) || is == UI_INPUT_ACCEPT) |
| 77 | { |
| 78 | ui.dialog = NULL; |
| 79 | do_sign(); |
| 80 | } |
| 81 | } |
| 82 | ui_panel_end(); |
| 83 | } |
| 84 | ui_dialog_end(); |
| 85 | } |
| 86 | |
| 87 | static int cert_file_filter(const char *fn) |
| 88 | { |
| 89 | return !!strstr(fn, ".pfx" ); |
| 90 | } |
| 91 | |
| 92 | static void cert_file_dialog(void) |
| 93 | { |
| 94 | if (ui_open_file(cert_filename)) |
| 95 | { |
| 96 | if (cert_filename[0] != 0) |
| 97 | { |
| 98 | ui_input_init(&cert_password, "" ); |
| 99 | ui.focus = &cert_password; |
| 100 | ui.dialog = cert_password_dialog; |
| 101 | } |
| 102 | else |
| 103 | ui.dialog = NULL; |
| 104 | } |
| 105 | } |
| 106 | |
| 107 | static void sig_sign_dialog(void) |
| 108 | { |
| 109 | const char *label = pdf_field_label(ctx, sig_widget->obj); |
| 110 | |
| 111 | ui_dialog_begin(400, (ui.gridsize+4)*3 + ui.lineheight*10); |
| 112 | { |
| 113 | ui_layout(T, X, NW, 2, 2); |
| 114 | |
| 115 | ui_label("%s" , label); |
| 116 | ui_spacer(); |
| 117 | |
| 118 | ui_label("Would you like to sign this field?" ); |
| 119 | |
| 120 | ui_layout(B, X, NW, 2, 2); |
| 121 | ui_panel_begin(0, ui.gridsize, 0, 0, 0); |
| 122 | { |
| 123 | ui_layout(R, NONE, S, 0, 0); |
| 124 | if (ui_button("Cancel" ) || (!ui.focus && ui.key == KEY_ESCAPE)) |
| 125 | ui.dialog = NULL; |
| 126 | ui_spacer(); |
| 127 | if (!(pdf_field_flags(ctx, sig_widget->obj) & PDF_FIELD_IS_READ_ONLY)) |
| 128 | { |
| 129 | if (ui_button("Sign" )) |
| 130 | { |
| 131 | fz_strlcpy(cert_filename, filename, sizeof cert_filename); |
| 132 | ui_init_open_file("." , cert_file_filter); |
| 133 | ui.dialog = cert_file_dialog; |
| 134 | } |
| 135 | } |
| 136 | } |
| 137 | ui_panel_end(); |
| 138 | } |
| 139 | ui_dialog_end(); |
| 140 | } |
| 141 | |
| 142 | static void sig_verify_dialog(void) |
| 143 | { |
| 144 | const char *label = pdf_field_label(ctx, sig_widget->obj); |
| 145 | |
| 146 | ui_dialog_begin(400, (ui.gridsize+4)*3 + ui.lineheight*10); |
| 147 | { |
| 148 | ui_layout(T, X, NW, 2, 2); |
| 149 | |
| 150 | ui_label("%s" , label); |
| 151 | ui_spacer(); |
| 152 | |
| 153 | ui_label("Designated name: %s." , sig_designated_name); |
| 154 | ui_spacer(); |
| 155 | |
| 156 | if (sig_cert_error) |
| 157 | ui_label("Certificate error: %s" , pdf_signature_error_description(sig_cert_error)); |
| 158 | else |
| 159 | ui_label("Certificate is trusted." ); |
| 160 | |
| 161 | ui_spacer(); |
| 162 | |
| 163 | if (sig_digest_error) |
| 164 | ui_label("Digest error: %s" , pdf_signature_error_description(sig_digest_error)); |
| 165 | else if (sig_subsequent_edits) |
| 166 | ui_label("The signature is valid but there have been edits since signing." ); |
| 167 | else |
| 168 | ui_label("The document is unchanged since signing." ); |
| 169 | |
| 170 | ui_layout(B, X, NW, 2, 2); |
| 171 | ui_panel_begin(0, ui.gridsize, 0, 0, 0); |
| 172 | { |
| 173 | ui_layout(L, NONE, S, 0, 0); |
| 174 | if (ui_button("Clear" )) |
| 175 | { |
| 176 | ui.dialog = NULL; |
| 177 | do_clear_signature(); |
| 178 | } |
| 179 | ui_layout(R, NONE, S, 0, 0); |
| 180 | if (ui_button("Close" ) || (!ui.focus && ui.key == KEY_ESCAPE)) |
| 181 | ui.dialog = NULL; |
| 182 | } |
| 183 | ui_panel_end(); |
| 184 | } |
| 185 | ui_dialog_end(); |
| 186 | } |
| 187 | |
| 188 | static void show_sig_dialog(pdf_widget *widget) |
| 189 | { |
| 190 | fz_try(ctx) |
| 191 | { |
| 192 | sig_widget = widget; |
| 193 | |
| 194 | if (pdf_signature_is_signed(ctx, pdf, widget->obj)) |
| 195 | { |
| 196 | sig_cert_error = pdf_check_certificate(ctx, pdf, widget->obj); |
| 197 | sig_digest_error = pdf_check_digest(ctx, pdf, widget->obj); |
| 198 | sig_subsequent_edits = pdf_signature_incremental_change_since_signing(ctx, pdf, widget->obj); |
| 199 | pdf_signature_designated_name(ctx, pdf, widget->obj, sig_designated_name, sizeof(sig_designated_name)); |
| 200 | ui.dialog = sig_verify_dialog; |
| 201 | } |
| 202 | else |
| 203 | { |
| 204 | ui.dialog = sig_sign_dialog; |
| 205 | } |
| 206 | } |
| 207 | fz_catch(ctx) |
| 208 | ui_show_warning_dialog("%s" , fz_caught_message(ctx)); |
| 209 | } |
| 210 | |
| 211 | static pdf_widget *tx_widget; |
| 212 | static struct input tx_input; |
| 213 | |
| 214 | static void tx_dialog(void) |
| 215 | { |
| 216 | int ff = pdf_field_flags(ctx, tx_widget->obj); |
| 217 | const char *label = pdf_field_label(ctx, tx_widget->obj); |
| 218 | int tx_h = (ff & PDF_TX_FIELD_IS_MULTILINE) ? 10 : 1; |
| 219 | int lbl_h = ui_break_lines((char*)label, NULL, 20, 394, NULL); |
| 220 | int is; |
| 221 | |
| 222 | ui_dialog_begin(400, (ui.gridsize+4)*3 + ui.lineheight*(tx_h+lbl_h-2)); |
| 223 | { |
| 224 | ui_layout(T, X, NW, 2, 2); |
| 225 | ui_label("%s" , label); |
| 226 | is = ui_input(&tx_input, 200, tx_h); |
| 227 | |
| 228 | ui_layout(B, X, NW, 2, 2); |
| 229 | ui_panel_begin(0, ui.gridsize, 0, 0, 0); |
| 230 | { |
| 231 | ui_layout(R, NONE, S, 0, 0); |
| 232 | if (ui_button("Cancel" ) || (!ui.focus && ui.key == KEY_ESCAPE)) |
| 233 | ui.dialog = NULL; |
| 234 | ui_spacer(); |
| 235 | if (ui_button("Okay" ) || is == UI_INPUT_ACCEPT) |
| 236 | { |
| 237 | pdf_set_text_field_value(ctx, tx_widget, tx_input.text); |
| 238 | if (pdf_update_page(ctx, tx_widget->page)) |
| 239 | render_page(); |
| 240 | ui.dialog = NULL; |
| 241 | } |
| 242 | } |
| 243 | ui_panel_end(); |
| 244 | } |
| 245 | ui_dialog_end(); |
| 246 | } |
| 247 | |
| 248 | void show_tx_dialog(pdf_widget *widget) |
| 249 | { |
| 250 | ui_input_init(&tx_input, pdf_field_value(ctx, widget->obj)); |
| 251 | ui.focus = &tx_input; |
| 252 | ui.dialog = tx_dialog; |
| 253 | tx_widget = widget; |
| 254 | } |
| 255 | |
| 256 | static pdf_widget *ch_widget; |
| 257 | static void ch_dialog(void) |
| 258 | { |
| 259 | const char *label; |
| 260 | const char *value; |
| 261 | const char **options; |
| 262 | int n, choice; |
| 263 | int label_h; |
| 264 | |
| 265 | label = pdf_field_label(ctx, ch_widget->obj); |
| 266 | label_h = ui_break_lines((char*)label, NULL, 20, 394, NULL); |
| 267 | n = pdf_choice_widget_options(ctx, ch_widget->page->doc, ch_widget, 0, NULL); |
| 268 | options = fz_malloc_array(ctx, n, const char *); |
| 269 | pdf_choice_widget_options(ctx, ch_widget->page->doc, ch_widget, 0, options); |
| 270 | value = pdf_field_value(ctx, ch_widget->obj); |
| 271 | |
| 272 | ui_dialog_begin(400, (ui.gridsize+4)*3 + ui.lineheight*(label_h-1)); |
| 273 | { |
| 274 | ui_layout(T, X, NW, 2, 2); |
| 275 | |
| 276 | ui_label("%s" , label); |
| 277 | choice = ui_select("Widget/Ch" , value, options, n); |
| 278 | if (choice >= 0) |
| 279 | pdf_set_choice_field_value(ctx, ch_widget, options[choice]); |
| 280 | |
| 281 | ui_layout(B, X, NW, 2, 2); |
| 282 | ui_panel_begin(0, ui.gridsize, 0, 0, 0); |
| 283 | { |
| 284 | ui_layout(R, NONE, S, 0, 0); |
| 285 | if (ui_button("Cancel" ) || (!ui.focus && ui.key == KEY_ESCAPE)) |
| 286 | ui.dialog = NULL; |
| 287 | ui_spacer(); |
| 288 | if (ui_button("Okay" )) |
| 289 | { |
| 290 | if (pdf_update_page(ctx, ch_widget->page)) |
| 291 | render_page(); |
| 292 | ui.dialog = NULL; |
| 293 | } |
| 294 | } |
| 295 | ui_panel_end(); |
| 296 | } |
| 297 | ui_dialog_end(); |
| 298 | |
| 299 | fz_free(ctx, options); |
| 300 | } |
| 301 | |
| 302 | void do_widget_canvas(fz_irect canvas_area) |
| 303 | { |
| 304 | pdf_widget *widget; |
| 305 | fz_rect bounds; |
| 306 | fz_irect area; |
| 307 | |
| 308 | if (!pdf) |
| 309 | return; |
| 310 | |
| 311 | for (widget = pdf_first_widget(ctx, page); widget; widget = pdf_next_widget(ctx, widget)) |
| 312 | { |
| 313 | bounds = pdf_bound_widget(ctx, widget); |
| 314 | bounds = fz_transform_rect(bounds, view_page_ctm); |
| 315 | area = fz_irect_from_rect(bounds); |
| 316 | |
| 317 | if (ui_mouse_inside(canvas_area) && ui_mouse_inside(area)) |
| 318 | { |
| 319 | if (!widget->is_hot) |
| 320 | pdf_annot_event_enter(ctx, widget); |
| 321 | widget->is_hot = 1; |
| 322 | |
| 323 | ui.hot = widget; |
| 324 | if (!ui.active && ui.down) |
| 325 | { |
| 326 | ui.active = widget; |
| 327 | pdf_annot_event_down(ctx, widget); |
| 328 | if (selected_annot != widget) |
| 329 | { |
| 330 | if (selected_annot && pdf_annot_type(ctx, selected_annot) == PDF_ANNOT_WIDGET) |
| 331 | pdf_annot_event_blur(ctx, selected_annot); |
| 332 | selected_annot = widget; |
| 333 | pdf_annot_event_focus(ctx, widget); |
| 334 | } |
| 335 | } |
| 336 | } |
| 337 | else |
| 338 | { |
| 339 | if (widget->is_hot) |
| 340 | pdf_annot_event_exit(ctx, widget); |
| 341 | widget->is_hot = 0; |
| 342 | } |
| 343 | |
| 344 | /* Set is_hot and is_active to select current appearance */ |
| 345 | widget->is_active = (ui.active == widget && ui.down); |
| 346 | |
| 347 | if (showform) |
| 348 | { |
| 349 | glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); |
| 350 | glEnable(GL_BLEND); |
| 351 | glColor4f(0, 0, 1, 0.1f); |
| 352 | glRectf(area.x0, area.y0, area.x1, area.y1); |
| 353 | glDisable(GL_BLEND); |
| 354 | } |
| 355 | |
| 356 | if (ui.active == widget || (!ui.active && ui.hot == widget)) |
| 357 | { |
| 358 | glLineStipple(1, 0xAAAA); |
| 359 | glEnable(GL_LINE_STIPPLE); |
| 360 | glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO); |
| 361 | glEnable(GL_BLEND); |
| 362 | glColor4f(1, 1, 1, 1); |
| 363 | glBegin(GL_LINE_LOOP); |
| 364 | glVertex2f(area.x0-0.5f, area.y0-0.5f); |
| 365 | glVertex2f(area.x1+0.5f, area.y0-0.5f); |
| 366 | glVertex2f(area.x1+0.5f, area.y1+0.5f); |
| 367 | glVertex2f(area.x0-0.5f, area.y1+0.5f); |
| 368 | glEnd(); |
| 369 | glDisable(GL_BLEND); |
| 370 | glDisable(GL_LINE_STIPPLE); |
| 371 | } |
| 372 | |
| 373 | if (ui.hot == widget && ui.active == widget && !ui.down) |
| 374 | { |
| 375 | pdf_annot_event_up(ctx, widget); |
| 376 | |
| 377 | if (pdf_widget_type(ctx, widget) == PDF_WIDGET_TYPE_SIGNATURE) |
| 378 | { |
| 379 | show_sig_dialog(widget); |
| 380 | } |
| 381 | else |
| 382 | { |
| 383 | if (pdf_field_flags(ctx, widget->obj) & PDF_FIELD_IS_READ_ONLY) |
| 384 | continue; |
| 385 | |
| 386 | switch (pdf_widget_type(ctx, widget)) |
| 387 | { |
| 388 | default: |
| 389 | break; |
| 390 | case PDF_WIDGET_TYPE_CHECKBOX: |
| 391 | case PDF_WIDGET_TYPE_RADIOBUTTON: |
| 392 | pdf_toggle_widget(ctx, widget); |
| 393 | break; |
| 394 | case PDF_WIDGET_TYPE_TEXT: |
| 395 | show_tx_dialog(widget); |
| 396 | break; |
| 397 | case PDF_WIDGET_TYPE_COMBOBOX: |
| 398 | case PDF_WIDGET_TYPE_LISTBOX: |
| 399 | ui.dialog = ch_dialog; |
| 400 | ch_widget = widget; |
| 401 | break; |
| 402 | } |
| 403 | } |
| 404 | |
| 405 | } |
| 406 | } |
| 407 | |
| 408 | if (pdf_update_page(ctx, page)) |
| 409 | render_page(); |
| 410 | } |
| 411 | |