aboutsummaryrefslogtreecommitdiff
path: root/src/cli/util/gnunet-qr.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/cli/util/gnunet-qr.c')
-rw-r--r--src/cli/util/gnunet-qr.c597
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 */
43static 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 */
50static 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 */
58static char *pngfilename = NULL;
59#endif
60
61/**
62 * Requested verbosity.
63 */
64static unsigned int verbosity = 0;
65
66/**
67 * Child process handle.
68 */
69struct GNUNET_OS_Process *childproc = NULL;
70
71/**
72 * Child process handle for waiting.
73 */
74static 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 */
96static void
97shutdown_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 */
117static void
118wait_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 */
150static void
151handle_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 */
234static const zbar_symbol_t *
235get_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 */
291static char *
292run_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 */
340static char *
341png_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 */
462static char *
463run_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 */
523static void
524run (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
565int
566main (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}