diff options
Diffstat (limited to 'src/cli/util/gnunet-qr.c')
-rw-r--r-- | src/cli/util/gnunet-qr.c | 597 |
1 files changed, 597 insertions, 0 deletions
diff --git a/src/cli/util/gnunet-qr.c b/src/cli/util/gnunet-qr.c new file mode 100644 index 000000000..d9b873c05 --- /dev/null +++ b/src/cli/util/gnunet-qr.c | |||
@@ -0,0 +1,597 @@ | |||
1 | /* | ||
2 | This file is part of GNUnet. | ||
3 | Copyright (C) 2013-2019 GNUnet e.V. | ||
4 | |||
5 | GNUnet is free software: you can redistribute it and/or modify it | ||
6 | under the terms of the GNU Affero General Public License as published | ||
7 | by the Free Software Foundation, either version 3 of the License, | ||
8 | or (at your option) any later version. | ||
9 | |||
10 | GNUnet is distributed in the hope that it will be useful, but | ||
11 | WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
13 | Affero General Public License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU Affero General Public License | ||
16 | along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
17 | |||
18 | SPDX-License-Identifier: AGPL3.0-or-later | ||
19 | */ | ||
20 | /** | ||
21 | * @file util/gnunet-qr.c | ||
22 | * @author Hartmut Goebel (original implementation) | ||
23 | * @author Martin Schanzenbach (integrate gnunet-uri) | ||
24 | * @author Christian Grothoff (error handling) | ||
25 | */ | ||
26 | #include "platform.h" | ||
27 | #include <stdio.h> | ||
28 | #include <stdbool.h> | ||
29 | #include <signal.h> | ||
30 | #include <zbar.h> | ||
31 | |||
32 | |||
33 | #include "gnunet_util_lib.h" | ||
34 | |||
35 | #if HAVE_PNG | ||
36 | #include <png.h> | ||
37 | #endif | ||
38 | |||
39 | /** | ||
40 | * Global exit code. | ||
41 | * Set to non-zero if an error occurs after the scheduler has started. | ||
42 | */ | ||
43 | static int exit_code = 0; | ||
44 | |||
45 | /** | ||
46 | * Video device to capture from. | ||
47 | * Used by default if PNG support is disabled or no PNG file is specified. | ||
48 | * Defaults to /dev/video0. | ||
49 | */ | ||
50 | static char *device = NULL; | ||
51 | |||
52 | #if HAVE_PNG | ||
53 | /** | ||
54 | * Name of the file to read from. | ||
55 | * If the file is not a PNG-encoded image of a QR code, an error will be | ||
56 | * thrown. | ||
57 | */ | ||
58 | static char *pngfilename = NULL; | ||
59 | #endif | ||
60 | |||
61 | /** | ||
62 | * Requested verbosity. | ||
63 | */ | ||
64 | static unsigned int verbosity = 0; | ||
65 | |||
66 | /** | ||
67 | * Child process handle. | ||
68 | */ | ||
69 | struct GNUNET_OS_Process *childproc = NULL; | ||
70 | |||
71 | /** | ||
72 | * Child process handle for waiting. | ||
73 | */ | ||
74 | static struct GNUNET_ChildWaitHandle *waitchildproc = NULL; | ||
75 | |||
76 | /** | ||
77 | * Macro to handle verbosity when printing messages. | ||
78 | */ | ||
79 | #define LOG(fmt, ...) \ | ||
80 | do \ | ||
81 | { \ | ||
82 | if (0 < verbosity) \ | ||
83 | { \ | ||
84 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, fmt, ##__VA_ARGS__); \ | ||
85 | if (verbosity > 1) \ | ||
86 | { \ | ||
87 | fprintf (stdout, fmt, ##__VA_ARGS__); \ | ||
88 | } \ | ||
89 | } \ | ||
90 | } \ | ||
91 | while (0) | ||
92 | |||
93 | /** | ||
94 | * Executed when program is terminating. | ||
95 | */ | ||
96 | static void | ||
97 | shutdown_program (void *cls) | ||
98 | { | ||
99 | if (NULL != waitchildproc) | ||
100 | { | ||
101 | GNUNET_wait_child_cancel (waitchildproc); | ||
102 | } | ||
103 | if (NULL != childproc) | ||
104 | { | ||
105 | /* A bit brutal, but this process is terminating so we're out of time */ | ||
106 | GNUNET_OS_process_kill (childproc, SIGKILL); | ||
107 | } | ||
108 | } | ||
109 | |||
110 | /** | ||
111 | * Callback executed when the child process terminates. | ||
112 | * | ||
113 | * @param cls closure | ||
114 | * @param type status of the child process | ||
115 | * @param code the exit code of the child process | ||
116 | */ | ||
117 | static void | ||
118 | wait_child (void *cls, | ||
119 | enum GNUNET_OS_ProcessStatusType type, | ||
120 | long unsigned int code) | ||
121 | { | ||
122 | GNUNET_OS_process_destroy (childproc); | ||
123 | childproc = NULL; | ||
124 | waitchildproc = NULL; | ||
125 | |||
126 | char *uri = cls; | ||
127 | |||
128 | if (0 != exit_code) | ||
129 | { | ||
130 | fprintf (stdout, _("Failed to add URI %s\n"), uri); | ||
131 | } | ||
132 | else | ||
133 | { | ||
134 | fprintf (stdout, _("Added URI %s\n"), uri); | ||
135 | } | ||
136 | |||
137 | GNUNET_free (uri); | ||
138 | |||
139 | GNUNET_SCHEDULER_shutdown (); | ||
140 | } | ||
141 | |||
142 | /** | ||
143 | * Dispatch URIs to the appropriate GNUnet helper process. | ||
144 | * | ||
145 | * @param cls closure | ||
146 | * @param uri URI to dispatch | ||
147 | * @param cfgfile name of the configuration file in use | ||
148 | * @param cfg the configuration in use | ||
149 | */ | ||
150 | static void | ||
151 | handle_uri (void *cls, | ||
152 | const char *uri, | ||
153 | const char *cfgfile, | ||
154 | const struct GNUNET_CONFIGURATION_Handle *cfg) | ||
155 | { | ||
156 | const char *cursor = uri; | ||
157 | |||
158 | if (0 != strncasecmp ("gnunet://", uri, strlen ("gnunet://"))) | ||
159 | { | ||
160 | fprintf (stderr, | ||
161 | _("Invalid URI: does not start with `gnunet://'\n")); | ||
162 | exit_code = 1; | ||
163 | return; | ||
164 | } | ||
165 | |||
166 | cursor += strlen ("gnunet://"); | ||
167 | |||
168 | const char *slash = strchr (cursor, '/'); | ||
169 | if (NULL == slash) | ||
170 | { | ||
171 | fprintf (stderr, _("Invalid URI: fails to specify a subsystem\n")); | ||
172 | exit_code = 1; | ||
173 | return; | ||
174 | } | ||
175 | |||
176 | char *subsystem = GNUNET_strndup (cursor, slash - cursor); | ||
177 | char *program = NULL; | ||
178 | |||
179 | if (GNUNET_OK != | ||
180 | GNUNET_CONFIGURATION_get_value_string (cfg, "uri", subsystem, &program)) | ||
181 | { | ||
182 | fprintf (stderr, _("No known handler for subsystem `%s'\n"), subsystem); | ||
183 | GNUNET_free (subsystem); | ||
184 | exit_code = 1; | ||
185 | return; | ||
186 | } | ||
187 | |||
188 | GNUNET_free (subsystem); | ||
189 | |||
190 | char **childargv = NULL; | ||
191 | unsigned int childargc = 0; | ||
192 | |||
193 | for (const char *token=strtok (program, " "); | ||
194 | NULL!=token; | ||
195 | token=strtok(NULL, " ")) | ||
196 | { | ||
197 | GNUNET_array_append (childargv, childargc, GNUNET_strdup (token)); | ||
198 | } | ||
199 | GNUNET_array_append (childargv, childargc, GNUNET_strdup (uri)); | ||
200 | GNUNET_array_append (childargv, childargc, NULL); | ||
201 | |||
202 | childproc = GNUNET_OS_start_process_vap (GNUNET_OS_INHERIT_STD_ALL, | ||
203 | NULL, | ||
204 | NULL, | ||
205 | NULL, | ||
206 | childargv[0], | ||
207 | childargv); | ||
208 | for (size_t i=0; i<childargc-1; ++i) | ||
209 | { | ||
210 | GNUNET_free (childargv[i]); | ||
211 | } | ||
212 | |||
213 | GNUNET_array_grow (childargv, childargc, 0); | ||
214 | |||
215 | if (NULL == childproc) | ||
216 | { | ||
217 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
218 | _("Unable to start child process `%s'\n"), | ||
219 | program); | ||
220 | GNUNET_free (program); | ||
221 | exit_code = 1; | ||
222 | return; | ||
223 | } | ||
224 | |||
225 | waitchildproc = GNUNET_wait_child (childproc, &wait_child, (void *)uri); | ||
226 | } | ||
227 | |||
228 | /** | ||
229 | * Obtain a QR code symbol from @a proc. | ||
230 | * | ||
231 | * @param proc the zbar processor to use | ||
232 | * @return NULL on error | ||
233 | */ | ||
234 | static const zbar_symbol_t * | ||
235 | get_symbol (zbar_processor_t *proc) | ||
236 | { | ||
237 | if (0 != zbar_processor_parse_config (proc, "enable")) | ||
238 | { | ||
239 | GNUNET_break (0); | ||
240 | return NULL; | ||
241 | } | ||
242 | |||
243 | int r = zbar_processor_init (proc, device, 1); | ||
244 | if (0 != r) | ||
245 | { | ||
246 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
247 | _("Failed to open device: `%s': %d\n"), | ||
248 | device, | ||
249 | r); | ||
250 | return NULL; | ||
251 | } | ||
252 | |||
253 | r = zbar_processor_set_visible (proc, 1); | ||
254 | r += zbar_processor_set_active (proc, 1); | ||
255 | if (0 != r) | ||
256 | { | ||
257 | GNUNET_break (0); | ||
258 | return NULL; | ||
259 | } | ||
260 | |||
261 | LOG (_("Capturing...\n")); | ||
262 | |||
263 | int n = zbar_process_one (proc, -1); | ||
264 | |||
265 | zbar_processor_set_active (proc, 0); | ||
266 | zbar_processor_set_visible (proc, 0); | ||
267 | |||
268 | if (-1 == n) | ||
269 | { | ||
270 | LOG (_("No captured images\n")); | ||
271 | return NULL; | ||
272 | } | ||
273 | |||
274 | LOG(_("Got %d images\n"), n); | ||
275 | |||
276 | const zbar_symbol_set_t *symbols = zbar_processor_get_results (proc); | ||
277 | if (NULL == symbols) | ||
278 | { | ||
279 | GNUNET_break (0); | ||
280 | return NULL; | ||
281 | } | ||
282 | |||
283 | return zbar_symbol_set_first_symbol (symbols); | ||
284 | } | ||
285 | |||
286 | /** | ||
287 | * Run the zbar QR code parser. | ||
288 | * | ||
289 | * @return NULL on error | ||
290 | */ | ||
291 | static char * | ||
292 | run_zbar (void) | ||
293 | { | ||
294 | zbar_processor_t *proc = zbar_processor_create (1); | ||
295 | if (NULL == proc) | ||
296 | { | ||
297 | GNUNET_break (0); | ||
298 | return NULL; | ||
299 | } | ||
300 | |||
301 | if (NULL == device) | ||
302 | { | ||
303 | device = GNUNET_strdup ("/dev/video0"); | ||
304 | } | ||
305 | |||
306 | const zbar_symbol_t *symbol = get_symbol (proc); | ||
307 | if (NULL == symbol) | ||
308 | { | ||
309 | zbar_processor_destroy (proc); | ||
310 | return NULL; | ||
311 | } | ||
312 | |||
313 | const char *data = zbar_symbol_get_data (symbol); | ||
314 | if (NULL == data) | ||
315 | { | ||
316 | GNUNET_break (0); | ||
317 | zbar_processor_destroy (proc); | ||
318 | return NULL; | ||
319 | } | ||
320 | |||
321 | LOG (_("Found %s: \"%s\"\n"), | ||
322 | zbar_get_symbol_name (zbar_symbol_get_type (symbol)), | ||
323 | data); | ||
324 | |||
325 | char *copy = GNUNET_strdup (data); | ||
326 | |||
327 | zbar_processor_destroy (proc); | ||
328 | GNUNET_free (device); | ||
329 | |||
330 | return copy; | ||
331 | } | ||
332 | |||
333 | #if HAVE_PNG | ||
334 | /** | ||
335 | * Decode the PNG-encoded file to a raw byte buffer. | ||
336 | * | ||
337 | * @param width[out] where to store the image width | ||
338 | * @param height[out] where to store the image height | ||
339 | */ | ||
340 | static char * | ||
341 | png_parse (uint32_t *width, uint32_t *height) | ||
342 | { | ||
343 | if (NULL == width || NULL == height) | ||
344 | { | ||
345 | return NULL; | ||
346 | } | ||
347 | |||
348 | FILE *pngfile = fopen (pngfilename, "rb"); | ||
349 | if (NULL == pngfile) | ||
350 | { | ||
351 | return NULL; | ||
352 | } | ||
353 | |||
354 | unsigned char header[8]; | ||
355 | if (8 != fread (header, 1, 8, pngfile)) | ||
356 | { | ||
357 | fclose (pngfile); | ||
358 | return NULL; | ||
359 | } | ||
360 | |||
361 | if (png_sig_cmp (header, 0, 8)) | ||
362 | { | ||
363 | fclose (pngfile); | ||
364 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
365 | _("%s is not a PNG file\n"), | ||
366 | pngfilename); | ||
367 | fprintf (stderr, _("%s is not a PNG file\n"), pngfilename); | ||
368 | return NULL; | ||
369 | } | ||
370 | |||
371 | /* libpng's default error handling might or might not conflict with GNUnet's | ||
372 | scheduler and event loop. Beware of strange interactions. */ | ||
373 | png_structp png = png_create_read_struct (PNG_LIBPNG_VER_STRING, | ||
374 | NULL, | ||
375 | NULL, | ||
376 | NULL); | ||
377 | if (NULL == png) | ||
378 | { | ||
379 | GNUNET_break (0); | ||
380 | fclose (pngfile); | ||
381 | return NULL; | ||
382 | } | ||
383 | |||
384 | png_infop pnginfo = png_create_info_struct (png); | ||
385 | if (NULL == pnginfo) | ||
386 | { | ||
387 | GNUNET_break (0); | ||
388 | png_destroy_read_struct (&png, NULL, NULL); | ||
389 | fclose (pngfile); | ||
390 | return NULL; | ||
391 | } | ||
392 | |||
393 | if (setjmp (png_jmpbuf (png))) | ||
394 | { | ||
395 | GNUNET_break (0); | ||
396 | png_destroy_read_struct (&png, &pnginfo, NULL); | ||
397 | fclose (pngfile); | ||
398 | return NULL; | ||
399 | } | ||
400 | |||
401 | png_init_io (png, pngfile); | ||
402 | png_set_sig_bytes (png, 8); | ||
403 | |||
404 | png_read_info (png, pnginfo); | ||
405 | |||
406 | png_byte pngcolor = png_get_color_type (png, pnginfo); | ||
407 | png_byte pngdepth = png_get_bit_depth (png, pnginfo); | ||
408 | |||
409 | /* Normalize picture --- based on a zbar example */ | ||
410 | if (0 != (pngcolor & PNG_COLOR_TYPE_PALETTE)) | ||
411 | { | ||
412 | png_set_palette_to_rgb (png); | ||
413 | } | ||
414 | |||
415 | if (pngcolor == PNG_COLOR_TYPE_GRAY && pngdepth < 8) | ||
416 | { | ||
417 | png_set_expand_gray_1_2_4_to_8 (png); | ||
418 | } | ||
419 | |||
420 | if (16 == pngdepth) | ||
421 | { | ||
422 | png_set_strip_16 (png); | ||
423 | } | ||
424 | |||
425 | if (0 != (pngcolor & PNG_COLOR_MASK_ALPHA)) | ||
426 | { | ||
427 | png_set_strip_alpha (png); | ||
428 | } | ||
429 | |||
430 | if (0 != (pngcolor & PNG_COLOR_MASK_COLOR)) | ||
431 | { | ||
432 | png_set_rgb_to_gray_fixed (png, 1, -1, -1); | ||
433 | } | ||
434 | |||
435 | png_uint_32 pngwidth = png_get_image_width (png, pnginfo); | ||
436 | png_uint_32 pngheight = png_get_image_height (png, pnginfo); | ||
437 | |||
438 | char *buffer = GNUNET_new_array (pngwidth * pngheight, char); | ||
439 | png_bytepp rows = GNUNET_new_array (pngheight, png_bytep); | ||
440 | |||
441 | for (png_uint_32 i=0; i<pngheight; ++i) | ||
442 | { | ||
443 | rows[i] = (unsigned char *)buffer + (pngwidth * i); | ||
444 | } | ||
445 | |||
446 | png_read_image (png, rows); | ||
447 | |||
448 | GNUNET_free (rows); | ||
449 | fclose (pngfile); | ||
450 | |||
451 | *width = pngwidth; | ||
452 | *height = pngheight; | ||
453 | |||
454 | return buffer; | ||
455 | } | ||
456 | |||
457 | /** | ||
458 | * Parse a PNG-encoded file for a QR code. | ||
459 | * | ||
460 | * @return NULL on error | ||
461 | */ | ||
462 | static char * | ||
463 | run_png_reader (void) | ||
464 | { | ||
465 | uint32_t width = 0; | ||
466 | uint32_t height = 0; | ||
467 | char *buffer = png_parse (&width, &height); | ||
468 | if (NULL == buffer) | ||
469 | { | ||
470 | return NULL; | ||
471 | } | ||
472 | |||
473 | zbar_image_scanner_t *scanner = zbar_image_scanner_create (); | ||
474 | zbar_image_scanner_set_config (scanner,0, ZBAR_CFG_ENABLE, 1); | ||
475 | |||
476 | zbar_image_t *zimage = zbar_image_create (); | ||
477 | zbar_image_set_format (zimage, zbar_fourcc ('Y', '8', '0', '0')); | ||
478 | zbar_image_set_size (zimage, width, height); | ||
479 | zbar_image_set_data (zimage, buffer, width * height, &zbar_image_free_data); | ||
480 | |||
481 | int n = zbar_scan_image (scanner, zimage); | ||
482 | |||
483 | if (-1 == n) | ||
484 | { | ||
485 | LOG (_("No captured images\n")); | ||
486 | return NULL; | ||
487 | } | ||
488 | |||
489 | LOG(_("Got %d images\n"), n); | ||
490 | |||
491 | const zbar_symbol_t *symbol = zbar_image_first_symbol (zimage); | ||
492 | |||
493 | const char *data = zbar_symbol_get_data (symbol); | ||
494 | if (NULL == data) | ||
495 | { | ||
496 | GNUNET_break (0); | ||
497 | zbar_image_destroy (zimage); | ||
498 | zbar_image_scanner_destroy (scanner); | ||
499 | return NULL; | ||
500 | } | ||
501 | |||
502 | LOG (_("Found %s: \"%s\"\n"), | ||
503 | zbar_get_symbol_name (zbar_symbol_get_type (symbol)), | ||
504 | data); | ||
505 | |||
506 | char *copy = GNUNET_strdup (data); | ||
507 | |||
508 | zbar_image_destroy (zimage); | ||
509 | zbar_image_scanner_destroy (scanner); | ||
510 | |||
511 | return copy; | ||
512 | } | ||
513 | #endif | ||
514 | |||
515 | /** | ||
516 | * Main function executed by the scheduler. | ||
517 | * | ||
518 | * @param cls closure | ||
519 | * @param args remaining command line arguments | ||
520 | * @param cfgfile name of the configuration file being used | ||
521 | * @param cfg the used configuration | ||
522 | */ | ||
523 | static void | ||
524 | run (void *cls, | ||
525 | char *const *args, | ||
526 | const char *cfgfile, | ||
527 | const struct GNUNET_CONFIGURATION_Handle *cfg) | ||
528 | { | ||
529 | char *data = NULL; | ||
530 | |||
531 | GNUNET_SCHEDULER_add_shutdown (&shutdown_program, NULL); | ||
532 | |||
533 | #if HAVE_PNG | ||
534 | if (NULL != pngfilename) | ||
535 | { | ||
536 | data = run_png_reader (); | ||
537 | } | ||
538 | else | ||
539 | #endif | ||
540 | { | ||
541 | data = run_zbar (); | ||
542 | } | ||
543 | |||
544 | if (NULL == data) | ||
545 | { | ||
546 | LOG (_("No data found\n")); | ||
547 | exit_code = 1; | ||
548 | GNUNET_SCHEDULER_shutdown (); | ||
549 | return; | ||
550 | } | ||
551 | |||
552 | handle_uri (cls, data, cfgfile, cfg); | ||
553 | |||
554 | if (0 != exit_code) | ||
555 | { | ||
556 | fprintf (stdout, _("Failed to add URI %s\n"), data); | ||
557 | GNUNET_free (data); | ||
558 | GNUNET_SCHEDULER_shutdown (); | ||
559 | return; | ||
560 | } | ||
561 | |||
562 | LOG (_("Dispatching the URI\n")); | ||
563 | } | ||
564 | |||
565 | int | ||
566 | main (int argc, char *const *argv) | ||
567 | { | ||
568 | struct GNUNET_GETOPT_CommandLineOption options[] = { | ||
569 | GNUNET_GETOPT_option_string ( | ||
570 | 'd', | ||
571 | "device", | ||
572 | "DEVICE", | ||
573 | gettext_noop ("use the video device DEVICE (defaults to /dev/video0)"), | ||
574 | &device), | ||
575 | #if HAVE_PNG | ||
576 | GNUNET_GETOPT_option_string ( | ||
577 | 'f', | ||
578 | "file", | ||
579 | "FILE", | ||
580 | gettext_noop ("read from the PNG-encoded file FILE"), | ||
581 | &pngfilename), | ||
582 | #endif | ||
583 | GNUNET_GETOPT_option_verbose (&verbosity), | ||
584 | GNUNET_GETOPT_OPTION_END, | ||
585 | }; | ||
586 | |||
587 | enum GNUNET_GenericReturnValue ret = | ||
588 | GNUNET_PROGRAM_run (argc, | ||
589 | argv, | ||
590 | "gnunet-qr", | ||
591 | gettext_noop ("Scan a QR code and import the URI read"), | ||
592 | options, | ||
593 | &run, | ||
594 | NULL); | ||
595 | |||
596 | return ((GNUNET_OK == ret) && (0 == exit_code)) ? 0 : 1; | ||
597 | } | ||