1 /*
2 * nghttp2 - HTTP/2 C Library
3 *
4 * Copyright (c) 2013 Tatsuhiro Tsujikawa
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining
7 * a copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, sublicense, and/or sell copies of the Software, and to
11 * permit persons to whom the Software is furnished to do so, subject to
12 * the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be
15 * included in all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 */
25 #ifdef HAVE_CONFIG_H
26 # include <config.h>
27 #endif // HAVE_CONFIG_H
28
29 #ifdef HAVE_UNISTD_H
30 # include <unistd.h>
31 #endif // HAVE_UNISTD_H
32 #include <getopt.h>
33
34 #include <cstdio>
35 #include <cstring>
36 #include <assert.h>
37 #include <cerrno>
38 #include <cstdlib>
39 #include <vector>
40 #include <iostream>
41
42 #include <jansson.h>
43
44 #define NGHTTP2_NO_SSIZE_T
45 #include <nghttp2/nghttp2.h>
46
47 #include "template.h"
48 #include "comp_helper.h"
49
50 namespace nghttp2 {
51
52 typedef struct {
53 int dump_header_table;
54 } inflate_config;
55
56 static inflate_config config;
57
to_ud(char c)58 static uint8_t to_ud(char c) {
59 if (c >= 'A' && c <= 'Z') {
60 return c - 'A' + 10;
61 } else if (c >= 'a' && c <= 'z') {
62 return c - 'a' + 10;
63 } else {
64 return c - '0';
65 }
66 }
67
decode_hex(uint8_t * dest,const char * src,size_t len)68 static void decode_hex(uint8_t *dest, const char *src, size_t len) {
69 size_t i;
70 for (i = 0; i < len; i += 2) {
71 *dest++ = to_ud(src[i]) << 4 | to_ud(src[i + 1]);
72 }
73 }
74
to_json(nghttp2_hd_inflater * inflater,json_t * headers,json_t * wire,int seq,size_t old_settings_table_size)75 static void to_json(nghttp2_hd_inflater *inflater, json_t *headers,
76 json_t *wire, int seq, size_t old_settings_table_size) {
77 auto obj = json_object();
78 json_object_set_new(obj, "seq", json_integer(seq));
79 json_object_set(obj, "wire", wire);
80 json_object_set(obj, "headers", headers);
81 auto max_dyn_table_size =
82 nghttp2_hd_inflate_get_max_dynamic_table_size(inflater);
83 if (old_settings_table_size != max_dyn_table_size) {
84 json_object_set_new(obj, "header_table_size",
85 json_integer(max_dyn_table_size));
86 }
87 if (config.dump_header_table) {
88 json_object_set_new(obj, "header_table",
89 dump_inflate_header_table(inflater));
90 }
91 json_dumpf(obj, stdout, JSON_INDENT(2) | JSON_PRESERVE_ORDER);
92 json_decref(obj);
93 printf("\n");
94 }
95
inflate_hd(json_t * obj,nghttp2_hd_inflater * inflater,int seq)96 static int inflate_hd(json_t *obj, nghttp2_hd_inflater *inflater, int seq) {
97 nghttp2_nv nv;
98 int inflate_flags;
99 size_t old_settings_table_size =
100 nghttp2_hd_inflate_get_max_dynamic_table_size(inflater);
101
102 auto wire = json_object_get(obj, "wire");
103
104 if (wire == nullptr) {
105 fprintf(stderr, "'wire' key is missing at %d\n", seq);
106 return -1;
107 }
108
109 if (!json_is_string(wire)) {
110 fprintf(stderr, "'wire' value is not string at %d\n", seq);
111 return -1;
112 }
113
114 auto table_size = json_object_get(obj, "header_table_size");
115
116 if (table_size) {
117 if (!json_is_integer(table_size)) {
118 fprintf(stderr,
119 "The value of 'header_table_size key' is not integer at %d\n",
120 seq);
121 return -1;
122 }
123 auto rv = nghttp2_hd_inflate_change_table_size(
124 inflater, json_integer_value(table_size));
125 if (rv != 0) {
126 fprintf(stderr,
127 "nghttp2_hd_change_table_size() failed with error %s at %d\n",
128 nghttp2_strerror(rv), seq);
129 return -1;
130 }
131 }
132
133 auto inputlen = strlen(json_string_value(wire));
134
135 if (inputlen & 1) {
136 fprintf(stderr, "Badly formatted output value at %d\n", seq);
137 exit(EXIT_FAILURE);
138 }
139
140 auto buflen = inputlen / 2;
141 auto buf = std::vector<uint8_t>(buflen);
142
143 decode_hex(buf.data(), json_string_value(wire), inputlen);
144
145 auto headers = json_array();
146
147 auto p = buf.data();
148 for (;;) {
149 inflate_flags = 0;
150 auto rv =
151 nghttp2_hd_inflate_hd3(inflater, &nv, &inflate_flags, p, buflen, 1);
152 if (rv < 0) {
153 fprintf(stderr, "inflate failed with error code %zd at %d\n", rv, seq);
154 exit(EXIT_FAILURE);
155 }
156 p += rv;
157 buflen -= rv;
158 if (inflate_flags & NGHTTP2_HD_INFLATE_EMIT) {
159 json_array_append_new(
160 headers, dump_header(nv.name, nv.namelen, nv.value, nv.valuelen));
161 }
162 if (inflate_flags & NGHTTP2_HD_INFLATE_FINAL) {
163 break;
164 }
165 }
166 assert(buflen == 0);
167 nghttp2_hd_inflate_end_headers(inflater);
168 to_json(inflater, headers, wire, seq, old_settings_table_size);
169 json_decref(headers);
170
171 return 0;
172 }
173
perform(void)174 static int perform(void) {
175 nghttp2_hd_inflater *inflater = nullptr;
176 json_error_t error;
177
178 auto json = json_loadf(stdin, 0, &error);
179
180 if (json == nullptr) {
181 fprintf(stderr, "JSON loading failed\n");
182 exit(EXIT_FAILURE);
183 }
184
185 auto cases = json_object_get(json, "cases");
186
187 if (cases == nullptr) {
188 fprintf(stderr, "Missing 'cases' key in root object\n");
189 exit(EXIT_FAILURE);
190 }
191
192 if (!json_is_array(cases)) {
193 fprintf(stderr, "'cases' must be JSON array\n");
194 exit(EXIT_FAILURE);
195 }
196
197 nghttp2_hd_inflate_new(&inflater);
198 output_json_header();
199 auto len = json_array_size(cases);
200
201 for (size_t i = 0; i < len; ++i) {
202 auto obj = json_array_get(cases, i);
203 if (!json_is_object(obj)) {
204 fprintf(stderr, "Unexpected JSON type at %zu. It should be object.\n", i);
205 continue;
206 }
207 if (inflate_hd(obj, inflater, i) != 0) {
208 continue;
209 }
210 if (i + 1 < len) {
211 printf(",\n");
212 }
213 }
214 output_json_footer();
215 nghttp2_hd_inflate_del(inflater);
216 json_decref(json);
217
218 return 0;
219 }
220
print_help(void)221 static void print_help(void) {
222 std::cout << R"(HPACK HTTP/2 header decoder
223 Usage: inflatehd [OPTIONS] < INPUT
224
225 Reads JSON data from stdin and outputs inflated name/value pairs in
226 JSON.
227
228 The root JSON object must contain "context" key, which indicates which
229 compression context is used. If it is "request", request compression
230 context is used. Otherwise, response compression context is used.
231 The value of "cases" key contains the sequence of compressed header
232 block. They share the same compression context and are processed in
233 the order they appear. Each item in the sequence is a JSON object and
234 it must have at least "wire" key. Its value is a string containing
235 compressed header block in hex string.
236
237 Example:
238
239 {
240 "context": "request",
241 "cases":
242 [
243 { "wire": "0284f77778ff" },
244 { "wire": "0185fafd3c3c7f81" }
245 ]
246 }
247
248 The output of this program can be used as input for deflatehd.
249
250 OPTIONS:
251 -d, --dump-header-table
252 Output dynamic header table.)"
253 << std::endl;
254 ;
255 }
256
257 constexpr static struct option long_options[] = {
258 {"dump-header-table", no_argument, nullptr, 'd'}, {nullptr, 0, nullptr, 0}};
259
main(int argc,char ** argv)260 int main(int argc, char **argv) {
261 config.dump_header_table = 0;
262 while (1) {
263 int option_index = 0;
264 int c = getopt_long(argc, argv, "dh", long_options, &option_index);
265 if (c == -1) {
266 break;
267 }
268 switch (c) {
269 case 'h':
270 print_help();
271 exit(EXIT_SUCCESS);
272 case 'd':
273 // --dump-header-table
274 config.dump_header_table = 1;
275 break;
276 case '?':
277 exit(EXIT_FAILURE);
278 default:
279 break;
280 }
281 }
282 perform();
283 return 0;
284 }
285
286 } // namespace nghttp2
287
main(int argc,char ** argv)288 int main(int argc, char **argv) {
289 return nghttp2::run_app(nghttp2::main, argc, argv);
290 }
291