blob: f5d72abadf03935d2a3d88b1c061216d1236a31c [file] [log] [blame]
Nigel Tao1b073492020-02-16 22:11:36 +11001// Copyright 2020 The Wuffs Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// ----------------
16
17/*
Nigel Tao0cd2f982020-03-03 23:03:02 +110018jsonptr is a JSON formatter (pretty-printer) that supports the JSON Pointer
19(RFC 6901) query syntax. It reads UTF-8 JSON from stdin and writes
20canonicalized, formatted UTF-8 JSON to stdout.
21
Nigel Taod60815c2020-03-26 14:32:35 +110022See the "const char* g_usage" string below for details.
Nigel Tao0cd2f982020-03-03 23:03:02 +110023
24----
25
26JSON Pointer (and this program's implementation) is one of many JSON query
27languages and JSON tools, such as jq, jql and JMESPath. This one is relatively
28simple and fewer-featured compared to those others.
29
30One benefit of simplicity is that this program's JSON and JSON Pointer
31implementations do not dynamically allocate or free memory (yet it does not
32require that the entire input fits in memory at once). They are therefore
33trivially protected against certain bug classes: memory leaks, double-frees and
34use-after-frees.
35
36The core JSON implementation is also written in the Wuffs programming language
Nigel Taof2eb7012020-03-16 21:10:20 +110037(and then transpiled to C/C++), which is memory-safe (e.g. array indexing is
38bounds-checked) but also guards against integer arithmetic overflows.
Nigel Tao0cd2f982020-03-03 23:03:02 +110039
Nigel Taofe0cbbd2020-03-05 22:01:30 +110040For defense in depth, on Linux, this program also self-imposes a
41SECCOMP_MODE_STRICT sandbox before reading (or otherwise processing) its input
42or writing its output. Under this sandbox, the only permitted system calls are
43read, write, exit and sigreturn.
44
Nigel Tao0cd2f982020-03-03 23:03:02 +110045All together, this program aims to safely handle untrusted JSON files without
46fear of security bugs such as remote code execution.
47
48----
Nigel Tao1b073492020-02-16 22:11:36 +110049
Nigel Taoc5b3a9e2020-02-24 11:54:35 +110050As of 2020-02-24, this program passes all 318 "test_parsing" cases from the
51JSON test suite (https://github.com/nst/JSONTestSuite), an appendix to the
52"Parsing JSON is a Minefield" article (http://seriot.ch/parsing_json.php) that
53was first published on 2016-10-26 and updated on 2018-03-30.
54
Nigel Tao0cd2f982020-03-03 23:03:02 +110055After modifying this program, run "build-example.sh example/jsonptr/" and then
56"script/run-json-test-suite.sh" to catch correctness regressions.
57
58----
59
Nigel Taod0b16cb2020-03-14 10:15:54 +110060This program uses Wuffs' JSON decoder at a relatively low level, processing the
61decoder's token-stream output individually. The core loop, in pseudo-code, is
62"for_each_token { handle_token(etc); }", where the handle_token function
Nigel Taod60815c2020-03-26 14:32:35 +110063changes global state (e.g. the `g_depth` and `g_ctx` variables) and prints
Nigel Taod0b16cb2020-03-14 10:15:54 +110064output text based on that state and the token's source text. Notably,
65handle_token is not recursive, even though JSON values can nest.
66
67This approach is centered around JSON tokens. Each JSON 'thing' (e.g. number,
68string, object) comprises one or more JSON tokens.
69
70An alternative, higher-level approach is in the sibling example/jsonfindptrs
71program. Neither approach is better or worse per se, but when studying this
72program, be aware that there are multiple ways to use Wuffs' JSON decoder.
73
74The two programs, jsonfindptrs and jsonptr, also demonstrate different
75trade-offs with regard to JSON object duplicate keys. The JSON spec permits
76different implementations to allow or reject duplicate keys. It is not always
77clear which approach is safer. Rejecting them is certainly unambiguous, and
78security bugs can lurk in ambiguous corners of a file format, if two different
79implementations both silently accept a file but differ on how to interpret it.
80On the other hand, in the worst case, detecting duplicate keys requires O(N)
81memory, where N is the size of the (potentially untrusted) input.
82
83This program (jsonptr) allows duplicate keys and requires only O(1) memory. As
84mentioned above, it doesn't dynamically allocate memory at all, and on Linux,
85it runs in a SECCOMP_MODE_STRICT sandbox.
86
87----
88
Nigel Tao1b073492020-02-16 22:11:36 +110089This example program differs from most other example Wuffs programs in that it
90is written in C++, not C.
91
92$CXX jsonptr.cc && ./a.out < ../../test/data/github-tags.json; rm -f a.out
93
94for a C++ compiler $CXX, such as clang++ or g++.
95*/
96
Nigel Tao721190a2020-04-03 22:25:21 +110097#if defined(__cplusplus) && (__cplusplus < 201103L)
98#error "This C++ program requires -std=c++11 or later"
99#endif
100
Nigel Taofe0cbbd2020-03-05 22:01:30 +1100101#include <errno.h>
Nigel Tao01abc842020-03-06 21:42:33 +1100102#include <fcntl.h>
103#include <stdio.h>
Nigel Tao9cc2c252020-02-23 17:05:49 +1100104#include <string.h>
Nigel Taofe0cbbd2020-03-05 22:01:30 +1100105#include <unistd.h>
Nigel Tao1b073492020-02-16 22:11:36 +1100106
107// Wuffs ships as a "single file C library" or "header file library" as per
108// https://github.com/nothings/stb/blob/master/docs/stb_howto.txt
109//
110// To use that single file as a "foo.c"-like implementation, instead of a
111// "foo.h"-like header, #define WUFFS_IMPLEMENTATION before #include'ing or
112// compiling it.
113#define WUFFS_IMPLEMENTATION
114
115// Defining the WUFFS_CONFIG__MODULE* macros are optional, but it lets users of
116// release/c/etc.c whitelist which parts of Wuffs to build. That file contains
117// the entire Wuffs standard library, implementing a variety of codecs and file
118// formats. Without this macro definition, an optimizing compiler or linker may
119// very well discard Wuffs code for unused codecs, but listing the Wuffs
120// modules we use makes that process explicit. Preprocessing means that such
121// code simply isn't compiled.
122#define WUFFS_CONFIG__MODULES
123#define WUFFS_CONFIG__MODULE__BASE
124#define WUFFS_CONFIG__MODULE__JSON
125
126// If building this program in an environment that doesn't easily accommodate
127// relative includes, you can use the script/inline-c-relative-includes.go
128// program to generate a stand-alone C++ file.
129#include "../../release/c/wuffs-unsupported-snapshot.c"
130
Nigel Taofe0cbbd2020-03-05 22:01:30 +1100131#if defined(__linux__)
132#include <linux/prctl.h>
133#include <linux/seccomp.h>
134#include <sys/prctl.h>
135#include <sys/syscall.h>
136#define WUFFS_EXAMPLE_USE_SECCOMP
137#endif
138
Nigel Tao2cf76db2020-02-27 22:42:01 +1100139#define TRY(error_msg) \
140 do { \
141 const char* z = error_msg; \
142 if (z) { \
143 return z; \
144 } \
145 } while (false)
146
Nigel Taod60815c2020-03-26 14:32:35 +1100147static const char* g_eod = "main: end of data";
Nigel Tao2cf76db2020-02-27 22:42:01 +1100148
Nigel Taod60815c2020-03-26 14:32:35 +1100149static const char* g_usage =
Nigel Tao01abc842020-03-06 21:42:33 +1100150 "Usage: jsonptr -flags input.json\n"
Nigel Tao0cd2f982020-03-03 23:03:02 +1100151 "\n"
Nigel Tao52c4d6a2020-03-08 21:12:38 +1100152 "Flags:\n"
Nigel Tao3690e832020-03-12 16:52:26 +1100153 " -c -compact-output\n"
Nigel Tao94440cf2020-04-02 22:28:24 +1100154 " -d=NUM -max-output-depth=NUM\n"
Nigel Tao52c4d6a2020-03-08 21:12:38 +1100155 " -i=NUM -indent=NUM\n"
Nigel Tao52c4d6a2020-03-08 21:12:38 +1100156 " -q=STR -query=STR\n"
Nigel Taod6fdfb12020-03-11 12:24:14 +1100157 " -s -strict-json-pointer-syntax\n"
Nigel Tao52c4d6a2020-03-08 21:12:38 +1100158 " -t -tabs\n"
159 " -fail-if-unsandboxed\n"
160 "\n"
Nigel Tao01abc842020-03-06 21:42:33 +1100161 "The input.json filename is optional. If absent, it reads from stdin.\n"
Nigel Tao0cd2f982020-03-03 23:03:02 +1100162 "\n"
Nigel Tao52c4d6a2020-03-08 21:12:38 +1100163 "----\n"
164 "\n"
Nigel Tao0cd2f982020-03-03 23:03:02 +1100165 "jsonptr is a JSON formatter (pretty-printer) that supports the JSON\n"
166 "Pointer (RFC 6901) query syntax. It reads UTF-8 JSON from stdin and\n"
167 "writes canonicalized, formatted UTF-8 JSON to stdout.\n"
168 "\n"
169 "Canonicalized means that e.g. \"abc\\u000A\\tx\\u0177z\" is re-written\n"
170 "as \"abc\\n\\txÅ·z\". It does not sort object keys, nor does it reject\n"
Nigel Tao01abc842020-03-06 21:42:33 +1100171 "duplicate keys. Canonicalization does not imply Unicode normalization.\n"
Nigel Tao0cd2f982020-03-03 23:03:02 +1100172 "\n"
173 "Formatted means that arrays' and objects' elements are indented, each\n"
Nigel Tao3690e832020-03-12 16:52:26 +1100174 "on its own line. Configure this with the -c / -compact-output, -i=NUM /\n"
Nigel Tao52c4d6a2020-03-08 21:12:38 +1100175 "-indent=NUM (for NUM ranging from 0 to 8) and -t / -tabs flags.\n"
Nigel Tao0cd2f982020-03-03 23:03:02 +1100176 "\n"
Nigel Tao52c4d6a2020-03-08 21:12:38 +1100177 "----\n"
178 "\n"
179 "The -q=STR or -query=STR flag gives an optional JSON Pointer query, to\n"
Nigel Taofe0cbbd2020-03-05 22:01:30 +1100180 "print a subset of the input. For example, given RFC 6901 section 5's\n"
Nigel Tao01abc842020-03-06 21:42:33 +1100181 "sample input (https://tools.ietf.org/rfc/rfc6901.txt), this command:\n"
182 " jsonptr -query=/foo/1 rfc-6901-json-pointer.json\n"
Nigel Tao0cd2f982020-03-03 23:03:02 +1100183 "will print:\n"
184 " \"baz\"\n"
185 "\n"
186 "An absent query is equivalent to the empty query, which identifies the\n"
Nigel Tao52c4d6a2020-03-08 21:12:38 +1100187 "entire input (the root value). Unlike a file system, the \"/\" query\n"
Nigel Taod0b16cb2020-03-14 10:15:54 +1100188 "does not identify the root. Instead, \"\" is the root and \"/\" is the\n"
189 "child (the value in a key-value pair) of the root whose key is the empty\n"
190 "string. Similarly, \"/xyz\" and \"/xyz/\" are two different nodes.\n"
Nigel Tao0cd2f982020-03-03 23:03:02 +1100191 "\n"
192 "If the query found a valid JSON value, this program will return a zero\n"
193 "exit code even if the rest of the input isn't valid JSON. If the query\n"
194 "did not find a value, or found an invalid one, this program returns a\n"
195 "non-zero exit code, but may still print partial output to stdout.\n"
196 "\n"
Nigel Tao01abc842020-03-06 21:42:33 +1100197 "The JSON specification (https://json.org/) permits implementations that\n"
Nigel Tao0cd2f982020-03-03 23:03:02 +1100198 "allow duplicate keys, as this one does. This JSON Pointer implementation\n"
199 "is also greedy, following the first match for each fragment without\n"
200 "back-tracking. For example, the \"/foo/bar\" query will fail if the root\n"
201 "object has multiple \"foo\" children but the first one doesn't have a\n"
Nigel Taofe0cbbd2020-03-05 22:01:30 +1100202 "\"bar\" child, even if later ones do.\n"
203 "\n"
Nigel Taod6fdfb12020-03-11 12:24:14 +1100204 "The -s or -strict-json-pointer-syntax flag restricts the -query=STR\n"
205 "string to exactly RFC 6901, with only two escape sequences: \"~0\" and\n"
206 "\"~1\" for \"~\" and \"/\". Without this flag, this program also lets\n"
207 "\"~n\" and \"~r\" escape the New Line and Carriage Return ASCII control\n"
208 "characters, which can work better with line oriented Unix tools that\n"
209 "assume exactly one value (i.e. one JSON Pointer string) per line.\n"
210 "\n"
Nigel Tao52c4d6a2020-03-08 21:12:38 +1100211 "----\n"
212 "\n"
Nigel Tao94440cf2020-04-02 22:28:24 +1100213 "The -d=NUM or -max-output-depth=NUM flag gives the maximum (inclusive)\n"
Nigel Tao52c4d6a2020-03-08 21:12:38 +1100214 "output depth. JSON containers ([] arrays and {} objects) can hold other\n"
215 "containers. When this flag is set, containers at depth NUM are replaced\n"
Nigel Tao94440cf2020-04-02 22:28:24 +1100216 "with \"[…]\" or \"{…}\". A bare -d or -max-output-depth is equivalent to\n"
217 "-d=1. The flag's absence is equivalent to an unlimited output depth.\n"
Nigel Tao52c4d6a2020-03-08 21:12:38 +1100218 "\n"
219 "The -max-output-depth flag only affects the program's output. It doesn't\n"
220 "affect whether or not the input is considered valid JSON. The JSON\n"
221 "specification permits implementations to set their own maximum input\n"
222 "depth. This JSON implementation sets it to 1024.\n"
223 "\n"
224 "Depth is measured in terms of nested containers. It is unaffected by the\n"
225 "number of spaces or tabs used to indent.\n"
226 "\n"
227 "When both -max-output-depth and -query are set, the output depth is\n"
228 "measured from when the query resolves, not from the input root. The\n"
229 "input depth (measured from the root) is still limited to 1024.\n"
230 "\n"
231 "----\n"
232 "\n"
Nigel Taofe0cbbd2020-03-05 22:01:30 +1100233 "The -fail-if-unsandboxed flag causes the program to exit if it does not\n"
234 "self-impose a sandbox. On Linux, it self-imposes a SECCOMP_MODE_STRICT\n"
Nigel Tao52c4d6a2020-03-08 21:12:38 +1100235 "sandbox, regardless of whether this flag was set.";
Nigel Tao0cd2f982020-03-03 23:03:02 +1100236
Nigel Tao2cf76db2020-02-27 22:42:01 +1100237// ----
238
Nigel Taof3146c22020-03-26 08:47:42 +1100239// Wuffs allows either statically or dynamically allocated work buffers. This
240// program exercises static allocation.
241#define WORK_BUFFER_ARRAY_SIZE \
242 WUFFS_JSON__DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE
243#if WORK_BUFFER_ARRAY_SIZE > 0
Nigel Taod60815c2020-03-26 14:32:35 +1100244uint8_t g_work_buffer_array[WORK_BUFFER_ARRAY_SIZE];
Nigel Taof3146c22020-03-26 08:47:42 +1100245#else
246// Not all C/C++ compilers support 0-length arrays.
Nigel Taod60815c2020-03-26 14:32:35 +1100247uint8_t g_work_buffer_array[1];
Nigel Taof3146c22020-03-26 08:47:42 +1100248#endif
249
Nigel Taod60815c2020-03-26 14:32:35 +1100250bool g_sandboxed = false;
Nigel Taofe0cbbd2020-03-05 22:01:30 +1100251
Nigel Taod60815c2020-03-26 14:32:35 +1100252int g_input_file_descriptor = 0; // A 0 default means stdin.
Nigel Tao01abc842020-03-06 21:42:33 +1100253
Nigel Tao2cf76db2020-02-27 22:42:01 +1100254#define MAX_INDENT 8
Nigel Tao107f0ef2020-03-01 21:35:02 +1100255#define INDENT_SPACES_STRING " "
Nigel Tao6e7d1412020-03-06 09:21:35 +1100256#define INDENT_TAB_STRING "\t"
Nigel Tao107f0ef2020-03-01 21:35:02 +1100257
Nigel Taofdac24a2020-03-06 21:53:08 +1100258#ifndef DST_BUFFER_ARRAY_SIZE
259#define DST_BUFFER_ARRAY_SIZE (32 * 1024)
Nigel Tao1b073492020-02-16 22:11:36 +1100260#endif
Nigel Taofdac24a2020-03-06 21:53:08 +1100261#ifndef SRC_BUFFER_ARRAY_SIZE
262#define SRC_BUFFER_ARRAY_SIZE (32 * 1024)
Nigel Tao1b073492020-02-16 22:11:36 +1100263#endif
Nigel Taofdac24a2020-03-06 21:53:08 +1100264#ifndef TOKEN_BUFFER_ARRAY_SIZE
265#define TOKEN_BUFFER_ARRAY_SIZE (4 * 1024)
Nigel Tao1b073492020-02-16 22:11:36 +1100266#endif
267
Nigel Taod60815c2020-03-26 14:32:35 +1100268uint8_t g_dst_array[DST_BUFFER_ARRAY_SIZE];
269uint8_t g_src_array[SRC_BUFFER_ARRAY_SIZE];
270wuffs_base__token g_tok_array[TOKEN_BUFFER_ARRAY_SIZE];
Nigel Tao1b073492020-02-16 22:11:36 +1100271
Nigel Taod60815c2020-03-26 14:32:35 +1100272wuffs_base__io_buffer g_dst;
273wuffs_base__io_buffer g_src;
274wuffs_base__token_buffer g_tok;
Nigel Tao1b073492020-02-16 22:11:36 +1100275
Nigel Taod60815c2020-03-26 14:32:35 +1100276// g_curr_token_end_src_index is the g_src.data.ptr index of the end of the
277// current token. An invariant is that (g_curr_token_end_src_index <=
278// g_src.meta.ri).
279size_t g_curr_token_end_src_index;
Nigel Tao2cf76db2020-02-27 22:42:01 +1100280
Nigel Taod60815c2020-03-26 14:32:35 +1100281uint32_t g_depth;
Nigel Tao2cf76db2020-02-27 22:42:01 +1100282
283enum class context {
284 none,
285 in_list_after_bracket,
286 in_list_after_value,
287 in_dict_after_brace,
288 in_dict_after_key,
289 in_dict_after_value,
Nigel Taod60815c2020-03-26 14:32:35 +1100290} g_ctx;
Nigel Tao2cf76db2020-02-27 22:42:01 +1100291
Nigel Tao0cd2f982020-03-03 23:03:02 +1100292bool //
293in_dict_before_key() {
Nigel Taod60815c2020-03-26 14:32:35 +1100294 return (g_ctx == context::in_dict_after_brace) ||
295 (g_ctx == context::in_dict_after_value);
Nigel Tao0cd2f982020-03-03 23:03:02 +1100296}
297
Nigel Taod60815c2020-03-26 14:32:35 +1100298uint32_t g_suppress_write_dst;
299bool g_wrote_to_dst;
Nigel Tao0cd2f982020-03-03 23:03:02 +1100300
Nigel Taod60815c2020-03-26 14:32:35 +1100301wuffs_json__decoder g_dec;
Nigel Tao1b073492020-02-16 22:11:36 +1100302
Nigel Tao0cd2f982020-03-03 23:03:02 +1100303// ----
304
305// Query is a JSON Pointer query. After initializing with a NUL-terminated C
306// string, its multiple fragments are consumed as the program walks the JSON
307// data from stdin. For example, letting "$" denote a NUL, suppose that we
308// started with a query string of "/apple/banana/12/durian" and are currently
Nigel Taob48ee752020-03-13 09:27:33 +1100309// trying to match the second fragment, "banana", so that Query::m_depth is 2:
Nigel Tao0cd2f982020-03-03 23:03:02 +1100310//
311// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
312// / a p p l e / b a n a n a / 1 2 / d u r i a n $
313// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
314// ^ ^
Nigel Taob48ee752020-03-13 09:27:33 +1100315// m_frag_i m_frag_k
Nigel Tao0cd2f982020-03-03 23:03:02 +1100316//
Nigel Taob48ee752020-03-13 09:27:33 +1100317// The two pointers m_frag_i and m_frag_k (abbreviated as mfi and mfk) are the
318// start (inclusive) and end (exclusive) of the query fragment. They satisfy
319// (mfi <= mfk) and may be equal if the fragment empty (note that "" is a valid
320// JSON object key).
Nigel Tao0cd2f982020-03-03 23:03:02 +1100321//
Nigel Taob48ee752020-03-13 09:27:33 +1100322// The m_frag_j (mfj) pointer moves between these two, or is nullptr. An
323// invariant is that (((mfi <= mfj) && (mfj <= mfk)) || (mfj == nullptr)).
Nigel Tao0cd2f982020-03-03 23:03:02 +1100324//
325// Wuffs' JSON tokenizer can portray a single JSON string as multiple Wuffs
326// tokens, as backslash-escaped values within that JSON string may each get
327// their own token.
328//
Nigel Taob48ee752020-03-13 09:27:33 +1100329// At the start of each object key (a JSON string), mfj is set to mfi.
Nigel Tao0cd2f982020-03-03 23:03:02 +1100330//
Nigel Taob48ee752020-03-13 09:27:33 +1100331// While mfj remains non-nullptr, each token's unescaped contents are then
332// compared to that part of the fragment from mfj to mfk. If it is a prefix
333// (including the case of an exact match), then mfj is advanced by the
334// unescaped length. Otherwise, mfj is set to nullptr.
Nigel Tao0cd2f982020-03-03 23:03:02 +1100335//
336// Comparison accounts for JSON Pointer's escaping notation: "~0" and "~1" in
337// the query (not the JSON value) are unescaped to "~" and "/" respectively.
Nigel Taob48ee752020-03-13 09:27:33 +1100338// "~n" and "~r" are also unescaped to "\n" and "\r". The program is
339// responsible for calling Query::validate (with a strict_json_pointer_syntax
340// argument) before otherwise using this class.
Nigel Tao0cd2f982020-03-03 23:03:02 +1100341//
Nigel Taob48ee752020-03-13 09:27:33 +1100342// The mfj pointer therefore advances from mfi to mfk, or drops out, as we
343// incrementally match the object key with the query fragment. For example, if
344// we have already matched the "ban" of "banana", then we would accept any of
345// an "ana" token, an "a" token or a "\u0061" token, amongst others. They would
346// advance mfj by 3, 1 or 1 bytes respectively.
Nigel Tao0cd2f982020-03-03 23:03:02 +1100347//
Nigel Taob48ee752020-03-13 09:27:33 +1100348// mfj
Nigel Tao0cd2f982020-03-03 23:03:02 +1100349// v
350// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
351// / a p p l e / b a n a n a / 1 2 / d u r i a n $
352// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
353// ^ ^
Nigel Taob48ee752020-03-13 09:27:33 +1100354// mfi mfk
Nigel Tao0cd2f982020-03-03 23:03:02 +1100355//
356// At the end of each object key (or equivalently, at the start of each object
Nigel Taob48ee752020-03-13 09:27:33 +1100357// value), if mfj is non-nullptr and equal to (but not less than) mfk then we
358// have a fragment match: the query fragment equals the object key. If there is
359// a next fragment (in this example, "12") we move the frag_etc pointers to its
360// start and end and increment Query::m_depth. Otherwise, we have matched the
361// complete query, and the upcoming JSON value is the result of that query.
Nigel Tao0cd2f982020-03-03 23:03:02 +1100362//
363// The discussion above centers on object keys. If the query fragment is
364// numeric then it can also match as an array index: the string fragment "12"
365// will match an array's 13th element (starting counting from zero). See RFC
366// 6901 for its precise definition of an "array index" number.
367//
Nigel Taob48ee752020-03-13 09:27:33 +1100368// Array index fragment match is represented by the Query::m_array_index field,
Nigel Tao0cd2f982020-03-03 23:03:02 +1100369// whose type (wuffs_base__result_u64) is a result type. An error result means
370// that the fragment is not an array index. A value result holds the number of
371// list elements remaining. When matching a query fragment in an array (instead
372// of in an object), each element ticks this number down towards zero. At zero,
373// the upcoming JSON value is the one that matches the query fragment.
374class Query {
375 private:
Nigel Taob48ee752020-03-13 09:27:33 +1100376 uint8_t* m_frag_i;
377 uint8_t* m_frag_j;
378 uint8_t* m_frag_k;
Nigel Tao0cd2f982020-03-03 23:03:02 +1100379
Nigel Taob48ee752020-03-13 09:27:33 +1100380 uint32_t m_depth;
Nigel Tao0cd2f982020-03-03 23:03:02 +1100381
Nigel Taob48ee752020-03-13 09:27:33 +1100382 wuffs_base__result_u64 m_array_index;
Nigel Tao0cd2f982020-03-03 23:03:02 +1100383
384 public:
385 void reset(char* query_c_string) {
Nigel Taob48ee752020-03-13 09:27:33 +1100386 m_frag_i = (uint8_t*)query_c_string;
387 m_frag_j = (uint8_t*)query_c_string;
388 m_frag_k = (uint8_t*)query_c_string;
389 m_depth = 0;
390 m_array_index.status.repr = "#main: not an array index query fragment";
391 m_array_index.value = 0;
Nigel Tao0cd2f982020-03-03 23:03:02 +1100392 }
393
Nigel Taob48ee752020-03-13 09:27:33 +1100394 void restart_fragment(bool enable) { m_frag_j = enable ? m_frag_i : nullptr; }
Nigel Tao0cd2f982020-03-03 23:03:02 +1100395
Nigel Taob48ee752020-03-13 09:27:33 +1100396 bool is_at(uint32_t depth) { return m_depth == depth; }
Nigel Tao0cd2f982020-03-03 23:03:02 +1100397
398 // tick returns whether the fragment is a valid array index whose value is
399 // zero. If valid but non-zero, it decrements it and returns false.
400 bool tick() {
Nigel Taob48ee752020-03-13 09:27:33 +1100401 if (m_array_index.status.is_ok()) {
402 if (m_array_index.value == 0) {
Nigel Tao0cd2f982020-03-03 23:03:02 +1100403 return true;
404 }
Nigel Taob48ee752020-03-13 09:27:33 +1100405 m_array_index.value--;
Nigel Tao0cd2f982020-03-03 23:03:02 +1100406 }
407 return false;
408 }
409
410 // next_fragment moves to the next fragment, returning whether it existed.
411 bool next_fragment() {
Nigel Taob48ee752020-03-13 09:27:33 +1100412 uint8_t* k = m_frag_k;
413 uint32_t d = m_depth;
Nigel Tao0cd2f982020-03-03 23:03:02 +1100414
415 this->reset(nullptr);
416
417 if (!k || (*k != '/')) {
418 return false;
419 }
420 k++;
421
422 bool all_digits = true;
423 uint8_t* i = k;
424 while ((*k != '\x00') && (*k != '/')) {
425 all_digits = all_digits && ('0' <= *k) && (*k <= '9');
426 k++;
427 }
Nigel Taob48ee752020-03-13 09:27:33 +1100428 m_frag_i = i;
429 m_frag_j = i;
430 m_frag_k = k;
431 m_depth = d + 1;
Nigel Tao0cd2f982020-03-03 23:03:02 +1100432 if (all_digits) {
433 // wuffs_base__parse_number_u64 rejects leading zeroes, e.g. "00", "07".
Nigel Taob48ee752020-03-13 09:27:33 +1100434 m_array_index =
Nigel Tao0cd2f982020-03-03 23:03:02 +1100435 wuffs_base__parse_number_u64(wuffs_base__make_slice_u8(i, k - i));
436 }
437 return true;
438 }
439
Nigel Taob48ee752020-03-13 09:27:33 +1100440 bool matched_all() { return m_frag_k == nullptr; }
Nigel Tao52c4d6a2020-03-08 21:12:38 +1100441
Nigel Taob48ee752020-03-13 09:27:33 +1100442 bool matched_fragment() { return m_frag_j && (m_frag_j == m_frag_k); }
Nigel Tao0cd2f982020-03-03 23:03:02 +1100443
444 void incremental_match_slice(uint8_t* ptr, size_t len) {
Nigel Taob48ee752020-03-13 09:27:33 +1100445 if (!m_frag_j) {
Nigel Tao0cd2f982020-03-03 23:03:02 +1100446 return;
447 }
Nigel Taob48ee752020-03-13 09:27:33 +1100448 uint8_t* j = m_frag_j;
Nigel Tao0cd2f982020-03-03 23:03:02 +1100449 while (true) {
450 if (len == 0) {
Nigel Taob48ee752020-03-13 09:27:33 +1100451 m_frag_j = j;
Nigel Tao0cd2f982020-03-03 23:03:02 +1100452 return;
453 }
454
455 if (*j == '\x00') {
456 break;
457
458 } else if (*j == '~') {
459 j++;
460 if (*j == '0') {
461 if (*ptr != '~') {
462 break;
463 }
464 } else if (*j == '1') {
465 if (*ptr != '/') {
466 break;
467 }
Nigel Taod6fdfb12020-03-11 12:24:14 +1100468 } else if (*j == 'n') {
469 if (*ptr != '\n') {
470 break;
471 }
472 } else if (*j == 'r') {
473 if (*ptr != '\r') {
474 break;
475 }
Nigel Tao0cd2f982020-03-03 23:03:02 +1100476 } else {
477 break;
478 }
479
480 } else if (*j != *ptr) {
481 break;
482 }
483
484 j++;
485 ptr++;
486 len--;
487 }
Nigel Taob48ee752020-03-13 09:27:33 +1100488 m_frag_j = nullptr;
Nigel Tao0cd2f982020-03-03 23:03:02 +1100489 }
490
491 void incremental_match_code_point(uint32_t code_point) {
Nigel Taob48ee752020-03-13 09:27:33 +1100492 if (!m_frag_j) {
Nigel Tao0cd2f982020-03-03 23:03:02 +1100493 return;
494 }
495 uint8_t u[WUFFS_BASE__UTF_8__BYTE_LENGTH__MAX_INCL];
496 size_t n = wuffs_base__utf_8__encode(
497 wuffs_base__make_slice_u8(&u[0],
498 WUFFS_BASE__UTF_8__BYTE_LENGTH__MAX_INCL),
499 code_point);
500 if (n > 0) {
501 this->incremental_match_slice(&u[0], n);
502 }
503 }
504
505 // validate returns whether the (ptr, len) arguments form a valid JSON
506 // Pointer. In particular, it must be valid UTF-8, and either be empty or
507 // start with a '/'. Any '~' within must immediately be followed by either
Nigel Taod6fdfb12020-03-11 12:24:14 +1100508 // '0' or '1'. If strict_json_pointer_syntax is false, a '~' may also be
509 // followed by either 'n' or 'r'.
510 static bool validate(char* query_c_string,
511 size_t length,
512 bool strict_json_pointer_syntax) {
Nigel Tao0cd2f982020-03-03 23:03:02 +1100513 if (length <= 0) {
514 return true;
515 }
516 if (query_c_string[0] != '/') {
517 return false;
518 }
519 wuffs_base__slice_u8 s =
520 wuffs_base__make_slice_u8((uint8_t*)query_c_string, length);
521 bool previous_was_tilde = false;
522 while (s.len > 0) {
523 wuffs_base__utf_8__next__output o = wuffs_base__utf_8__next(s);
524 if (!o.is_valid()) {
525 return false;
526 }
Nigel Taod6fdfb12020-03-11 12:24:14 +1100527
528 if (previous_was_tilde) {
529 switch (o.code_point) {
530 case '0':
531 case '1':
532 break;
533 case 'n':
534 case 'r':
535 if (strict_json_pointer_syntax) {
536 return false;
537 }
538 break;
539 default:
540 return false;
541 }
Nigel Tao0cd2f982020-03-03 23:03:02 +1100542 }
543 previous_was_tilde = o.code_point == '~';
Nigel Taod6fdfb12020-03-11 12:24:14 +1100544
Nigel Tao0cd2f982020-03-03 23:03:02 +1100545 s.ptr += o.byte_length;
546 s.len -= o.byte_length;
547 }
548 return !previous_was_tilde;
549 }
Nigel Taod60815c2020-03-26 14:32:35 +1100550} g_query;
Nigel Tao0cd2f982020-03-03 23:03:02 +1100551
552// ----
553
Nigel Tao68920952020-03-03 11:25:18 +1100554struct {
555 int remaining_argc;
556 char** remaining_argv;
557
Nigel Tao3690e832020-03-12 16:52:26 +1100558 bool compact_output;
Nigel Taofe0cbbd2020-03-05 22:01:30 +1100559 bool fail_if_unsandboxed;
Nigel Tao68920952020-03-03 11:25:18 +1100560 size_t indent;
Nigel Tao52c4d6a2020-03-08 21:12:38 +1100561 uint32_t max_output_depth;
Nigel Tao0cd2f982020-03-03 23:03:02 +1100562 char* query_c_string;
Nigel Taod6fdfb12020-03-11 12:24:14 +1100563 bool strict_json_pointer_syntax;
Nigel Tao68920952020-03-03 11:25:18 +1100564 bool tabs;
Nigel Taod60815c2020-03-26 14:32:35 +1100565} g_flags = {0};
Nigel Tao68920952020-03-03 11:25:18 +1100566
567const char* //
568parse_flags(int argc, char** argv) {
Nigel Taod60815c2020-03-26 14:32:35 +1100569 g_flags.indent = 4;
570 g_flags.max_output_depth = 0xFFFFFFFF;
Nigel Tao68920952020-03-03 11:25:18 +1100571
572 int c = (argc > 0) ? 1 : 0; // Skip argv[0], the program name.
573 for (; c < argc; c++) {
574 char* arg = argv[c];
575 if (*arg++ != '-') {
576 break;
577 }
578
579 // A double-dash "--foo" is equivalent to a single-dash "-foo". As special
580 // cases, a bare "-" is not a flag (some programs may interpret it as
581 // stdin) and a bare "--" means to stop parsing flags.
582 if (*arg == '\x00') {
583 break;
584 } else if (*arg == '-') {
585 arg++;
586 if (*arg == '\x00') {
587 c++;
588 break;
589 }
590 }
591
Nigel Tao3690e832020-03-12 16:52:26 +1100592 if (!strcmp(arg, "c") || !strcmp(arg, "compact-output")) {
Nigel Taod60815c2020-03-26 14:32:35 +1100593 g_flags.compact_output = true;
Nigel Tao68920952020-03-03 11:25:18 +1100594 continue;
595 }
Nigel Tao94440cf2020-04-02 22:28:24 +1100596 if (!strcmp(arg, "d") || !strcmp(arg, "max-output-depth")) {
597 g_flags.max_output_depth = 1;
598 continue;
599 } else if (!strncmp(arg, "d=", 2) ||
600 !strncmp(arg, "max-output-depth=", 16)) {
601 while (*arg++ != '=') {
602 }
603 wuffs_base__result_u64 u = wuffs_base__parse_number_u64(
604 wuffs_base__make_slice_u8((uint8_t*)arg, strlen(arg)));
605 if (wuffs_base__status__is_ok(&u.status) && (u.value <= 0xFFFFFFFF)) {
606 g_flags.max_output_depth = (uint32_t)(u.value);
607 continue;
608 }
609 return g_usage;
610 }
Nigel Taofe0cbbd2020-03-05 22:01:30 +1100611 if (!strcmp(arg, "fail-if-unsandboxed")) {
Nigel Taod60815c2020-03-26 14:32:35 +1100612 g_flags.fail_if_unsandboxed = true;
Nigel Taofe0cbbd2020-03-05 22:01:30 +1100613 continue;
614 }
Nigel Tao68920952020-03-03 11:25:18 +1100615 if (!strncmp(arg, "i=", 2) || !strncmp(arg, "indent=", 7)) {
616 while (*arg++ != '=') {
617 }
618 if (('0' <= arg[0]) && (arg[0] <= '8') && (arg[1] == '\x00')) {
Nigel Taod60815c2020-03-26 14:32:35 +1100619 g_flags.indent = arg[0] - '0';
Nigel Tao68920952020-03-03 11:25:18 +1100620 continue;
621 }
Nigel Taod60815c2020-03-26 14:32:35 +1100622 return g_usage;
Nigel Tao0cd2f982020-03-03 23:03:02 +1100623 }
624 if (!strncmp(arg, "q=", 2) || !strncmp(arg, "query=", 6)) {
625 while (*arg++ != '=') {
626 }
Nigel Taod60815c2020-03-26 14:32:35 +1100627 g_flags.query_c_string = arg;
Nigel Taod6fdfb12020-03-11 12:24:14 +1100628 continue;
629 }
630 if (!strcmp(arg, "s") || !strcmp(arg, "strict-json-pointer-syntax")) {
Nigel Taod60815c2020-03-26 14:32:35 +1100631 g_flags.strict_json_pointer_syntax = true;
Nigel Taod6fdfb12020-03-11 12:24:14 +1100632 continue;
Nigel Tao68920952020-03-03 11:25:18 +1100633 }
634 if (!strcmp(arg, "t") || !strcmp(arg, "tabs")) {
Nigel Taod60815c2020-03-26 14:32:35 +1100635 g_flags.tabs = true;
Nigel Tao68920952020-03-03 11:25:18 +1100636 continue;
637 }
638
Nigel Taod60815c2020-03-26 14:32:35 +1100639 return g_usage;
Nigel Tao68920952020-03-03 11:25:18 +1100640 }
641
Nigel Taod60815c2020-03-26 14:32:35 +1100642 if (g_flags.query_c_string &&
643 !Query::validate(g_flags.query_c_string, strlen(g_flags.query_c_string),
644 g_flags.strict_json_pointer_syntax)) {
Nigel Taod6fdfb12020-03-11 12:24:14 +1100645 return "main: bad JSON Pointer (RFC 6901) syntax for the -query=STR flag";
646 }
647
Nigel Taod60815c2020-03-26 14:32:35 +1100648 g_flags.remaining_argc = argc - c;
649 g_flags.remaining_argv = argv + c;
Nigel Tao0cd2f982020-03-03 23:03:02 +1100650 return nullptr;
Nigel Tao68920952020-03-03 11:25:18 +1100651}
652
Nigel Tao2cf76db2020-02-27 22:42:01 +1100653const char* //
654initialize_globals(int argc, char** argv) {
Nigel Taod60815c2020-03-26 14:32:35 +1100655 g_dst = wuffs_base__make_io_buffer(
656 wuffs_base__make_slice_u8(g_dst_array, DST_BUFFER_ARRAY_SIZE),
Nigel Tao2cf76db2020-02-27 22:42:01 +1100657 wuffs_base__empty_io_buffer_meta());
Nigel Tao1b073492020-02-16 22:11:36 +1100658
Nigel Taod60815c2020-03-26 14:32:35 +1100659 g_src = wuffs_base__make_io_buffer(
660 wuffs_base__make_slice_u8(g_src_array, SRC_BUFFER_ARRAY_SIZE),
Nigel Tao2cf76db2020-02-27 22:42:01 +1100661 wuffs_base__empty_io_buffer_meta());
662
Nigel Taod60815c2020-03-26 14:32:35 +1100663 g_tok = wuffs_base__make_token_buffer(
664 wuffs_base__make_slice_token(g_tok_array, TOKEN_BUFFER_ARRAY_SIZE),
Nigel Tao2cf76db2020-02-27 22:42:01 +1100665 wuffs_base__empty_token_buffer_meta());
666
Nigel Taod60815c2020-03-26 14:32:35 +1100667 g_curr_token_end_src_index = 0;
Nigel Tao2cf76db2020-02-27 22:42:01 +1100668
Nigel Taod60815c2020-03-26 14:32:35 +1100669 g_depth = 0;
Nigel Tao2cf76db2020-02-27 22:42:01 +1100670
Nigel Taod60815c2020-03-26 14:32:35 +1100671 g_ctx = context::none;
Nigel Tao2cf76db2020-02-27 22:42:01 +1100672
Nigel Tao68920952020-03-03 11:25:18 +1100673 TRY(parse_flags(argc, argv));
Nigel Taod60815c2020-03-26 14:32:35 +1100674 if (g_flags.fail_if_unsandboxed && !g_sandboxed) {
Nigel Taofe0cbbd2020-03-05 22:01:30 +1100675 return "main: unsandboxed";
676 }
Nigel Tao01abc842020-03-06 21:42:33 +1100677 const int stdin_fd = 0;
Nigel Taod60815c2020-03-26 14:32:35 +1100678 if (g_flags.remaining_argc >
679 ((g_input_file_descriptor != stdin_fd) ? 1 : 0)) {
680 return g_usage;
Nigel Tao107f0ef2020-03-01 21:35:02 +1100681 }
682
Nigel Taod60815c2020-03-26 14:32:35 +1100683 g_query.reset(g_flags.query_c_string);
Nigel Tao0cd2f982020-03-03 23:03:02 +1100684
685 // If the query is non-empty, suprress writing to stdout until we've
686 // completed the query.
Nigel Taod60815c2020-03-26 14:32:35 +1100687 g_suppress_write_dst = g_query.next_fragment() ? 1 : 0;
688 g_wrote_to_dst = false;
Nigel Tao0cd2f982020-03-03 23:03:02 +1100689
Nigel Taod60815c2020-03-26 14:32:35 +1100690 TRY(g_dec.initialize(sizeof__wuffs_json__decoder(), WUFFS_VERSION, 0)
Nigel Tao4b186b02020-03-18 14:25:21 +1100691 .message());
692
693 // Consume an optional whitespace trailer. This isn't part of the JSON spec,
694 // but it works better with line oriented Unix tools (such as "echo 123 |
695 // jsonptr" where it's "echo", not "echo -n") or hand-edited JSON files which
696 // can accidentally contain trailing whitespace.
Nigel Taod60815c2020-03-26 14:32:35 +1100697 g_dec.set_quirk_enabled(WUFFS_JSON__QUIRK_ALLOW_TRAILING_NEW_LINE, true);
Nigel Tao4b186b02020-03-18 14:25:21 +1100698
699 return nullptr;
Nigel Tao2cf76db2020-02-27 22:42:01 +1100700}
Nigel Tao1b073492020-02-16 22:11:36 +1100701
702// ----
703
Nigel Taofe0cbbd2020-03-05 22:01:30 +1100704// ignore_return_value suppresses errors from -Wall -Werror.
705static void //
706ignore_return_value(int ignored) {}
707
Nigel Tao2914bae2020-02-26 09:40:30 +1100708const char* //
709read_src() {
Nigel Taod60815c2020-03-26 14:32:35 +1100710 if (g_src.meta.closed) {
Nigel Tao9cc2c252020-02-23 17:05:49 +1100711 return "main: internal error: read requested on a closed source";
Nigel Taoa8406922020-02-19 12:22:00 +1100712 }
Nigel Taod60815c2020-03-26 14:32:35 +1100713 g_src.compact();
714 if (g_src.meta.wi >= g_src.data.len) {
715 return "main: g_src buffer is full";
Nigel Tao1b073492020-02-16 22:11:36 +1100716 }
Nigel Taofe0cbbd2020-03-05 22:01:30 +1100717 while (true) {
Nigel Taod60815c2020-03-26 14:32:35 +1100718 ssize_t n = read(g_input_file_descriptor, g_src.data.ptr + g_src.meta.wi,
719 g_src.data.len - g_src.meta.wi);
Nigel Taofe0cbbd2020-03-05 22:01:30 +1100720 if (n >= 0) {
Nigel Taod60815c2020-03-26 14:32:35 +1100721 g_src.meta.wi += n;
722 g_src.meta.closed = n == 0;
Nigel Taofe0cbbd2020-03-05 22:01:30 +1100723 break;
724 } else if (errno != EINTR) {
725 return strerror(errno);
726 }
Nigel Tao1b073492020-02-16 22:11:36 +1100727 }
728 return nullptr;
729}
730
Nigel Tao2914bae2020-02-26 09:40:30 +1100731const char* //
732flush_dst() {
Nigel Taofe0cbbd2020-03-05 22:01:30 +1100733 while (true) {
Nigel Taod60815c2020-03-26 14:32:35 +1100734 size_t n = g_dst.meta.wi - g_dst.meta.ri;
Nigel Taofe0cbbd2020-03-05 22:01:30 +1100735 if (n == 0) {
736 break;
Nigel Tao1b073492020-02-16 22:11:36 +1100737 }
Nigel Taofe0cbbd2020-03-05 22:01:30 +1100738 const int stdout_fd = 1;
Nigel Taod60815c2020-03-26 14:32:35 +1100739 ssize_t i = write(stdout_fd, g_dst.data.ptr + g_dst.meta.ri, n);
Nigel Taofe0cbbd2020-03-05 22:01:30 +1100740 if (i >= 0) {
Nigel Taod60815c2020-03-26 14:32:35 +1100741 g_dst.meta.ri += i;
Nigel Taofe0cbbd2020-03-05 22:01:30 +1100742 } else if (errno != EINTR) {
743 return strerror(errno);
744 }
Nigel Tao1b073492020-02-16 22:11:36 +1100745 }
Nigel Taod60815c2020-03-26 14:32:35 +1100746 g_dst.compact();
Nigel Tao1b073492020-02-16 22:11:36 +1100747 return nullptr;
748}
749
Nigel Tao2914bae2020-02-26 09:40:30 +1100750const char* //
751write_dst(const void* s, size_t n) {
Nigel Taod60815c2020-03-26 14:32:35 +1100752 if (g_suppress_write_dst > 0) {
Nigel Tao0cd2f982020-03-03 23:03:02 +1100753 return nullptr;
754 }
Nigel Tao1b073492020-02-16 22:11:36 +1100755 const uint8_t* p = static_cast<const uint8_t*>(s);
756 while (n > 0) {
Nigel Taod60815c2020-03-26 14:32:35 +1100757 size_t i = g_dst.writer_available();
Nigel Tao1b073492020-02-16 22:11:36 +1100758 if (i == 0) {
759 const char* z = flush_dst();
760 if (z) {
761 return z;
762 }
Nigel Taod60815c2020-03-26 14:32:35 +1100763 i = g_dst.writer_available();
Nigel Tao1b073492020-02-16 22:11:36 +1100764 if (i == 0) {
Nigel Taod60815c2020-03-26 14:32:35 +1100765 return "main: g_dst buffer is full";
Nigel Tao1b073492020-02-16 22:11:36 +1100766 }
767 }
768
769 if (i > n) {
770 i = n;
771 }
Nigel Taod60815c2020-03-26 14:32:35 +1100772 memcpy(g_dst.data.ptr + g_dst.meta.wi, p, i);
773 g_dst.meta.wi += i;
Nigel Tao1b073492020-02-16 22:11:36 +1100774 p += i;
775 n -= i;
Nigel Taod60815c2020-03-26 14:32:35 +1100776 g_wrote_to_dst = true;
Nigel Tao1b073492020-02-16 22:11:36 +1100777 }
778 return nullptr;
779}
780
781// ----
782
Nigel Tao2914bae2020-02-26 09:40:30 +1100783uint8_t //
784hex_digit(uint8_t nibble) {
Nigel Taob5461bd2020-02-21 14:13:37 +1100785 nibble &= 0x0F;
786 if (nibble <= 9) {
787 return '0' + nibble;
788 }
789 return ('A' - 10) + nibble;
790}
791
Nigel Tao2914bae2020-02-26 09:40:30 +1100792const char* //
Nigel Tao3b486982020-02-27 15:05:59 +1100793handle_unicode_code_point(uint32_t ucp) {
794 if (ucp < 0x0020) {
795 switch (ucp) {
796 case '\b':
797 return write_dst("\\b", 2);
798 case '\f':
799 return write_dst("\\f", 2);
800 case '\n':
801 return write_dst("\\n", 2);
802 case '\r':
803 return write_dst("\\r", 2);
804 case '\t':
805 return write_dst("\\t", 2);
806 default: {
807 // Other bytes less than 0x0020 are valid UTF-8 but not valid in a
808 // JSON string. They need to remain escaped.
809 uint8_t esc6[6];
810 esc6[0] = '\\';
811 esc6[1] = 'u';
812 esc6[2] = '0';
813 esc6[3] = '0';
814 esc6[4] = hex_digit(ucp >> 4);
815 esc6[5] = hex_digit(ucp >> 0);
816 return write_dst(&esc6[0], 6);
817 }
818 }
819
Nigel Taob9ad34f2020-03-03 12:44:01 +1100820 } else if (ucp == '\"') {
821 return write_dst("\\\"", 2);
822
823 } else if (ucp == '\\') {
824 return write_dst("\\\\", 2);
825
826 } else {
827 uint8_t u[WUFFS_BASE__UTF_8__BYTE_LENGTH__MAX_INCL];
828 size_t n = wuffs_base__utf_8__encode(
829 wuffs_base__make_slice_u8(&u[0],
830 WUFFS_BASE__UTF_8__BYTE_LENGTH__MAX_INCL),
831 ucp);
832 if (n > 0) {
833 return write_dst(&u[0], n);
Nigel Tao3b486982020-02-27 15:05:59 +1100834 }
Nigel Tao3b486982020-02-27 15:05:59 +1100835 }
836
Nigel Tao2cf76db2020-02-27 22:42:01 +1100837 return "main: internal error: unexpected Unicode code point";
Nigel Tao3b486982020-02-27 15:05:59 +1100838}
839
840const char* //
Nigel Tao2cf76db2020-02-27 22:42:01 +1100841handle_token(wuffs_base__token t) {
842 do {
Nigel Tao462f8662020-04-01 23:01:51 +1100843 int64_t vbc = t.value_base_category();
Nigel Tao2cf76db2020-02-27 22:42:01 +1100844 uint64_t vbd = t.value_base_detail();
845 uint64_t len = t.length();
Nigel Tao1b073492020-02-16 22:11:36 +1100846
847 // Handle ']' or '}'.
Nigel Tao9f7a2502020-02-23 09:42:02 +1100848 if ((vbc == WUFFS_BASE__TOKEN__VBC__STRUCTURE) &&
Nigel Tao2cf76db2020-02-27 22:42:01 +1100849 (vbd & WUFFS_BASE__TOKEN__VBD__STRUCTURE__POP)) {
Nigel Taod60815c2020-03-26 14:32:35 +1100850 if (g_query.is_at(g_depth)) {
Nigel Tao0cd2f982020-03-03 23:03:02 +1100851 return "main: no match for query";
852 }
Nigel Taod60815c2020-03-26 14:32:35 +1100853 if (g_depth <= 0) {
854 return "main: internal error: inconsistent g_depth";
Nigel Tao1b073492020-02-16 22:11:36 +1100855 }
Nigel Taod60815c2020-03-26 14:32:35 +1100856 g_depth--;
Nigel Tao1b073492020-02-16 22:11:36 +1100857
Nigel Taod60815c2020-03-26 14:32:35 +1100858 if (g_query.matched_all() && (g_depth >= g_flags.max_output_depth)) {
859 g_suppress_write_dst--;
Nigel Tao52c4d6a2020-03-08 21:12:38 +1100860 // '…' is U+2026 HORIZONTAL ELLIPSIS, which is 3 UTF-8 bytes.
861 TRY(write_dst((vbd & WUFFS_BASE__TOKEN__VBD__STRUCTURE__FROM_LIST)
862 ? "\"[…]\""
863 : "\"{…}\"",
864 7));
865 } else {
866 // Write preceding whitespace.
Nigel Taod60815c2020-03-26 14:32:35 +1100867 if ((g_ctx != context::in_list_after_bracket) &&
868 (g_ctx != context::in_dict_after_brace) &&
869 !g_flags.compact_output) {
Nigel Tao52c4d6a2020-03-08 21:12:38 +1100870 TRY(write_dst("\n", 1));
Nigel Taod60815c2020-03-26 14:32:35 +1100871 for (uint32_t i = 0; i < g_depth; i++) {
872 TRY(write_dst(
873 g_flags.tabs ? INDENT_TAB_STRING : INDENT_SPACES_STRING,
874 g_flags.tabs ? 1 : g_flags.indent));
Nigel Tao52c4d6a2020-03-08 21:12:38 +1100875 }
Nigel Tao1b073492020-02-16 22:11:36 +1100876 }
Nigel Tao52c4d6a2020-03-08 21:12:38 +1100877
878 TRY(write_dst(
879 (vbd & WUFFS_BASE__TOKEN__VBD__STRUCTURE__FROM_LIST) ? "]" : "}",
880 1));
Nigel Tao1b073492020-02-16 22:11:36 +1100881 }
882
Nigel Taod60815c2020-03-26 14:32:35 +1100883 g_ctx = (vbd & WUFFS_BASE__TOKEN__VBD__STRUCTURE__TO_LIST)
884 ? context::in_list_after_value
885 : context::in_dict_after_key;
Nigel Tao1b073492020-02-16 22:11:36 +1100886 goto after_value;
887 }
888
Nigel Taod1c928a2020-02-28 12:43:53 +1100889 // Write preceding whitespace and punctuation, if it wasn't ']', '}' or a
890 // continuation of a multi-token chain.
Nigel Tao0cd2f982020-03-03 23:03:02 +1100891 if (!t.link_prev()) {
Nigel Taod60815c2020-03-26 14:32:35 +1100892 if (g_ctx == context::in_dict_after_key) {
893 TRY(write_dst(": ", g_flags.compact_output ? 1 : 2));
894 } else if (g_ctx != context::none) {
895 if ((g_ctx != context::in_list_after_bracket) &&
896 (g_ctx != context::in_dict_after_brace)) {
Nigel Tao0cd2f982020-03-03 23:03:02 +1100897 TRY(write_dst(",", 1));
Nigel Tao107f0ef2020-03-01 21:35:02 +1100898 }
Nigel Taod60815c2020-03-26 14:32:35 +1100899 if (!g_flags.compact_output) {
Nigel Tao0cd2f982020-03-03 23:03:02 +1100900 TRY(write_dst("\n", 1));
Nigel Taod60815c2020-03-26 14:32:35 +1100901 for (size_t i = 0; i < g_depth; i++) {
902 TRY(write_dst(
903 g_flags.tabs ? INDENT_TAB_STRING : INDENT_SPACES_STRING,
904 g_flags.tabs ? 1 : g_flags.indent));
Nigel Tao0cd2f982020-03-03 23:03:02 +1100905 }
906 }
907 }
908
Nigel Tao52c4d6a2020-03-08 21:12:38 +1100909 bool query_matched_fragment = false;
Nigel Taod60815c2020-03-26 14:32:35 +1100910 if (g_query.is_at(g_depth)) {
911 switch (g_ctx) {
Nigel Tao0cd2f982020-03-03 23:03:02 +1100912 case context::in_list_after_bracket:
913 case context::in_list_after_value:
Nigel Taod60815c2020-03-26 14:32:35 +1100914 query_matched_fragment = g_query.tick();
Nigel Tao0cd2f982020-03-03 23:03:02 +1100915 break;
916 case context::in_dict_after_key:
Nigel Taod60815c2020-03-26 14:32:35 +1100917 query_matched_fragment = g_query.matched_fragment();
Nigel Tao0cd2f982020-03-03 23:03:02 +1100918 break;
Nigel Tao18ef5b42020-03-16 10:37:47 +1100919 default:
920 break;
Nigel Tao0cd2f982020-03-03 23:03:02 +1100921 }
922 }
Nigel Tao52c4d6a2020-03-08 21:12:38 +1100923 if (!query_matched_fragment) {
Nigel Tao0cd2f982020-03-03 23:03:02 +1100924 // No-op.
Nigel Taod60815c2020-03-26 14:32:35 +1100925 } else if (!g_query.next_fragment()) {
Nigel Tao0cd2f982020-03-03 23:03:02 +1100926 // There is no next fragment. We have matched the complete query, and
927 // the upcoming JSON value is the result of that query.
928 //
Nigel Taod60815c2020-03-26 14:32:35 +1100929 // Un-suppress writing to stdout and reset the g_ctx and g_depth as if
930 // we were about to decode a top-level value. This makes any subsequent
931 // indentation be relative to this point, and we will return g_eod
932 // after the upcoming JSON value is complete.
933 if (g_suppress_write_dst != 1) {
934 return "main: internal error: inconsistent g_suppress_write_dst";
Nigel Tao52c4d6a2020-03-08 21:12:38 +1100935 }
Nigel Taod60815c2020-03-26 14:32:35 +1100936 g_suppress_write_dst = 0;
937 g_ctx = context::none;
938 g_depth = 0;
Nigel Tao0cd2f982020-03-03 23:03:02 +1100939 } else if ((vbc != WUFFS_BASE__TOKEN__VBC__STRUCTURE) ||
940 !(vbd & WUFFS_BASE__TOKEN__VBD__STRUCTURE__PUSH)) {
941 // The query has moved on to the next fragment but the upcoming JSON
942 // value is not a container.
943 return "main: no match for query";
Nigel Tao1b073492020-02-16 22:11:36 +1100944 }
945 }
946
947 // Handle the token itself: either a container ('[' or '{') or a simple
Nigel Tao85fba7f2020-02-29 16:28:06 +1100948 // value: string (a chain of raw or escaped parts), literal or number.
Nigel Tao1b073492020-02-16 22:11:36 +1100949 switch (vbc) {
Nigel Tao85fba7f2020-02-29 16:28:06 +1100950 case WUFFS_BASE__TOKEN__VBC__STRUCTURE:
Nigel Taod60815c2020-03-26 14:32:35 +1100951 if (g_query.matched_all() && (g_depth >= g_flags.max_output_depth)) {
952 g_suppress_write_dst++;
Nigel Tao52c4d6a2020-03-08 21:12:38 +1100953 } else {
954 TRY(write_dst(
955 (vbd & WUFFS_BASE__TOKEN__VBD__STRUCTURE__TO_LIST) ? "[" : "{",
956 1));
957 }
Nigel Taod60815c2020-03-26 14:32:35 +1100958 g_depth++;
959 g_ctx = (vbd & WUFFS_BASE__TOKEN__VBD__STRUCTURE__TO_LIST)
960 ? context::in_list_after_bracket
961 : context::in_dict_after_brace;
Nigel Tao85fba7f2020-02-29 16:28:06 +1100962 return nullptr;
963
Nigel Tao2cf76db2020-02-27 22:42:01 +1100964 case WUFFS_BASE__TOKEN__VBC__STRING:
Nigel Taod1c928a2020-02-28 12:43:53 +1100965 if (!t.link_prev()) {
Nigel Tao2cf76db2020-02-27 22:42:01 +1100966 TRY(write_dst("\"", 1));
Nigel Taod60815c2020-03-26 14:32:35 +1100967 g_query.restart_fragment(in_dict_before_key() &&
968 g_query.is_at(g_depth));
Nigel Tao2cf76db2020-02-27 22:42:01 +1100969 }
Nigel Taocb37a562020-02-28 09:56:24 +1100970
971 if (vbd & WUFFS_BASE__TOKEN__VBD__STRING__CONVERT_0_DST_1_SRC_DROP) {
972 // No-op.
973 } else if (vbd &
974 WUFFS_BASE__TOKEN__VBD__STRING__CONVERT_1_DST_1_SRC_COPY) {
Nigel Taod60815c2020-03-26 14:32:35 +1100975 uint8_t* ptr = g_src.data.ptr + g_curr_token_end_src_index - len;
Nigel Tao0cd2f982020-03-03 23:03:02 +1100976 TRY(write_dst(ptr, len));
Nigel Taod60815c2020-03-26 14:32:35 +1100977 g_query.incremental_match_slice(ptr, len);
Nigel Taocb37a562020-02-28 09:56:24 +1100978 } else {
979 return "main: internal error: unexpected string-token conversion";
980 }
981
Nigel Taod1c928a2020-02-28 12:43:53 +1100982 if (t.link_next()) {
Nigel Tao2cf76db2020-02-27 22:42:01 +1100983 return nullptr;
984 }
985 TRY(write_dst("\"", 1));
986 goto after_value;
987
988 case WUFFS_BASE__TOKEN__VBC__UNICODE_CODE_POINT:
Nigel Tao0cd2f982020-03-03 23:03:02 +1100989 if (!t.link_prev() || !t.link_next()) {
990 return "main: internal error: unexpected unlinked token";
991 }
992 TRY(handle_unicode_code_point(vbd));
Nigel Taod60815c2020-03-26 14:32:35 +1100993 g_query.incremental_match_code_point(vbd);
Nigel Tao0cd2f982020-03-03 23:03:02 +1100994 return nullptr;
Nigel Tao2cf76db2020-02-27 22:42:01 +1100995
Nigel Tao85fba7f2020-02-29 16:28:06 +1100996 case WUFFS_BASE__TOKEN__VBC__LITERAL:
Nigel Tao2cf76db2020-02-27 22:42:01 +1100997 case WUFFS_BASE__TOKEN__VBC__NUMBER:
Nigel Taod60815c2020-03-26 14:32:35 +1100998 TRY(write_dst(g_src.data.ptr + g_curr_token_end_src_index - len, len));
Nigel Tao2cf76db2020-02-27 22:42:01 +1100999 goto after_value;
Nigel Tao1b073492020-02-16 22:11:36 +11001000 }
1001
1002 // Return an error if we didn't match the (vbc, vbd) pair.
Nigel Tao2cf76db2020-02-27 22:42:01 +11001003 return "main: internal error: unexpected token";
1004 } while (0);
Nigel Tao1b073492020-02-16 22:11:36 +11001005
Nigel Tao2cf76db2020-02-27 22:42:01 +11001006 // Book-keeping after completing a value (whether a container value or a
1007 // simple value). Empty parent containers are no longer empty. If the parent
1008 // container is a "{...}" object, toggle between keys and values.
1009after_value:
Nigel Taod60815c2020-03-26 14:32:35 +11001010 if (g_depth == 0) {
1011 return g_eod;
Nigel Tao2cf76db2020-02-27 22:42:01 +11001012 }
Nigel Taod60815c2020-03-26 14:32:35 +11001013 switch (g_ctx) {
Nigel Tao2cf76db2020-02-27 22:42:01 +11001014 case context::in_list_after_bracket:
Nigel Taod60815c2020-03-26 14:32:35 +11001015 g_ctx = context::in_list_after_value;
Nigel Tao2cf76db2020-02-27 22:42:01 +11001016 break;
1017 case context::in_dict_after_brace:
Nigel Taod60815c2020-03-26 14:32:35 +11001018 g_ctx = context::in_dict_after_key;
Nigel Tao2cf76db2020-02-27 22:42:01 +11001019 break;
1020 case context::in_dict_after_key:
Nigel Taod60815c2020-03-26 14:32:35 +11001021 g_ctx = context::in_dict_after_value;
Nigel Tao2cf76db2020-02-27 22:42:01 +11001022 break;
1023 case context::in_dict_after_value:
Nigel Taod60815c2020-03-26 14:32:35 +11001024 g_ctx = context::in_dict_after_key;
Nigel Tao2cf76db2020-02-27 22:42:01 +11001025 break;
Nigel Tao18ef5b42020-03-16 10:37:47 +11001026 default:
1027 break;
Nigel Tao2cf76db2020-02-27 22:42:01 +11001028 }
1029 return nullptr;
1030}
1031
1032const char* //
1033main1(int argc, char** argv) {
1034 TRY(initialize_globals(argc, argv));
1035
1036 while (true) {
Nigel Taod60815c2020-03-26 14:32:35 +11001037 wuffs_base__status status = g_dec.decode_tokens(
1038 &g_tok, &g_src,
1039 wuffs_base__make_slice_u8(g_work_buffer_array, WORK_BUFFER_ARRAY_SIZE));
Nigel Tao2cf76db2020-02-27 22:42:01 +11001040
Nigel Taod60815c2020-03-26 14:32:35 +11001041 while (g_tok.meta.ri < g_tok.meta.wi) {
1042 wuffs_base__token t = g_tok.data.ptr[g_tok.meta.ri++];
Nigel Tao2cf76db2020-02-27 22:42:01 +11001043 uint64_t n = t.length();
Nigel Taod60815c2020-03-26 14:32:35 +11001044 if ((g_src.meta.ri - g_curr_token_end_src_index) < n) {
1045 return "main: internal error: inconsistent g_src indexes";
Nigel Tao2cf76db2020-02-27 22:42:01 +11001046 }
Nigel Taod60815c2020-03-26 14:32:35 +11001047 g_curr_token_end_src_index += n;
Nigel Tao2cf76db2020-02-27 22:42:01 +11001048
Nigel Taod0b16cb2020-03-14 10:15:54 +11001049 // Skip filler tokens (e.g. whitespace).
Nigel Tao2cf76db2020-02-27 22:42:01 +11001050 if (t.value() == 0) {
1051 continue;
1052 }
1053
1054 const char* z = handle_token(t);
1055 if (z == nullptr) {
1056 continue;
Nigel Taod60815c2020-03-26 14:32:35 +11001057 } else if (z == g_eod) {
Nigel Tao0cd2f982020-03-03 23:03:02 +11001058 goto end_of_data;
Nigel Tao2cf76db2020-02-27 22:42:01 +11001059 }
1060 return z;
Nigel Tao1b073492020-02-16 22:11:36 +11001061 }
Nigel Tao2cf76db2020-02-27 22:42:01 +11001062
1063 if (status.repr == nullptr) {
Nigel Tao0cd2f982020-03-03 23:03:02 +11001064 return "main: internal error: unexpected end of token stream";
Nigel Tao2cf76db2020-02-27 22:42:01 +11001065 } else if (status.repr == wuffs_base__suspension__short_read) {
Nigel Taod60815c2020-03-26 14:32:35 +11001066 if (g_curr_token_end_src_index != g_src.meta.ri) {
1067 return "main: internal error: inconsistent g_src indexes";
Nigel Tao2cf76db2020-02-27 22:42:01 +11001068 }
1069 TRY(read_src());
Nigel Taod60815c2020-03-26 14:32:35 +11001070 g_curr_token_end_src_index = g_src.meta.ri;
Nigel Tao2cf76db2020-02-27 22:42:01 +11001071 } else if (status.repr == wuffs_base__suspension__short_write) {
Nigel Taod60815c2020-03-26 14:32:35 +11001072 g_tok.compact();
Nigel Tao2cf76db2020-02-27 22:42:01 +11001073 } else {
1074 return status.message();
Nigel Tao1b073492020-02-16 22:11:36 +11001075 }
1076 }
Nigel Tao0cd2f982020-03-03 23:03:02 +11001077end_of_data:
1078
Nigel Taod60815c2020-03-26 14:32:35 +11001079 // With a non-empty g_query, don't try to consume trailing whitespace or
Nigel Tao0cd2f982020-03-03 23:03:02 +11001080 // confirm that we've processed all the tokens.
Nigel Taod60815c2020-03-26 14:32:35 +11001081 if (g_flags.query_c_string && *g_flags.query_c_string) {
Nigel Tao0cd2f982020-03-03 23:03:02 +11001082 return nullptr;
1083 }
Nigel Tao6b161af2020-02-24 11:01:48 +11001084
Nigel Tao6b161af2020-02-24 11:01:48 +11001085 // Check that we've exhausted the input.
Nigel Taod60815c2020-03-26 14:32:35 +11001086 if ((g_src.meta.ri == g_src.meta.wi) && !g_src.meta.closed) {
Nigel Taofe0cbbd2020-03-05 22:01:30 +11001087 TRY(read_src());
1088 }
Nigel Taod60815c2020-03-26 14:32:35 +11001089 if ((g_src.meta.ri < g_src.meta.wi) || !g_src.meta.closed) {
Nigel Tao6b161af2020-02-24 11:01:48 +11001090 return "main: valid JSON followed by further (unexpected) data";
1091 }
1092
1093 // Check that we've used all of the decoded tokens, other than trailing
Nigel Tao4b186b02020-03-18 14:25:21 +11001094 // filler tokens. For example, "true\n" is valid JSON (and fully consumed
1095 // with WUFFS_JSON__QUIRK_ALLOW_TRAILING_NEW_LINE enabled) with a trailing
1096 // filler token for the "\n".
Nigel Taod60815c2020-03-26 14:32:35 +11001097 for (; g_tok.meta.ri < g_tok.meta.wi; g_tok.meta.ri++) {
1098 if (g_tok.data.ptr[g_tok.meta.ri].value_base_category() !=
Nigel Tao6b161af2020-02-24 11:01:48 +11001099 WUFFS_BASE__TOKEN__VBC__FILLER) {
1100 return "main: internal error: decoded OK but unprocessed tokens remain";
1101 }
1102 }
1103
1104 return nullptr;
Nigel Tao1b073492020-02-16 22:11:36 +11001105}
1106
Nigel Tao2914bae2020-02-26 09:40:30 +11001107int //
1108compute_exit_code(const char* status_msg) {
Nigel Tao9cc2c252020-02-23 17:05:49 +11001109 if (!status_msg) {
1110 return 0;
1111 }
Nigel Tao01abc842020-03-06 21:42:33 +11001112 size_t n;
Nigel Taod60815c2020-03-26 14:32:35 +11001113 if (status_msg == g_usage) {
Nigel Tao01abc842020-03-06 21:42:33 +11001114 n = strlen(status_msg);
1115 } else {
Nigel Tao9cc2c252020-02-23 17:05:49 +11001116 n = strnlen(status_msg, 2047);
Nigel Tao01abc842020-03-06 21:42:33 +11001117 if (n >= 2047) {
1118 status_msg = "main: internal error: error message is too long";
1119 n = strnlen(status_msg, 2047);
1120 }
Nigel Tao9cc2c252020-02-23 17:05:49 +11001121 }
Nigel Taofe0cbbd2020-03-05 22:01:30 +11001122 const int stderr_fd = 2;
1123 ignore_return_value(write(stderr_fd, status_msg, n));
1124 ignore_return_value(write(stderr_fd, "\n", 1));
Nigel Tao9cc2c252020-02-23 17:05:49 +11001125 // Return an exit code of 1 for regular (forseen) errors, e.g. badly
1126 // formatted or unsupported input.
1127 //
1128 // Return an exit code of 2 for internal (exceptional) errors, e.g. defensive
1129 // run-time checks found that an internal invariant did not hold.
1130 //
1131 // Automated testing, including badly formatted inputs, can therefore
1132 // discriminate between expected failure (exit code 1) and unexpected failure
1133 // (other non-zero exit codes). Specifically, exit code 2 for internal
1134 // invariant violation, exit code 139 (which is 128 + SIGSEGV on x86_64
1135 // linux) for a segmentation fault (e.g. null pointer dereference).
1136 return strstr(status_msg, "internal error:") ? 2 : 1;
1137}
1138
Nigel Tao2914bae2020-02-26 09:40:30 +11001139int //
1140main(int argc, char** argv) {
Nigel Tao01abc842020-03-06 21:42:33 +11001141 // Look for an input filename (the first non-flag argument) in argv. If there
1142 // is one, open it (but do not read from it) before we self-impose a sandbox.
1143 //
1144 // Flags start with "-", unless it comes after a bare "--" arg.
1145 {
1146 bool dash_dash = false;
1147 int a;
1148 for (a = 1; a < argc; a++) {
1149 char* arg = argv[a];
1150 if ((arg[0] == '-') && !dash_dash) {
1151 dash_dash = (arg[1] == '-') && (arg[2] == '\x00');
1152 continue;
1153 }
Nigel Taod60815c2020-03-26 14:32:35 +11001154 g_input_file_descriptor = open(arg, O_RDONLY);
1155 if (g_input_file_descriptor < 0) {
Nigel Tao01abc842020-03-06 21:42:33 +11001156 fprintf(stderr, "%s: %s\n", arg, strerror(errno));
1157 return 1;
1158 }
1159 break;
1160 }
1161 }
1162
Nigel Taofe0cbbd2020-03-05 22:01:30 +11001163#if defined(WUFFS_EXAMPLE_USE_SECCOMP)
1164 prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);
Nigel Taod60815c2020-03-26 14:32:35 +11001165 g_sandboxed = true;
Nigel Taofe0cbbd2020-03-05 22:01:30 +11001166#endif
1167
Nigel Tao0cd2f982020-03-03 23:03:02 +11001168 const char* z = main1(argc, argv);
Nigel Taod60815c2020-03-26 14:32:35 +11001169 if (g_wrote_to_dst) {
Nigel Tao0cd2f982020-03-03 23:03:02 +11001170 const char* z1 = write_dst("\n", 1);
1171 const char* z2 = flush_dst();
1172 z = z ? z : (z1 ? z1 : z2);
1173 }
1174 int exit_code = compute_exit_code(z);
Nigel Taofe0cbbd2020-03-05 22:01:30 +11001175
1176#if defined(WUFFS_EXAMPLE_USE_SECCOMP)
1177 // Call SYS_exit explicitly, instead of calling SYS_exit_group implicitly by
1178 // either calling _exit or returning from main. SECCOMP_MODE_STRICT allows
1179 // only SYS_exit.
1180 syscall(SYS_exit, exit_code);
1181#endif
Nigel Tao9cc2c252020-02-23 17:05:49 +11001182 return exit_code;
Nigel Tao1b073492020-02-16 22:11:36 +11001183}