1 #include <stdio.h>
2 #include "duktape.h"
3 #include "duk_cbor.h"
4 
5 #define FMT_CBOR 1
6 #define FMT_JSON 2
7 #define FMT_JX 3
8 #define FMT_JS 4
9 
10 static int read_format = 0;
11 static int write_format = 0;
12 static int write_indent = 0;
13 
push_stdin(duk_context * ctx,int to_string)14 static void push_stdin(duk_context *ctx, int to_string) {
15 	unsigned char *buf;
16 	size_t off;
17 	size_t len;
18 	size_t got;
19 
20 	off = 0;
21 	len = 256;
22 	buf = (unsigned char *) duk_push_dynamic_buffer(ctx, len);
23 
24 	for (;;) {
25 #if 0
26 		fprintf(stderr, "reading %ld of input\n", (long) (len - off));
27 #endif
28 		got = fread(buf + off, 1, len - off, stdin);
29 		if (got == 0U) {
30 			break;
31 		}
32 		off += got;
33 		if (len - off < 256U) {
34 			size_t newlen = len * 2U;
35 			buf = (unsigned char *) duk_resize_buffer(ctx, -1, newlen);
36 			len = newlen;
37 		}
38 	}
39 	buf = (unsigned char *) duk_resize_buffer(ctx, -1, off);
40 
41 	if (to_string) {
42 		duk_push_lstring(ctx, (const char *) buf, off);
43 		duk_remove(ctx, -2);
44 	}
45 }
46 
usage_and_exit(void)47 static void usage_and_exit(void) {
48 	fprintf(stderr, "Usage: jsoncbor -r cbor|json|jx|js -w cbor|json|jx [--indent N]\n"
49 	                "       jsoncbor -e               # shorthand for -r json -w cbor\n"
50 	                "       jsoncbor -d [--indent N]  # shorthand for -r cbor -w json [--indent N]\n"
51 	                "\n"
52 	                "       Input is read from stdin, output is written to stdout.\n"
53 	                "       'jx' is a Duktape custom JSON extension.\n"
54 	                "       'js' means evaluate input as an ECMAScript expression.\n");
55 	exit(1);
56 }
57 
transcode_helper(duk_context * ctx,void * udata)58 static duk_ret_t transcode_helper(duk_context *ctx, void *udata) {
59 	const unsigned char *buf;
60 	size_t len;
61 	duk_idx_t top;
62 
63 	(void) udata;
64 
65 	/* For Duktape->JSON conversion map all typed arrays into base-64.
66 	 * This is generally more useful than the default behavior.  However,
67 	 * the base-64 value doesn't have any kind of 'tag' to allow it to be
68 	 * parsed back into binary automatically.  Right now the CBOR parser
69 	 * creates plain fixed buffers from incoming binary strings.
70 	 */
71 	duk_eval_string_noresult(ctx,
72 		"(function () {\n"
73 		"    Object.getPrototypeOf(new Uint8Array(0)).toJSON = function () {\n"
74 		"        return Duktape.enc('base64', this);\n"
75 		"    };\n"
76 		"}())\n");
77 
78 	top = duk_get_top(ctx);
79 
80 	if (read_format == FMT_CBOR) {
81 		push_stdin(ctx, 0 /*to_string*/);
82 		duk_cbor_decode(ctx, -1, 0);
83 	} else if (read_format == FMT_JSON) {
84 		push_stdin(ctx, 1 /*to_string*/);
85 		duk_json_decode(ctx, -1);
86 	} else if (read_format == FMT_JX) {
87 		duk_eval_string(ctx, "(function(v){return Duktape.dec('jx',v)})");
88 		push_stdin(ctx, 1 /*to_string*/);
89 		duk_call(ctx, 1);
90 	} else if (read_format == FMT_JS) {
91 		push_stdin(ctx, 1 /*to_string*/);
92 		duk_eval(ctx);
93 	} else {
94 		(void) duk_type_error(ctx, "invalid read format");
95 	}
96 
97 	if (duk_get_top(ctx) != top + 1) {
98 		fprintf(stderr, "top invalid after decoding: %d vs. %d\n", duk_get_top(ctx), top + 1);
99 		exit(1);
100 	}
101 
102 	if (write_format == FMT_CBOR) {
103 		duk_cbor_encode(ctx, -1, 0);
104 	} else if (write_format == FMT_JSON) {
105 		duk_eval_string(ctx, "(function(v,i){return JSON.stringify(v,null,i)})");
106 		duk_insert(ctx, -2);
107 		duk_push_int(ctx, write_indent);
108 		duk_call(ctx, 2);
109 	} else if (write_format == FMT_JX) {
110 		duk_eval_string(ctx, "(function(v,i){return Duktape.enc('jx',v,null,i)})");
111 		duk_insert(ctx, -2);
112 		duk_push_int(ctx, write_indent);
113 		duk_call(ctx, 2);
114 	} else {
115 		(void) duk_type_error(ctx, "invalid write format");
116 	}
117 
118 	if (duk_is_string(ctx, -1)) {
119 		buf = (const unsigned char *) duk_require_lstring(ctx, -1, &len);
120 		fwrite((const void *) buf, 1, len, stdout);
121 		fprintf(stdout, "\n");
122 	} else {
123 		buf = (const unsigned char *) duk_require_buffer_data(ctx, -1, &len);
124 		fwrite((const void *) buf, 1, len, stdout);
125 	}
126 
127 	if (duk_get_top(ctx) != top + 1) {
128 		fprintf(stderr, "top invalid after encoding: %d vs. %d\n", duk_get_top(ctx), top + 1);
129 		exit(1);
130 	}
131 
132 	return 0;
133 }
134 
parse_format(const char * s)135 static int parse_format(const char *s) {
136 	if (strcmp(s, "cbor") == 0) {
137 		return FMT_CBOR;
138 	} else if (strcmp(s, "json") == 0) {
139 		return FMT_JSON;
140 	} else if (strcmp(s, "jx") == 0) {
141 		return FMT_JX;
142 	} else if (strcmp(s, "js") == 0) {
143 		return FMT_JS;
144 	} else {
145 		return 0;
146 	}
147 }
148 
main(int argc,char * argv[])149 int main(int argc, char *argv[]) {
150 	duk_context *ctx;
151 	duk_int_t rc;
152 	int exitcode = 0;
153 	int i;
154 
155 	for (i = 1; i < argc; i++) {
156 		if (strcmp(argv[i], "-e") == 0) {
157 			read_format = FMT_JSON;
158 			write_format = FMT_CBOR;
159 		} else if (strcmp(argv[i], "-d") == 0) {
160 			read_format = FMT_CBOR;
161 			write_format = FMT_JSON;
162 		} else if (strcmp(argv[i], "-r") == 0) {
163 			if (i + 1 >= argc) {
164 				goto usage;
165 			}
166 			read_format = parse_format(argv[i + 1]);
167 			i++;
168 		} else if (strcmp(argv[i], "-w") == 0) {
169 			if (i + 1 >= argc) {
170 				goto usage;
171 			}
172 			write_format = parse_format(argv[i + 1]);
173 			i++;
174 		} else if (strcmp(argv[i], "--indent") == 0) {
175 			if (i + 1 >= argc) {
176 				goto usage;
177 			}
178 			write_indent = atoi(argv[i + 1]);
179 			i++;
180 		} else {
181 			goto usage;
182 		}
183 	}
184 
185 	if (read_format == 0 || write_format == 0) {
186 		goto usage;
187 	}
188 
189 	ctx = duk_create_heap_default();
190 	if (!ctx) {
191 		return 1;
192 	}
193 
194 	rc = duk_safe_call(ctx, transcode_helper, NULL, 0, 1);
195 	if (rc != 0) {
196 		fprintf(stderr, "%s\n", duk_safe_to_string(ctx, -1));
197 		exitcode = 1;
198 	}
199 	/* duk_pop(ctx): unnecessary */
200 
201 	duk_destroy_heap(ctx);
202 
203 	return exitcode;
204 
205  usage:
206 	usage_and_exit();
207 }
208