1/*
2 * Copyright (c) 2009-2014 Petri Lehtinen <petri@digip.org>
3 *
4 * Jansson is free software; you can redistribute it and/or modify
5 * it under the terms of the MIT license. See LICENSE for details.
6 */
7
8#ifdef HAVE_CONFIG_H
9#include <jansson_private_config.h>
10#endif
11
12#include <stdio.h>
13#include <stdlib.h>
14#include <string.h>
15#include <ctype.h>
16#include <jansson.h>
17
18#ifdef HAVE_LOCALE_H
19#include <locale.h>
20 #endif
21
22#if _WIN32
23#include <io.h>  /* for _setmode() */
24#include <fcntl.h>  /* for _O_BINARY */
25
26static const char dir_sep = '\\';
27#else
28static const char dir_sep = '/';
29#endif
30
31
32struct config {
33    int indent;
34    int compact;
35    int preserve_order;
36    int ensure_ascii;
37    int sort_keys;
38    int strip;
39    int use_env;
40    int have_hashseed;
41    int hashseed;
42    int precision;
43} conf;
44
45#define l_isspace(c) ((c) == ' ' || (c) == '\n' || (c) == '\r' || (c) == '\t')
46
47/* Return a pointer to the first non-whitespace character of str.
48   Modifies str so that all trailing whitespace characters are
49   replaced by '\0'. */
50static const char *strip(char *str)
51{
52    size_t length;
53    char *result = str;
54    while (*result && l_isspace(*result))
55        result++;
56
57    length = strlen(result);
58    if (length == 0)
59        return result;
60
61    while (l_isspace(result[length - 1]))
62        result[--length] = '\0';
63
64    return result;
65}
66
67
68static char *loadfile(FILE *file)
69{
70    long fsize, ret;
71    char *buf;
72
73    fseek(file, 0, SEEK_END);
74    fsize = ftell(file);
75    fseek(file, 0, SEEK_SET);
76
77    buf = malloc(fsize+1);
78    ret = fread(buf, 1, fsize, file);
79    if (ret != fsize)
80        exit(1);
81    buf[fsize] = '\0';
82
83    return buf;
84}
85
86
87static void read_conf(FILE *conffile)
88{
89    char *buffer, *line, *val;
90
91    buffer = loadfile(conffile);
92    for (line = strtok(buffer, "\r\n"); line; line = strtok(NULL, "\r\n")) {
93        if (!strncmp(line, "export ", 7))
94            continue;
95        val = strchr(line, '=');
96        if (!val) {
97            printf("invalid configuration line\n");
98            break;
99        }
100        *val++ = '\0';
101
102        if (!strcmp(line, "JSON_INDENT"))
103            conf.indent = atoi(val);
104        if (!strcmp(line, "JSON_COMPACT"))
105            conf.compact = atoi(val);
106        if (!strcmp(line, "JSON_ENSURE_ASCII"))
107            conf.ensure_ascii = atoi(val);
108        if (!strcmp(line, "JSON_PRESERVE_ORDER"))
109            conf.preserve_order = atoi(val);
110        if (!strcmp(line, "JSON_SORT_KEYS"))
111            conf.sort_keys = atoi(val);
112        if (!strcmp(line, "JSON_REAL_PRECISION"))
113            conf.precision = atoi(val);
114        if (!strcmp(line, "STRIP"))
115            conf.strip = atoi(val);
116        if (!strcmp(line, "HASHSEED")) {
117            conf.have_hashseed = 1;
118            conf.hashseed = atoi(val);
119        } else {
120            conf.have_hashseed = 0;
121        }
122    }
123
124    free(buffer);
125}
126
127
128static int cmpfile(const char *str, const char *path, const char *fname)
129{
130    char filename[1024], *buffer;
131    int ret;
132    FILE *file;
133
134    sprintf(filename, "%s%c%s", path, dir_sep, fname);
135    file = fopen(filename, "rb");
136    if (!file) {
137        if (conf.strip)
138            strcat(filename, ".strip");
139        else
140            strcat(filename, ".normal");
141        file = fopen(filename, "rb");
142    }
143    if (!file) {
144        printf("Error: test result file could not be opened.\n");
145        exit(1);
146    }
147
148    buffer = loadfile(file);
149    if (strcmp(buffer, str) != 0)
150        ret = 1;
151    else
152        ret = 0;
153    free(buffer);
154    fclose(file);
155
156    return ret;
157}
158
159int use_conf(char *test_path)
160{
161    int ret;
162    size_t flags = 0;
163    char filename[1024], errstr[1024];
164    char *buffer;
165    FILE *infile, *conffile;
166    json_t *json;
167    json_error_t error;
168
169    sprintf(filename, "%s%cinput", test_path, dir_sep);
170    if (!(infile = fopen(filename, "rb"))) {
171        fprintf(stderr, "Could not open \"%s\"\n", filename);
172        return 2;
173    }
174
175    sprintf(filename, "%s%cenv", test_path, dir_sep);
176    conffile = fopen(filename, "rb");
177    if (conffile) {
178        read_conf(conffile);
179        fclose(conffile);
180    }
181
182    if (conf.indent < 0 || conf.indent > 31) {
183        fprintf(stderr, "invalid value for JSON_INDENT: %d\n", conf.indent);
184        fclose(infile);
185        return 2;
186    }
187    if (conf.indent)
188        flags |= JSON_INDENT(conf.indent);
189
190    if (conf.compact)
191        flags |= JSON_COMPACT;
192
193    if (conf.ensure_ascii)
194        flags |= JSON_ENSURE_ASCII;
195
196    if (conf.preserve_order)
197        flags |= JSON_PRESERVE_ORDER;
198
199    if (conf.sort_keys)
200        flags |= JSON_SORT_KEYS;
201
202    if (conf.precision < 0 || conf.precision > 31) {
203        fprintf(stderr, "invalid value for JSON_REAL_PRECISION: %d\n",
204                conf.precision);
205        fclose(infile);
206        return 2;
207    }
208    if (conf.precision)
209        flags |= JSON_REAL_PRECISION(conf.precision);
210
211    if (conf.have_hashseed)
212        json_object_seed(conf.hashseed);
213
214    if (conf.strip) {
215        /* Load to memory, strip leading and trailing whitespace */
216        buffer = loadfile(infile);
217        json = json_loads(strip(buffer), 0, &error);
218        free(buffer);
219    }
220    else
221        json = json_loadf(infile, 0, &error);
222
223    fclose(infile);
224
225    if (!json) {
226        sprintf(errstr, "%d %d %d\n%s\n",
227                error.line, error.column, error.position,
228                error.text);
229
230        ret = cmpfile(errstr, test_path, "error");
231        return ret;
232    }
233
234    buffer = json_dumps(json, flags);
235    ret = cmpfile(buffer, test_path, "output");
236    free(buffer);
237    json_decref(json);
238
239    return ret;
240}
241
242static int getenv_int(const char *name)
243{
244    char *value, *end;
245    long result;
246
247    value = getenv(name);
248    if(!value)
249        return 0;
250
251    result = strtol(value, &end, 10);
252    if(*end != '\0')
253        return 0;
254
255    return (int)result;
256}
257
258int use_env()
259{
260    int indent, precision;
261    size_t flags = 0;
262    json_t *json;
263    json_error_t error;
264
265    #ifdef _WIN32
266    /* On Windows, set stdout and stderr to binary mode to avoid
267       outputting DOS line terminators */
268    _setmode(_fileno(stdout), _O_BINARY);
269    _setmode(_fileno(stderr), _O_BINARY);
270    #endif
271
272    indent = getenv_int("JSON_INDENT");
273    if(indent < 0 || indent > 31) {
274        fprintf(stderr, "invalid value for JSON_INDENT: %d\n", indent);
275        return 2;
276    }
277    if(indent > 0)
278        flags |= JSON_INDENT(indent);
279
280    if(getenv_int("JSON_COMPACT") > 0)
281        flags |= JSON_COMPACT;
282
283    if(getenv_int("JSON_ENSURE_ASCII"))
284        flags |= JSON_ENSURE_ASCII;
285
286    if(getenv_int("JSON_PRESERVE_ORDER"))
287        flags |= JSON_PRESERVE_ORDER;
288
289    if(getenv_int("JSON_SORT_KEYS"))
290        flags |= JSON_SORT_KEYS;
291
292    precision = getenv_int("JSON_REAL_PRECISION");
293    if(precision < 0 || precision > 31) {
294        fprintf(stderr, "invalid value for JSON_REAL_PRECISION: %d\n",
295                precision);
296        return 2;
297    }
298
299    if(getenv("HASHSEED"))
300        json_object_seed(getenv_int("HASHSEED"));
301
302    if(precision > 0)
303        flags |= JSON_REAL_PRECISION(precision);
304
305    if(getenv_int("STRIP")) {
306        /* Load to memory, strip leading and trailing whitespace */
307        size_t size = 0, used = 0;
308        char *buffer = NULL, *buf_ck = NULL;
309
310        while(1) {
311            size_t count;
312
313            size = (size == 0 ? 128 : size * 2);
314            buf_ck = realloc(buffer, size);
315            if(!buf_ck) {
316                fprintf(stderr, "Unable to allocate %d bytes\n", (int)size);
317                free(buffer);
318                return 1;
319            }
320            buffer = buf_ck;
321
322            count = fread(buffer + used, 1, size - used, stdin);
323            if(count < size - used) {
324                buffer[used + count] = '\0';
325                break;
326            }
327            used += count;
328        }
329
330        json = json_loads(strip(buffer), 0, &error);
331        free(buffer);
332    }
333    else
334        json = json_loadf(stdin, 0, &error);
335
336    if(!json) {
337        fprintf(stderr, "%d %d %d\n%s\n",
338            error.line, error.column,
339            error.position, error.text);
340        return 1;
341    }
342
343    json_dumpf(json, stdout, flags);
344    json_decref(json);
345
346    return 0;
347}
348
349int main(int argc, char *argv[])
350{
351    int i;
352    char *test_path = NULL;
353
354    #ifdef HAVE_SETLOCALE
355    setlocale(LC_ALL, "");
356    #endif
357
358    if (argc < 2) {
359        goto usage;
360    }
361
362    for (i = 1; i < argc; i++) {
363        if (!strcmp(argv[i], "--strip"))
364            conf.strip = 1;
365        else if (!strcmp(argv[i], "--env"))
366            conf.use_env = 1;
367        else
368            test_path = argv[i];
369    }
370
371    if (conf.use_env)
372        return use_env();
373    else
374    {
375        if (!test_path)
376            goto usage;
377
378        return use_conf(test_path);
379    }
380
381usage:
382    fprintf(stderr, "argc =%d\n", argc);
383    fprintf(stderr, "usage: %s [--strip] [--env] test_dir\n", argv[0]);
384    return 2;
385}
386