1 /*
2  * Copyright (c) 2009-2016 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  * License: MIT
9  */
10 module jansson_d.dump;
11 
12 
13 package:
14 
15 private static import core.stdc.stdio;
16 private static import core.stdc.stdlib;
17 private static import core.stdc.string;
18 private static import core.sys.posix.sys.types;
19 private static import core.sys.posix.unistd;
20 private static import jansson_d.hashtable;
21 private static import jansson_d.jansson;
22 private static import jansson_d.jansson_private;
23 private static import jansson_d.strbuffer;
24 private static import jansson_d.utf;
25 private static import jansson_d.value;
26 
27 enum MAX_INTEGER_STR_LENGTH = 100;
28 enum MAX_REAL_STR_LENGTH = 100;
29 
30 private template FLAGS_TO_INDENT(string f)
31 {
32 	enum FLAGS_TO_INDENT = "(" ~ f ~ " &0x1F)";
33 }
34 
35 private template FLAGS_TO_PRECISION(string f)
36 {
37 	enum FLAGS_TO_PRECISION = "((" ~ f ~ " >> 11) & 0x1F)";
38 }
39 
40 private struct buffer_
41 {
42 	const size_t size;
43 	size_t used;
44 	char* data;
45 }
46 
47 extern (C)
48 nothrow @trusted @nogc
49 private int dump_to_strbuffer(scope const char* buffer, size_t size, scope void* data)
50 
51 	do
52 	{
53 		return jansson_d.strbuffer.strbuffer_append_bytes(cast(jansson_d.strbuffer.strbuffer_t*)(data), buffer, size);
54 	}
55 
56 extern (C)
57 pure nothrow @trusted @nogc @live
58 private int dump_to_buffer(scope const char* buffer, size_t size, scope void* data)
59 
60 	in
61 	{
62 		assert(buffer != null);
63 		assert(data != null);
64 	}
65 
66 	do
67 	{
68 		.buffer_* buf = cast(.buffer_*)(data);
69 
70 		if ((buf.used + size) <= buf.size) {
71 			core.stdc..string.memcpy(&buf.data[buf.used], buffer, size);
72 		}
73 
74 		buf.used += size;
75 
76 		return 0;
77 	}
78 
79 extern (C)
80 nothrow @nogc @live
81 private int dump_to_file(scope const char* buffer, size_t size, void* data)
82 
83 	in
84 	{
85 		assert(buffer != null);
86 		assert(data != null);
87 	}
88 
89 	do
90 	{
91 		core.stdc.stdio.FILE* dest = cast(core.stdc.stdio.FILE*)(data);
92 
93 		if (core.stdc.stdio.fwrite(buffer, size, 1, dest) != 1) {
94 			return -1;
95 		}
96 
97 		return 0;
98 	}
99 
100 extern (C)
101 nothrow @nogc @live
102 private int dump_to_fd(scope const char* buffer, size_t size, scope void* data)
103 
104 	in
105 	{
106 		assert(buffer != null);
107 		assert(data != null);
108 	}
109 
110 	do
111 	{
112 		static if (__traits(compiles, core.sys.posix.unistd.write)) {
113 			int* dest = cast(int*)(data);
114 
115 			if (core.sys.posix.unistd.write(*dest, buffer, size) == cast(core.sys.posix.sys.types.ssize_t)(size)) {
116 				return 0;
117 			}
118 		}
119 
120 		return -1;
121 	}
122 
123 /* 32 spaces (the maximum indentation size) */
124 private static immutable char[] whitespace = "                                \0";
125 
126 nothrow @nogc @live
127 private int dump_indent(size_t flags, int depth, int space, jansson_d.jansson.json_dump_callback_t dump, void* data)
128 
129 	in
130 	{
131 		assert(dump != null);
132 	}
133 
134 	do
135 	{
136 		if (mixin (.FLAGS_TO_INDENT!("flags")) > 0) {
137 			uint ws_count = mixin (.FLAGS_TO_INDENT!("flags"));
138 			uint n_spaces = depth * ws_count;
139 
140 			if (dump("\n", 1, data)) {
141 				return -1;
142 			}
143 
144 			while (n_spaces > 0) {
145 				int cur_n = (n_spaces < (.whitespace.length - 1)) ? (n_spaces) : (.whitespace.length - 1);
146 
147 				if (dump(&(.whitespace[0]), cur_n, data)) {
148 					return -1;
149 				}
150 
151 				n_spaces -= cur_n;
152 			}
153 		} else if ((space) && (!(flags & jansson_d.jansson.JSON_COMPACT))) {
154 			return dump(" ", 1, data);
155 		}
156 
157 		return 0;
158 	}
159 
160 nothrow @nogc @live
161 private int dump_string(scope const char* str, size_t len, jansson_d.jansson.json_dump_callback_t dump, void* data, size_t flags)
162 
163 	in
164 	{
165 		assert(str != null);
166 		assert(dump != null);
167 	}
168 
169 	do
170 	{
171 		if (dump("\"", 1, data)) {
172 			return -1;
173 		}
174 
175 		const (char)* str_temp = str;
176 		const (char)* pos = str_temp;
177 		const (char)* end = str_temp;
178 		const (char)* lim = str_temp + len;
179 		int codepoint = 0;
180 
181 		while (true) {
182 			while (end < lim) {
183 				end = jansson_d.utf.utf8_iterate(pos, lim - pos, &codepoint);
184 
185 				if (end == null) {
186 					return -1;
187 				}
188 
189 				/* mandatory escape or control char */
190 				if ((codepoint == '\\') || (codepoint == '"') || (codepoint < 0x20)) {
191 					break;
192 				}
193 
194 				/* slash */
195 				if ((flags & jansson_d.jansson.JSON_ESCAPE_SLASH) && (codepoint == '/')) {
196 					break;
197 				}
198 
199 				/* non-ASCII */
200 				if ((flags & jansson_d.jansson.JSON_ENSURE_ASCII) && (codepoint > 0x7F)) {
201 					break;
202 				}
203 
204 				pos = end;
205 			}
206 
207 			if (pos != str_temp) {
208 				if (dump(str_temp, pos - str_temp, data)) {
209 					return -1;
210 				}
211 			}
212 
213 			if (end == pos) {
214 				break;
215 			}
216 
217 			/* handle \, /, ", and control codes */
218 			int length_ = 2;
219 
220 			const (char)* text = void;
221 
222 			switch (codepoint) {
223 				case '\\':
224 					text = "\\\\";
225 
226 					break;
227 
228 				case '\"':
229 					text = "\\\"";
230 
231 					break;
232 
233 				case '\b':
234 					text = "\\b";
235 
236 					break;
237 
238 				case '\f':
239 					text = "\\f";
240 
241 					break;
242 
243 				case '\n':
244 					text = "\\n";
245 
246 					break;
247 
248 				case '\r':
249 					text = "\\r";
250 
251 					break;
252 
253 				case '\t':
254 					text = "\\t";
255 
256 					break;
257 
258 				case '/':
259 					text = "\\/";
260 
261 					break;
262 
263 				default:
264 					char[13] seq = void;
265 
266 					/* codepoint is in BMP */
267 					if (codepoint < 0x010000) {
268 						jansson_d.jansson_private.snprintf(&(seq[0]), seq.length, "\\u%04X", cast(uint)(codepoint));
269 						length_ = 6;
270 					} else {
271 						/* not in BMP . construct a UTF-16 surrogate pair */
272 
273 						codepoint -= 0x010000;
274 						int first = 0xD800 | ((codepoint & 0x0FFC00) >> 10);
275 						int last = 0xDC00 | (codepoint & 0x0003FF);
276 
277 						jansson_d.jansson_private.snprintf(&(seq[0]), seq.length, "\\u%04X\\u%04X", cast(uint)(first), cast(uint)(last));
278 						length_ = 12;
279 					}
280 
281 					text = &(seq[0]);
282 
283 					break;
284 			}
285 
286 			if (dump(text, length_, data)) {
287 				return -1;
288 			}
289 
290 			pos = end;
291 			str_temp = end;
292 		}
293 
294 		return dump("\"", 1, data);
295 	}
296 
297 private struct key_len_
298 {
299 	const (char)* key;
300 	int len;
301 }
302 
303 extern (C)
304 pure nothrow @trusted @nogc @live
305 private int compare_keys(scope const void* key1, scope const void* key2)
306 
307 	in
308 	{
309 		assert(key1 != null);
310 		assert(key2 != null);
311 	}
312 
313 	do
314 	{
315 		const .key_len_* k1 = cast(const .key_len_*)(key1);
316 		const .key_len_* k2 = cast(const .key_len_*)(key2);
317 		const size_t min_size = (k1.len < k2.len) ? (k1.len) : (k2.len);
318 		int res = core.stdc..string.memcmp(k1.key, k2.key, min_size);
319 
320 		if (res != 0) {
321 			return res;
322 		}
323 
324 		return k1.len - k2.len;
325 	}
326 
327 nothrow @trusted @nogc
328 private int do_dump(scope const jansson_d.jansson.json_t* json, size_t flags, int depth, scope jansson_d.hashtable.hashtable_t* parents, jansson_d.jansson.json_dump_callback_t dump, void* data)
329 
330 	in
331 	{
332 		assert(dump != null);
333 	}
334 
335 	do
336 	{
337 		int embed = flags & jansson_d.jansson.JSON_EMBED;
338 
339 		flags &= ~jansson_d.jansson.JSON_EMBED;
340 
341 		if (json == null) {
342 			return -1;
343 		}
344 
345 		switch (mixin (jansson_d.jansson.json_typeof!("json"))) {
346 			case jansson_d.jansson.json_type.JSON_NULL:
347 				return dump("null", 4, data);
348 
349 			case jansson_d.jansson.json_type.JSON_TRUE:
350 				return dump("true", 4, data);
351 
352 			case jansson_d.jansson.json_type.JSON_FALSE:
353 				return dump("false", 5, data);
354 
355 			case jansson_d.jansson.json_type.JSON_INTEGER:
356 				char[.MAX_INTEGER_STR_LENGTH] buffer = void;
357 				int size = jansson_d.jansson_private.snprintf(&(buffer[0]), buffer.length, "%" ~ jansson_d.jansson.JSON_INTEGER_FORMAT, jansson_d.value.json_integer_value(json));
358 
359 				if ((size < 0) || (size >= buffer.length)) {
360 					return -1;
361 				}
362 
363 				return dump(&(buffer[0]), size, data);
364 
365 			case jansson_d.jansson.json_type.JSON_REAL:
366 				char[.MAX_REAL_STR_LENGTH] buffer = void;
367 				double value = jansson_d.value.json_real_value(json);
368 				int size = jansson_d.jansson_private.jsonp_dtostr(&(buffer[0]), buffer.length, value, mixin (.FLAGS_TO_PRECISION!("flags")));
369 
370 				if (size < 0) {
371 					return -1;
372 				}
373 
374 				return dump(&(buffer[0]), size, data);
375 
376 			case jansson_d.jansson.json_type.JSON_STRING:
377 				return .dump_string(jansson_d.value.json_string_value(json), jansson_d.value.json_string_length(json), dump, data, flags);
378 
379 			case jansson_d.jansson.json_type.JSON_ARRAY:
380 				/*
381 				 * Space for "0x", double the sizeof a pointer for the hex and a
382 				 * terminator.
383 				 */
384 				char[2 + (json.sizeof * 2) + 1] key = void;
385 				size_t key_len = void;
386 
387 				/* detect circular references */
388 				if (jansson_d.jansson_private.jsonp_loop_check(parents, json, &(key[0]), key.length, &key_len)) {
389 					return -1;
390 				}
391 
392 				size_t n = jansson_d.value.json_array_size(json);
393 
394 				if ((!embed) && (dump("[", 1, data))) {
395 					return -1;
396 				}
397 
398 				if (n == 0) {
399 					jansson_d.hashtable.hashtable_del(parents, &(key[0]), key_len);
400 
401 					return (embed) ? (0) : (dump("]", 1, data));
402 				}
403 
404 				if (.dump_indent(flags, depth + 1, 0, dump, data)) {
405 					return -1;
406 				}
407 
408 				for (size_t i = 0; i < n; ++i) {
409 					if (.do_dump(jansson_d.value.json_array_get(json, i), flags, depth + 1, parents, dump, data)) {
410 						return -1;
411 					}
412 
413 					if (i < (n - 1)) {
414 						if ((dump(",", 1, data)) || (.dump_indent(flags, depth + 1, 1, dump, data))) {
415 							return -1;
416 						}
417 					} else {
418 						if (.dump_indent(flags, depth, 0, dump, data)) {
419 							return -1;
420 						}
421 					}
422 				}
423 
424 				jansson_d.hashtable.hashtable_del(parents, &(key[0]), key_len);
425 
426 				return (embed) ? (0) : (dump("]", 1, data));
427 
428 			case jansson_d.jansson.json_type.JSON_OBJECT:
429 				const (char)* separator = void;
430 				int separator_length = void;
431 
432 				if (flags & jansson_d.jansson.JSON_COMPACT) {
433 					separator = ":";
434 					separator_length = 1;
435 				} else {
436 					separator = ": ";
437 					separator_length = 2;
438 				}
439 
440 				char[jansson_d.jansson_private.LOOP_KEY_LEN] loop_key = void;
441 				size_t loop_key_len = void;
442 
443 				/* detect circular references */
444 				if (jansson_d.jansson_private.jsonp_loop_check(parents, json, &(loop_key[0]), loop_key.length, &loop_key_len)) {
445 					return -1;
446 				}
447 
448 				void* iter = jansson_d.value.json_object_iter(cast(jansson_d.jansson.json_t*)(json));
449 
450 				if ((!embed) && (dump("{", 1, data))) {
451 					return -1;
452 				}
453 
454 				if (iter == null) {
455 					jansson_d.hashtable.hashtable_del(parents, &(loop_key[0]), loop_key_len);
456 
457 					return (embed) ? (0) : (dump("}", 1, data));
458 				}
459 
460 				if (.dump_indent(flags, depth + 1, 0, dump, data)) {
461 					return -1;
462 				}
463 
464 				if (flags & jansson_d.jansson.JSON_SORT_KEYS) {
465 					size_t size = jansson_d.value.json_object_size(json);
466 					.key_len_* keys = cast(.key_len_*)(jansson_d.jansson_private.jsonp_malloc(size * .key_len_.sizeof));
467 
468 					if (keys == null) {
469 						return -1;
470 					}
471 
472 					size_t i = 0;
473 
474 					while (iter != null) {
475 						.key_len_* keylen = &keys[i];
476 
477 						keylen.key = jansson_d.value.json_object_iter_key(iter);
478 						keylen.len = cast(int)(jansson_d.value.json_object_iter_key_len(iter));
479 
480 						iter = jansson_d.value.json_object_iter_next(cast(jansson_d.jansson.json_t*)(json), iter);
481 						i++;
482 					}
483 
484 					assert(i == size);
485 
486 					core.stdc.stdlib.qsort(keys, size, .key_len_.sizeof, &.compare_keys);
487 
488 					for (i = 0; i < size; i++) {
489 						const .key_len_* key = &keys[i];
490 						jansson_d.jansson.json_t* value = jansson_d.value.json_object_getn(json, key.key, key.len);
491 						assert(value);
492 
493 						.dump_string(key.key, key.len, dump, data, flags);
494 
495 						if ((dump(separator, separator_length, data)) || (.do_dump(value, flags, depth + 1, parents, dump, data))) {
496 							jansson_d.jansson_private.jsonp_free(keys);
497 
498 							return -1;
499 						}
500 
501 						if (i < (size - 1)) {
502 							if ((dump(",", 1, data)) || (.dump_indent(flags, depth + 1, 1, dump, data))) {
503 								jansson_d.jansson_private.jsonp_free(keys);
504 
505 								return -1;
506 							}
507 						} else {
508 							if (.dump_indent(flags, depth, 0, dump, data)) {
509 								jansson_d.jansson_private.jsonp_free(keys);
510 
511 								return -1;
512 							}
513 						}
514 					}
515 
516 					jansson_d.jansson_private.jsonp_free(keys);
517 				} else {
518 					/* Don't sort keys */
519 
520 					while (iter != null) {
521 						void* next = jansson_d.value.json_object_iter_next(cast(jansson_d.jansson.json_t*)(json), iter);
522 						const char* key = jansson_d.value.json_object_iter_key(iter);
523 						const size_t key_len = jansson_d.value.json_object_iter_key_len(iter);
524 
525 						.dump_string(key, key_len, dump, data, flags);
526 
527 						if ((dump(separator, separator_length, data)) || (.do_dump(jansson_d.value.json_object_iter_value(iter), flags, depth + 1, parents, dump, data))) {
528 							return -1;
529 						}
530 
531 						if (next != null) {
532 							if ((dump(",", 1, data)) || (.dump_indent(flags, depth + 1, 1, dump, data))) {
533 								return -1;
534 							}
535 						} else {
536 							if (.dump_indent(flags, depth, 0, dump, data)) {
537 								return -1;
538 							}
539 						}
540 
541 						iter = next;
542 					}
543 				}
544 
545 				jansson_d.hashtable.hashtable_del(parents, &(loop_key[0]), loop_key_len);
546 
547 				return (embed) ? (0) : (dump("}", 1, data));
548 
549 			default:
550 				/* not reached */
551 				return -1;
552 		}
553 	}
554 
555 ///
556 extern (C)
557 nothrow @trusted @nogc @live //ToDo: @nodiscard
558 public char* json_dumps(scope const jansson_d.jansson.json_t* json, size_t flags)
559 
560 	do
561 	{
562 		jansson_d.strbuffer.strbuffer_t strbuff = void;
563 
564 		if (jansson_d.strbuffer.strbuffer_init(&strbuff)) {
565 			return null;
566 		}
567 
568 		char* result = void;
569 
570 		if (.json_dump_callback(json, &.dump_to_strbuffer, cast(void*)(&strbuff), flags)) {
571 			result = null;
572 		} else {
573 			result = jansson_d.jansson_private.jsonp_strdup(jansson_d.strbuffer.strbuffer_value(&strbuff));
574 		}
575 
576 		jansson_d.strbuffer.strbuffer_close(&strbuff);
577 
578 		return result;
579 	}
580 
581 ///
582 extern (C)
583 nothrow @trusted @nogc @live
584 public size_t json_dumpb(scope const jansson_d.jansson.json_t* json, scope char* buffer, size_t size, size_t flags)
585 
586 	do
587 	{
588 		.buffer_ buf = {size, 0, buffer};
589 
590 		if (.json_dump_callback(json, &.dump_to_buffer, cast(void*)(&buf), flags)) {
591 			return 0;
592 		}
593 
594 		return buf.used;
595 	}
596 
597 ///
598 extern (C)
599 nothrow @trusted @nogc @live
600 public int json_dumpf(scope const jansson_d.jansson.json_t* json, core.stdc.stdio.FILE* output, size_t flags)
601 
602 	do
603 	{
604 		return .json_dump_callback(json, &.dump_to_file, cast(void*)(output), flags);
605 	}
606 
607 ///
608 extern (C)
609 nothrow @trusted @nogc @live
610 public int json_dumpfd(scope const jansson_d.jansson.json_t* json, int output, size_t flags)
611 
612 	do
613 	{
614 		return .json_dump_callback(json, &.dump_to_fd, cast(void*)(&output), flags);
615 	}
616 
617 ///
618 extern (C)
619 nothrow @nogc @live
620 public int json_dump_file(scope const jansson_d.jansson.json_t* json, scope const char* path, size_t flags)
621 
622 	do
623 	{
624 		core.stdc.stdio.FILE* output = core.stdc.stdio.fopen(path, "w");
625 
626 		if (output == null) {
627 			return -1;
628 		}
629 
630 		int result = .json_dumpf(json, output, flags);
631 
632 		if (core.stdc.stdio.fclose(output) != 0) {
633 			return -1;
634 		}
635 
636 		return result;
637 	}
638 
639 ///
640 extern (C)
641 nothrow @trusted @nogc
642 public int json_dump_callback(scope const jansson_d.jansson.json_t* json, jansson_d.jansson.json_dump_callback_t callback, void* data, size_t flags)
643 
644 	do
645 	{
646 		if (!(flags & jansson_d.jansson.JSON_ENCODE_ANY)) {
647 			if ((!mixin (jansson_d.jansson.json_is_array!("json"))) && (!mixin (jansson_d.jansson.json_is_object!("json")))) {
648 				return -1;
649 			}
650 		}
651 
652 		jansson_d.hashtable.hashtable_t parents_set = void;
653 
654 		if (jansson_d.hashtable.hashtable_init(&parents_set)) {
655 			return -1;
656 		}
657 
658 		int res = .do_dump(json, flags, 0, &parents_set, callback, data);
659 		jansson_d.hashtable.hashtable_close(&parents_set);
660 
661 		return res;
662 	}