diff --git a/tests/gpg/t-support.h b/tests/gpg/t-support.h index a1536c18..b6ad515a 100644 --- a/tests/gpg/t-support.h +++ b/tests/gpg/t-support.h @@ -1,321 +1,218 @@ /* t-support.h - Helper routines for regression tests. Copyright (C) 2000 Werner Koch (dd9jn) Copyright (C) 2001, 2002, 2003, 2004 g10 Code GmbH This file is part of GPGME. GPGME is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. GPGME is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #ifdef HAVE_W32_SYSTEM #include #endif #include #ifndef DIM #define DIM(v) (sizeof(v)/sizeof((v)[0])) #endif #define fail_if_err(err) \ do \ { \ if (err) \ { \ fprintf (stderr, "%s:%d: %s: %s\n", \ __FILE__, __LINE__, gpgme_strsource (err), \ gpgme_strerror (err)); \ exit (1); \ } \ } \ while (0) #ifdef GPGRT_HAVE_MACRO_FUNCTION void GPGRT_ATTR_NORETURN _test (const char *expr, const char *file, int line, const char *func) { fprintf (stderr, "Test \"%s\" in %s failed (%s:%d)\n", expr, func, file, line); exit (1); } # define test(expr) \ ((expr) \ ? (void) 0 \ : _test (#expr, __FILE__, __LINE__, __FUNCTION__)) #else /*!GPGRT_HAVE_MACRO_FUNCTION*/ void _test (const char *expr, const char *file, int line) { fprintf (stderr, "Test \"%s\" failed (%s:%d)\n", expr, file, line); exit (1); } # define test(expr) \ ((expr) \ ? (void) 0 \ : _test (#expr, __FILE__, __LINE__)) #endif /*!GPGRT_HAVE_MACRO_FUNCTION*/ static const char * nonnull (const char *s) { return s? s :"[none]"; } void print_data (gpgme_data_t dh) { #define BUF_SIZE 512 char buf[BUF_SIZE + 1]; int ret; ret = gpgme_data_seek (dh, 0, SEEK_SET); if (ret) fail_if_err (gpgme_err_code_from_errno (errno)); while ((ret = gpgme_data_read (dh, buf, BUF_SIZE)) > 0) fwrite (buf, ret, 1, stdout); if (ret < 0) fail_if_err (gpgme_err_code_from_errno (errno)); } gpgme_error_t passphrase_cb (void *opaque, const char *uid_hint, const char *passphrase_info, int last_was_bad, int fd) { int res; char pass[] = "abc\n"; int passlen = strlen (pass); int off = 0; (void)opaque; (void)uid_hint; (void)passphrase_info; (void)last_was_bad; do { res = gpgme_io_write (fd, &pass[off], passlen - off); if (res > 0) off += res; } while (res > 0 && off != passlen); return off == passlen ? 0 : gpgme_error_from_errno (errno); } char * make_filename (const char *fname) { const char *srcdir = getenv ("srcdir"); char *buf; if (!srcdir) srcdir = "."; buf = malloc (strlen(srcdir) + strlen(fname) + 2); if (!buf) exit (8); strcpy (buf, srcdir); strcat (buf, "/"); strcat (buf, fname); return buf; } void init_gpgme (gpgme_protocol_t proto) { gpgme_error_t err; gpgme_check_version (NULL); setlocale (LC_ALL, ""); gpgme_set_locale (NULL, LC_CTYPE, setlocale (LC_CTYPE, NULL)); #ifndef HAVE_W32_SYSTEM gpgme_set_locale (NULL, LC_MESSAGES, setlocale (LC_MESSAGES, NULL)); #endif err = gpgme_engine_check_version (proto); fail_if_err (err); } void print_import_result (gpgme_import_result_t r) { gpgme_import_status_t st; for (st=r->imports; st; st = st->next) { printf (" fpr: %s err: %d (%s) status:", nonnull (st->fpr), st->result, gpgme_strerror (st->result)); if (st->status & GPGME_IMPORT_NEW) fputs (" new", stdout); if (st->status & GPGME_IMPORT_UID) fputs (" uid", stdout); if (st->status & GPGME_IMPORT_SIG) fputs (" sig", stdout); if (st->status & GPGME_IMPORT_SUBKEY) fputs (" subkey", stdout); if (st->status & GPGME_IMPORT_SECRET) fputs (" secret", stdout); putchar ('\n'); } printf ("key import summary:\n" " considered: %d\n" " no user id: %d\n" " imported: %d\n" " imported_rsa: %d\n" " unchanged: %d\n" " new user ids: %d\n" " new subkeys: %d\n" " new signatures: %d\n" " new revocations: %d\n" " secret read: %d\n" " secret imported: %d\n" " secret unchanged: %d\n" " skipped new keys: %d\n" " not imported: %d\n" " skipped v3 keys: %d\n", r->considered, r->no_user_id, r->imported, r->imported_rsa, r->unchanged, r->new_user_ids, r->new_sub_keys, r->new_signatures, r->new_revocations, r->secret_read, r->secret_imported, r->secret_unchanged, r->skipped_new_keys, r->not_imported, r->skipped_v3_keys); } - - -/* Read the next number in the version string STR and return it in - *NUMBER. Return a pointer to the tail of STR after parsing, or - *NULL if the version string was invalid. */ -static const char * -parse_version_number (const char *str, int *number) -{ -#define MAXVAL ((INT_MAX - 10) / 10) - int val = 0; - - /* Leading zeros are not allowed. */ - if (*str == '0' && isdigit(str[1])) - return NULL; - - while (isdigit (*str) && val <= MAXVAL) - { - val *= 10; - val += *(str++) - '0'; - } - *number = val; - return val > MAXVAL ? NULL : str; -} - - -/* Parse the version string STR in the format MAJOR.MINOR.MICRO (for - example, 9.3.2) and return the components in MAJOR, MINOR and MICRO - as integers. The function returns the tail of the string that - follows the version number. This might be the empty string if there - is nothing following the version number, or a patchlevel. The - function returns NULL if the version string is not valid. */ -static const char * -parse_version_string (const char *str, int *major, int *minor, int *micro) -{ - str = parse_version_number (str, major); - if (!str || *str != '.') - return NULL; - str++; - - str = parse_version_number (str, minor); - if (!str || *str != '.') - return NULL; - str++; - - str = parse_version_number (str, micro); - if (!str) - return NULL; - - /* A patchlevel might follow. */ - return str; -} - - -/* Return true if MY_VERSION is at least REQ_VERSION, and false - otherwise. */ -static int -compare_versions (const char *my_version, - const char *rq_version) -{ - int my_major, my_minor, my_micro; - int rq_major, rq_minor, rq_micro; - const char *my_plvl, *rq_plvl; - - if (!rq_version) - return 1; - if (!my_version) - return 0; - - my_plvl = parse_version_string (my_version, &my_major, &my_minor, &my_micro); - if (!my_plvl) - return 0; - - rq_plvl = parse_version_string (rq_version, &rq_major, &rq_minor, &rq_micro); - if (!rq_plvl) - return 0; - - if (my_major > rq_major - || (my_major == rq_major && my_minor > rq_minor) - || (my_major == rq_major && my_minor == rq_minor - && my_micro > rq_micro) - || (my_major == rq_major && my_minor == rq_minor - && my_micro == rq_micro && strcmp (my_plvl, rq_plvl) >= 0)) - return 1; - - return 0; -} - -/* Return true if we have the required gpg version. */ -static int -check_gpg_version (const char *req_version) -{ - gpgme_engine_info_t engine_info; - init_gpgme (GPGME_PROTOCOL_OpenPGP); - - fail_if_err (gpgme_get_engine_info (&engine_info)); - for (; engine_info; engine_info = engine_info->next) - if (engine_info->protocol == GPGME_PROTOCOL_OpenPGP) - break; - - test (engine_info); - - return compare_versions (engine_info->version, req_version); -} diff --git a/tests/json/t-json.c b/tests/json/t-json.c index 024b2553..77750ad0 100644 --- a/tests/json/t-json.c +++ b/tests/json/t-json.c @@ -1,393 +1,499 @@ /* t-json.c - Regression test. Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik Software engineering by Intevation GmbH This file is part of GPGME. GPGME is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. GPGME is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include "../gpg/t-support.h" #include "../../src/cJSON.h" /* Register tests here */ static const char*tests[] = { "t-config", "t-version", "t-keylist", "t-keylist-secret", "t-decrypt", "t-config-opt", "t-encrypt", "t-encrypt-sign", "t-sign", "t-verify", "t-decrypt-verify", "t-export", "t-createkey", "t-export-secret-info", "t-chunking", "t-sig-notations", /* For these two the order is important * as t-import imports the deleted key from t-delete */ "t-delete", "t-import", NULL }; static int verbose = 0; + +/* Read the next number in the version string STR and return it in + *NUMBER. Return a pointer to the tail of STR after parsing, or + *NULL if the version string was invalid. */ +static const char * +parse_version_number (const char *str, int *number) +{ +#define MAXVAL ((INT_MAX - 10) / 10) + int val = 0; + + /* Leading zeros are not allowed. */ + if (*str == '0' && isdigit(str[1])) + return NULL; + + while (isdigit (*str) && val <= MAXVAL) + { + val *= 10; + val += *(str++) - '0'; + } + *number = val; + return val > MAXVAL ? NULL : str; +} + + +/* Parse the version string STR in the format MAJOR.MINOR.MICRO (for + example, 9.3.2) and return the components in MAJOR, MINOR and MICRO + as integers. The function returns the tail of the string that + follows the version number. This might be the empty string if there + is nothing following the version number, or a patchlevel. The + function returns NULL if the version string is not valid. */ +static const char * +parse_version_string (const char *str, int *major, int *minor, int *micro) +{ + str = parse_version_number (str, major); + if (!str || *str != '.') + return NULL; + str++; + + str = parse_version_number (str, minor); + if (!str || *str != '.') + return NULL; + str++; + + str = parse_version_number (str, micro); + if (!str) + return NULL; + + /* A patchlevel might follow. */ + return str; +} + + +/* Return true if MY_VERSION is at least REQ_VERSION, and false + otherwise. */ +static int +compare_versions (const char *my_version, + const char *rq_version) +{ + int my_major, my_minor, my_micro; + int rq_major, rq_minor, rq_micro; + const char *my_plvl, *rq_plvl; + + if (!rq_version) + return 1; + if (!my_version) + return 0; + + my_plvl = parse_version_string (my_version, &my_major, &my_minor, &my_micro); + if (!my_plvl) + return 0; + + rq_plvl = parse_version_string (rq_version, &rq_major, &rq_minor, &rq_micro); + if (!rq_plvl) + return 0; + + if (my_major > rq_major + || (my_major == rq_major && my_minor > rq_minor) + || (my_major == rq_major && my_minor == rq_minor + && my_micro > rq_micro) + || (my_major == rq_major && my_minor == rq_minor + && my_micro == rq_micro && strcmp (my_plvl, rq_plvl) >= 0)) + return 1; + + return 0; +} + +/* Return true if we have the required gpg version. + + This should probably go into gpgrt or gpgme proper. +*/ +static int +check_gpg_version (const char *req_version) +{ + gpgme_engine_info_t engine_info; + init_gpgme (GPGME_PROTOCOL_OpenPGP); + + fail_if_err (gpgme_get_engine_info (&engine_info)); + for (; engine_info; engine_info = engine_info->next) + if (engine_info->protocol == GPGME_PROTOCOL_OpenPGP) + break; + + test (engine_info); + + return compare_versions (engine_info->version, req_version); +} + static char * get_file (const char *fname) { gpg_error_t err; gpgrt_stream_t fp; struct stat st; char *buf; size_t buflen; fp = gpgrt_fopen (fname, "r"); if (!fp) { err = gpg_error_from_syserror (); fprintf (stderr, "Error: can't open '%s': %s\n", fname, gpg_strerror (err)); return NULL; } if (fstat (gpgrt_fileno(fp), &st)) { err = gpg_error_from_syserror (); fprintf (stderr, "Error: can't stat '%s': %s\n", fname, gpg_strerror (err)); gpgrt_fclose (fp); return NULL; } buflen = st.st_size; buf = malloc (buflen+1); if (!buf) { fprintf (stderr, "Error: no mem\n"); gpgrt_fclose (fp); return NULL; } if (gpgrt_fread (buf, buflen, 1, fp) != 1) { err = gpg_error_from_syserror (); fprintf (stderr, "error reading '%s': %s\n", fname, gpg_strerror (err)); gpgrt_fclose (fp); free (buf); return NULL; } buf[buflen] = 0; gpgrt_fclose (fp); return buf; } /* Check that the element needle exists in hay. Returns 0 if the needle was found. */ int test_contains (cjson_t needle, cjson_t hay) { if (verbose == 2) fprintf (stderr, "%s: -------checking-------- " "\n%s\n -------against-------- \n%s\n", nonnull (needle->string), cJSON_Print (needle), cJSON_Print (hay)); /* Type check. This automatically checks bool vals and NULL */ if (needle->type != hay->type) { if (verbose) fprintf (stderr, "%s: type mismatch expected %i got %i\n", nonnull (needle->string), needle->type, hay->type); return 1; } /* First the simple types */ if (cjson_is_number (needle)) { if (needle->valueint != hay->valueint) { if (verbose) fprintf (stderr, "%s: value mismatch. Expected %i got %i\n", nonnull (needle->string), needle->valueint, hay->valueint); return 1; } } if (cjson_is_string (needle)) { if (strcmp (needle->valuestring, hay->valuestring) && /* Use * as a general don't care placeholder */ strcmp (needle->valuestring, "*")) { if (verbose) fprintf (stderr, "%s: string mismatch Expected '%s' got '%s'\n", needle->string, needle->valuestring, hay->valuestring); return 1; } } /* Now the complex types */ if (needle->child) { if (!hay->child) { fprintf (stderr, "Depth mismatch. Expected child for %s\n", nonnull (needle->string)); } if (test_contains (needle->child, hay->child)) { int found = 0; for (cjson_t hit = hay->child; hit; hit = hit->next) { found |= !test_contains (needle->child, hit); if (found) { break; } } if (!found) { return 1; } } } if (needle->prev) { return 0; } /* Walk elements of an array */ for (cjson_t it = needle->next; it; it = it->next) { int found = 0; if (!it->string && it->child) { /* Try out all other anonymous children on the same level */ cjson_t hit = hay; /* Return to the beginning */ while (hit->prev) { hit = hit->prev; } for (; hit && hit->child; hit = hit->next) { found |= !test_contains (it->child, hit->child); if (found) { break; } } if (!found) { return 1; } continue; } /* Try the children in the haystack */ for (cjson_t hit = hay; hit; hit = hit->next) { if (hit->string && it->string && !strcmp (hit->string, it->string)) { found = 1; if (test_contains (it, hit)) { return 1; } } } if (!found) { if (verbose) fprintf (stderr, "Failed to find '%s' in list\n", nonnull (it->string)); return 1; } } return 0; } int check_response (const char *response, const char *expected) { cjson_t hay; cjson_t needle; int rc; size_t erroff; hay = cJSON_Parse (response, &erroff); if (!hay) { fprintf (stderr, "Failed to parse json at %i:\n%s\n", (int) erroff, response); return 1; } needle = cJSON_Parse (expected, &erroff); if (!needle) { fprintf (stderr, "Failed to parse json at %i:\n%s\n", (int) erroff, expected); cJSON_Delete (hay); return 1; } rc = test_contains (needle, hay); cJSON_Delete (needle); cJSON_Delete (hay); return rc; } int run_test (const char *test, const char *gpgme_json) { gpgme_ctx_t ctx; gpgme_data_t json_stdin = NULL; gpgme_data_t json_stdout = NULL; gpgme_data_t json_stderr = NULL; char *test_in; char *test_out; const char *argv[3]; char *response; char *expected = NULL; size_t response_size; int rc = 0; const char *top_srcdir = getenv ("top_srcdir"); if (!top_srcdir) { fprintf (stderr, "Error top_srcdir environment variable not set\n"); exit(1); } gpgrt_asprintf (&test_in, "%s/tests/json/%s.in.json", top_srcdir, test); gpgrt_asprintf (&test_out, "%s/tests/json/%s.out.json", top_srcdir, test); printf ("Running %s...\n", test); fail_if_err (gpgme_new (&ctx)); gpgme_set_protocol (ctx, GPGME_PROTOCOL_SPAWN); fail_if_err (gpgme_data_new_from_file (&json_stdin, test_in, 1)); fail_if_err (gpgme_data_new (&json_stdout)); fail_if_err (gpgme_data_new (&json_stderr)); argv[0] = gpgme_json; argv[1] = "-s"; argv[2] = NULL; fail_if_err (gpgme_op_spawn (ctx, gpgme_json, argv, json_stdin, json_stdout, json_stderr, 0)); response = gpgme_data_release_and_get_mem (json_stdout, &response_size); if (response_size) { expected = get_file (test_out); test (expected); rc = check_response (response, expected); } else { rc = 1; } if (!rc) { printf (" success\n"); gpgme_data_release (json_stderr); } else { char *buf; size_t size; buf = gpgme_data_release_and_get_mem (json_stderr, &size); printf (" failed%s\n", response_size ? "" : ", no response from gpgme-json"); if (size) { printf ("gpgme-json stderr:\n%.*s\n", (int)size, buf); } free (buf); } free (test_out); free (test_in); free (response); free (expected); gpgme_data_release (json_stdin); gpgme_release (ctx); return rc; } int main (int argc, char *argv[]) { const char *gpgme_json = getenv ("gpgme_json"); int last_argc = -1; if (argc) { argc--; argv++; } while (argc && last_argc != argc ) { last_argc = argc; if (!strcmp (*argv, "--verbose")) { verbose++; argc--; argv++; } } if (!check_gpg_version ("2.2.0")) { /* Lets not break too much or have to test all combinations */ printf ("Testsuite skipped. Minimum GnuPG version (2.2.0) " "not found.\n"); exit(0); } init_gpgme (GPGME_PROTOCOL_SPAWN); for (const char **test = tests; *test; test++) { if (run_test (*test, gpgme_json)) { exit(1); } } return 0; }