cpp: Add wrapper for gpgme_set_global_flag
[gpgme.git] / tests / json / t-json.c
1 /* t-json.c - Regression test.
2  * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
3  *                    Software engineering by Intevation GmbH
4  *
5  * This file is part of GPGME.
6  *
7  * GPGME is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU Lesser General Public License as
9  * published by the Free Software Foundation; either version 2.1 of
10  * the License, or (at your option) any later version.
11  *
12  * GPGME is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this program; if not, see <https://gnu.org/licenses/>.
19  * SPDX-License-Identifier: LGPL-2.1-or-later
20  */
21
22 #ifdef HAVE_CONFIG_H
23 # include <config.h>
24 #endif
25
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <errno.h>
30 #include <sys/stat.h>
31
32 #include <gpgme.h>
33 #include <gpg-error.h>
34
35 #include "../gpg/t-support.h"
36 #include "../../src/cJSON.h"
37
38 /* Register tests here */
39 static const char*tests[] = { "t-config", "t-version",
40     "t-keylist", "t-keylist-secret", "t-decrypt", "t-config-opt",
41     "t-encrypt", "t-encrypt-sign", "t-sign", "t-verify",
42     "t-decrypt-verify", "t-export", "t-createkey",
43     "t-export-secret-info", "t-chunking", "t-sig-notations",
44     /* For these two the order is important
45      * as t-import imports the deleted key from t-delete */
46     "t-delete", "t-import",
47     NULL };
48
49 static int verbose = 0;
50
51
52 /* Read the next number in the version string STR and return it in
53    *NUMBER.  Return a pointer to the tail of STR after parsing, or
54    *NULL if the version string was invalid.  */
55 static const char *
56 parse_version_number (const char *str, int *number)
57 {
58 #define MAXVAL ((INT_MAX - 10) / 10)
59   int val = 0;
60
61   /* Leading zeros are not allowed.  */
62   if (*str == '0' && isdigit(str[1]))
63     return NULL;
64
65   while (isdigit (*str) && val <= MAXVAL)
66     {
67       val *= 10;
68       val += *(str++) - '0';
69     }
70   *number = val;
71   return val > MAXVAL ? NULL : str;
72 }
73
74
75 /* Parse the version string STR in the format MAJOR.MINOR.MICRO (for
76    example, 9.3.2) and return the components in MAJOR, MINOR and MICRO
77    as integers.  The function returns the tail of the string that
78    follows the version number.  This might be the empty string if there
79    is nothing following the version number, or a patchlevel.  The
80    function returns NULL if the version string is not valid.  */
81 static const char *
82 parse_version_string (const char *str, int *major, int *minor, int *micro)
83 {
84   str = parse_version_number (str, major);
85   if (!str || *str != '.')
86     return NULL;
87   str++;
88
89   str = parse_version_number (str, minor);
90   if (!str || *str != '.')
91     return NULL;
92   str++;
93
94   str = parse_version_number (str, micro);
95   if (!str)
96     return NULL;
97
98   /* A patchlevel might follow.  */
99   return str;
100 }
101
102
103 /* Return true if MY_VERSION is at least REQ_VERSION, and false
104    otherwise.  */
105 static int
106 compare_versions (const char *my_version,
107                   const char *rq_version)
108 {
109   int my_major, my_minor, my_micro;
110   int rq_major, rq_minor, rq_micro;
111   const char *my_plvl, *rq_plvl;
112
113   if (!rq_version)
114     return 1;
115   if (!my_version)
116     return 0;
117
118   my_plvl = parse_version_string (my_version, &my_major, &my_minor, &my_micro);
119   if (!my_plvl)
120     return 0;
121
122   rq_plvl = parse_version_string (rq_version, &rq_major, &rq_minor, &rq_micro);
123   if (!rq_plvl)
124     return 0;
125
126   if (my_major > rq_major
127       || (my_major == rq_major && my_minor > rq_minor)
128       || (my_major == rq_major && my_minor == rq_minor
129           && my_micro > rq_micro)
130       || (my_major == rq_major && my_minor == rq_minor
131           && my_micro == rq_micro && strcmp (my_plvl, rq_plvl) >= 0))
132     return 1;
133
134   return 0;
135 }
136
137 /* Return true if we have the required gpg version.
138
139    This should probably go into gpgrt or gpgme proper.
140 */
141 static int
142 check_gpg_version (const char *req_version)
143 {
144   gpgme_engine_info_t engine_info;
145   init_gpgme (GPGME_PROTOCOL_OpenPGP);
146
147   fail_if_err (gpgme_get_engine_info (&engine_info));
148   for (; engine_info; engine_info = engine_info->next)
149     if (engine_info->protocol == GPGME_PROTOCOL_OpenPGP)
150       break;
151
152   test (engine_info);
153
154   return compare_versions (engine_info->version, req_version);
155 }
156
157 static char *
158 get_file (const char *fname)
159 {
160   gpg_error_t err;
161   gpgrt_stream_t fp;
162   struct stat st;
163   char *buf;
164   size_t buflen;
165
166   fp = gpgrt_fopen (fname, "r");
167   if (!fp)
168     {
169       err = gpg_error_from_syserror ();
170       fprintf (stderr, "Error: can't open '%s': %s\n", fname,
171                gpg_strerror (err));
172       return NULL;
173     }
174
175   if (fstat (gpgrt_fileno(fp), &st))
176     {
177       err = gpg_error_from_syserror ();
178       fprintf (stderr, "Error: can't stat '%s': %s\n", fname,
179                gpg_strerror (err));
180       gpgrt_fclose (fp);
181       return NULL;
182     }
183
184   buflen = st.st_size;
185   buf = malloc (buflen+1);
186   if (!buf)
187     {
188       fprintf (stderr, "Error: no mem\n");
189       gpgrt_fclose (fp);
190       return NULL;
191     }
192
193   if (gpgrt_fread (buf, buflen, 1, fp) != 1)
194     {
195       err = gpg_error_from_syserror ();
196       fprintf (stderr, "error reading '%s': %s\n", fname, gpg_strerror (err));
197       gpgrt_fclose (fp);
198       free (buf);
199       return NULL;
200     }
201   buf[buflen] = 0;
202   gpgrt_fclose (fp);
203
204   return buf;
205 }
206
207 /* Check that the element needle exists in hay. Returns 0 if
208    the needle was found. */
209 int
210 test_contains (cjson_t needle, cjson_t hay)
211 {
212   if (verbose == 2)
213     fprintf (stderr, "%s: -------checking-------- "
214                      "\n%s\n -------against-------- \n%s\n",
215              nonnull (needle->string), cJSON_Print (needle),
216              cJSON_Print (hay));
217
218   /* Type check. This automatically checks bool vals and NULL */
219   if (needle->type != hay->type)
220     {
221       if (verbose)
222         fprintf (stderr, "%s: type mismatch expected %i got %i\n",
223                  nonnull (needle->string), needle->type,
224                  hay->type);
225       return 1;
226     }
227
228   /* First the simple types */
229   if (cjson_is_number (needle))
230     {
231       if (needle->valueint != hay->valueint)
232         {
233           if (verbose)
234             fprintf (stderr, "%s: value mismatch. Expected %i got %i\n",
235                      nonnull (needle->string), needle->valueint,
236                      hay->valueint);
237           return 1;
238         }
239     }
240   if (cjson_is_string (needle))
241     {
242       if (strcmp (needle->valuestring, hay->valuestring) &&
243           /* Use * as a general don't care placeholder */
244           strcmp (needle->valuestring, "*"))
245         {
246           if (verbose)
247             fprintf (stderr, "%s: string mismatch Expected '%s' got '%s'\n",
248                      needle->string, needle->valuestring, hay->valuestring);
249           return 1;
250         }
251     }
252
253   /* Now the complex types */
254   if (needle->child)
255     {
256       if (!hay->child)
257         {
258           fprintf (stderr, "Depth mismatch. Expected child for %s\n",
259                    nonnull (needle->string));
260         }
261       if (test_contains (needle->child, hay->child))
262         {
263           int found = 0;
264           for (cjson_t hit = hay->child; hit; hit = hit->next)
265             {
266               found |= !test_contains (needle->child, hit);
267               if (found)
268                 {
269                   break;
270                 }
271             }
272           if (!found)
273             {
274               return 1;
275             }
276         }
277     }
278
279   if (needle->prev)
280     {
281       return 0;
282     }
283
284   /* Walk elements of an array */
285   for (cjson_t it = needle->next; it; it = it->next)
286     {
287       int found = 0;
288       if (!it->string && it->child)
289         {
290           /* Try out all other anonymous children on the same level */
291           cjson_t hit = hay;
292           /* Return to the beginning */
293           while (hit->prev)
294             {
295               hit = hit->prev;
296             }
297           for (; hit && hit->child; hit = hit->next)
298             {
299               found |= !test_contains (it->child, hit->child);
300               if (found)
301                 {
302                   break;
303                 }
304             }
305           if (!found)
306             {
307               return 1;
308             }
309           continue;
310         }
311
312       /* Try the children in the haystack */
313       for (cjson_t hit = hay; hit; hit = hit->next)
314         {
315           if (hit->string && it->string &&
316               !strcmp (hit->string, it->string))
317             {
318               found = 1;
319               if (test_contains (it, hit))
320                 {
321                   return 1;
322                 }
323             }
324         }
325       if (!found)
326         {
327           if (verbose)
328             fprintf (stderr, "Failed to find '%s' in list\n",
329                      nonnull (it->string));
330           return 1;
331         }
332     }
333   return 0;
334 }
335
336
337 int
338 check_response (const char *response, const char *expected)
339 {
340   cjson_t hay;
341   cjson_t needle;
342   int rc;
343   size_t erroff;
344
345   hay = cJSON_Parse (response, &erroff);
346
347   if (!hay)
348     {
349       fprintf (stderr, "Failed to parse json at %i:\n%s\n", (int) erroff,
350                response);
351       return 1;
352     }
353   needle = cJSON_Parse (expected, &erroff);
354   if (!needle)
355     {
356       fprintf (stderr, "Failed to parse json at %i:\n%s\n", (int) erroff,
357                expected);
358       cJSON_Delete (hay);
359       return 1;
360     }
361
362   rc = test_contains (needle, hay);
363
364   cJSON_Delete (needle);
365   cJSON_Delete (hay);
366   return rc;
367 }
368
369
370 int
371 run_test (const char *test, const char *gpgme_json)
372 {
373   gpgme_ctx_t ctx;
374   gpgme_data_t json_stdin = NULL;
375   gpgme_data_t json_stdout = NULL;
376   gpgme_data_t json_stderr = NULL;
377   char *test_in;
378   char *test_out;
379   const char *argv[3];
380   char *response;
381   char *expected = NULL;
382   size_t response_size;
383   int rc = 0;
384   const char *top_srcdir = getenv ("top_srcdir");
385
386   if (!top_srcdir)
387     {
388       fprintf (stderr, "Error top_srcdir environment variable not set\n");
389       exit(1);
390     }
391
392   gpgrt_asprintf (&test_in, "%s/tests/json/%s.in.json",
393                   top_srcdir, test);
394   gpgrt_asprintf (&test_out, "%s/tests/json/%s.out.json",
395                   top_srcdir, test);
396
397   printf ("Running %s...\n", test);
398
399   fail_if_err (gpgme_new (&ctx));
400
401   gpgme_set_protocol (ctx, GPGME_PROTOCOL_SPAWN);
402
403   fail_if_err (gpgme_data_new_from_file (&json_stdin, test_in, 1));
404   fail_if_err (gpgme_data_new (&json_stdout));
405   fail_if_err (gpgme_data_new (&json_stderr));
406
407   argv[0] = gpgme_json;
408   argv[1] = "-s";
409   argv[2] = NULL;
410
411   fail_if_err (gpgme_op_spawn (ctx, gpgme_json, argv,
412                                json_stdin,
413                                json_stdout,
414                                json_stderr,
415                                0));
416   response = gpgme_data_release_and_get_mem (json_stdout,
417                                              &response_size);
418   if (response_size)
419     {
420       expected = get_file (test_out);
421
422       test (expected);
423
424       rc = check_response (response, expected);
425     }
426   else
427     {
428       rc = 1;
429     }
430
431   if (!rc)
432     {
433       printf (" success\n");
434       gpgme_data_release (json_stderr);
435     }
436   else
437     {
438       char *buf;
439       size_t size;
440
441       buf = gpgme_data_release_and_get_mem (json_stderr, &size);
442       printf (" failed%s\n", response_size ? "" :
443                              ", no response from gpgme-json");
444       if (size)
445         {
446           printf ("gpgme-json stderr:\n%.*s\n", (int)size, buf);
447         }
448       free (buf);
449     }
450
451   free (test_out);
452   free (test_in);
453   free (response);
454   free (expected);
455   gpgme_data_release (json_stdin);
456   gpgme_release (ctx);
457
458   return rc;
459 }
460
461 int
462 main (int argc, char *argv[])
463 {
464   const char *gpgme_json = getenv ("gpgme_json");
465   int last_argc = -1;
466
467   if (argc)
468     { argc--; argv++; }
469
470
471   while (argc && last_argc != argc )
472     {
473       last_argc = argc;
474       if (!strcmp (*argv, "--verbose"))
475         {
476           verbose++;
477           argc--; argv++;
478         }
479     }
480
481   if (!check_gpg_version ("2.1.18"))
482     {
483       /* Lets not break too much or have to test all combinations */
484       printf ("Testsuite skipped. Minimum GnuPG version (2.1.18) "
485               "not found.\n");
486       exit(0);
487     }
488
489   init_gpgme (GPGME_PROTOCOL_SPAWN);
490
491   for (const char **test = tests; *test; test++)
492     {
493       if (run_test (*test, gpgme_json))
494         {
495           exit(1);
496         }
497     }
498   return 0;
499 }