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 #include <nghttp2/nghttp2.h>
45
46 #include "template.h"
47 #include "comp_helper.h"
48
49 namespace nghttp2 {
50
51 typedef struct {
52 int dump_header_table;
53 } inflate_config;
54
55 static inflate_config config;
56
to_ud(char c)57 static uint8_t to_ud(char c) {
58 if (c >= 'A' && c <= 'Z') {
59 return c - 'A' + 10;
60 } else if (c >= 'a' && c <= 'z') {
61 return c - 'a' + 10;
62 } else {
63 return c - '0';
64 }
65 }
66
decode_hex(uint8_t * dest,const char * src,size_t len)67 static void decode_hex(uint8_t *dest, const char *src, size_t len) {
68 size_t i;
69 for (i = 0; i < len; i += 2) {
70 *dest++ = to_ud(src[i]) << 4 | to_ud(src[i + 1]);
71 }
72 }
73
to_json(nghttp2_hd_inflater * inflater,json_t * headers,json_t * wire,int seq,size_t old_settings_table_size)74 static void to_json(nghttp2_hd_inflater *inflater, json_t *headers,
75 json_t *wire, int seq, size_t old_settings_table_size) {
76 auto obj = json_object();
77 json_object_set_new(obj, "seq", json_integer(seq));
78 json_object_set(obj, "wire", wire);
79 json_object_set(obj, "headers", headers);
80 auto max_dyn_table_size =
81 nghttp2_hd_inflate_get_max_dynamic_table_size(inflater);
82 if (old_settings_table_size != max_dyn_table_size) {
83 json_object_set_new(obj, "header_table_size",
84 json_integer(max_dyn_table_size));
85 }
86 if (config.dump_header_table) {
87 json_object_set_new(obj, "header_table",
88 dump_inflate_header_table(inflater));
89 }
90 json_dumpf(obj, stdout, JSON_INDENT(2) | JSON_PRESERVE_ORDER);
91 json_decref(obj);
92 printf("\n");
93 }
94
inflate_hd(json_t * obj,nghttp2_hd_inflater * inflater,int seq)95 static int inflate_hd(json_t *obj, nghttp2_hd_inflater *inflater, int seq) {
96 ssize_t rv;
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 rv = nghttp2_hd_inflate_change_table_size(inflater,
124 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 rv = nghttp2_hd_inflate_hd(inflater, &nv, &inflate_flags, p, buflen, 1);
151 if (rv < 0) {
152 fprintf(stderr, "inflate failed with error code %zd at %d\n", rv, seq);
153 exit(EXIT_FAILURE);
154 }
155 p += rv;
156 buflen -= rv;
157 if (inflate_flags & NGHTTP2_HD_INFLATE_EMIT) {
158 json_array_append_new(
159 headers, dump_header(nv.name, nv.namelen, nv.value, nv.valuelen));
160 }
161 if (inflate_flags & NGHTTP2_HD_INFLATE_FINAL) {
162 break;
163 }
164 }
165 assert(buflen == 0);
166 nghttp2_hd_inflate_end_headers(inflater);
167 to_json(inflater, headers, wire, seq, old_settings_table_size);
168 json_decref(headers);
169
170 return 0;
171 }
172
perform(void)173 static int perform(void) {
174 nghttp2_hd_inflater *inflater = NULL;
175 json_error_t error;
176
177 auto json = json_loadf(stdin, 0, &error);
178
179 if (json == nullptr) {
180 fprintf(stderr, "JSON loading failed\n");
181 exit(EXIT_FAILURE);
182 }
183
184 auto cases = json_object_get(json, "cases");
185
186 if (cases == nullptr) {
187 fprintf(stderr, "Missing 'cases' key in root object\n");
188 exit(EXIT_FAILURE);
189 }
190
191 if (!json_is_array(cases)) {
192 fprintf(stderr, "'cases' must be JSON array\n");
193 exit(EXIT_FAILURE);
194 }
195
196 nghttp2_hd_inflate_new(&inflater);
197 output_json_header();
198 auto len = json_array_size(cases);
199
200 for (size_t i = 0; i < len; ++i) {
201 auto obj = json_array_get(cases, i);
202 if (!json_is_object(obj)) {
203 fprintf(stderr, "Unexpected JSON type at %zu. It should be object.\n", i);
204 continue;
205 }
206 if (inflate_hd(obj, inflater, i) != 0) {
207 continue;
208 }
209 if (i + 1 < len) {
210 printf(",\n");
211 }
212 }
213 output_json_footer();
214 nghttp2_hd_inflate_del(inflater);
215 json_decref(json);
216
217 return 0;
218 }
219
print_help(void)220 static void print_help(void) {
221 std::cout << R"(HPACK HTTP/2 header decoder
222 Usage: inflatehd [OPTIONS] < INPUT
223
224 Reads JSON data from stdin and outputs inflated name/value pairs in
225 JSON.
226
227 The root JSON object must contain "context" key, which indicates which
228 compression context is used. If it is "request", request compression
229 context is used. Otherwise, response compression context is used.
230 The value of "cases" key contains the sequence of compressed header
231 block. They share the same compression context and are processed in
232 the order they appear. Each item in the sequence is a JSON object and
233 it must have at least "wire" key. Its value is a string containing
234 compressed header block in hex string.
235
236 Example:
237
238 {
239 "context": "request",
240 "cases":
241 [
242 { "wire": "0284f77778ff" },
243 { "wire": "0185fafd3c3c7f81" }
244 ]
245 }
246
247 The output of this program can be used as input for deflatehd.
248
249 OPTIONS:
250 -d, --dump-header-table
251 Output dynamic header table.)"
252 << std::endl;
253 ;
254 }
255
256 constexpr static struct option long_options[] = {
257 {"dump-header-table", no_argument, nullptr, 'd'}, {nullptr, 0, nullptr, 0}};
258
main(int argc,char ** argv)259 int main(int argc, char **argv) {
260 config.dump_header_table = 0;
261 while (1) {
262 int option_index = 0;
263 int c = getopt_long(argc, argv, "dh", long_options, &option_index);
264 if (c == -1) {
265 break;
266 }
267 switch (c) {
268 case 'h':
269 print_help();
270 exit(EXIT_SUCCESS);
271 case 'd':
272 // --dump-header-table
273 config.dump_header_table = 1;
274 break;
275 case '?':
276 exit(EXIT_FAILURE);
277 default:
278 break;
279 }
280 }
281 perform();
282 return 0;
283 }
284
285 } // namespace nghttp2
286
main(int argc,char ** argv)287 int main(int argc, char **argv) {
288 return nghttp2::run_app(nghttp2::main, argc, argv);
289 }
290