Imported Upstream version 1.0.0
[platform/upstream/nghttp2.git] / src / inflatehd.cc
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 extern "C" {
45
46 #include "nghttp2_hd.h"
47 #include "nghttp2_frame.h"
48
49 #include "comp_helper.h"
50 }
51
52 typedef struct { int dump_header_table; } inflate_config;
53
54 static inflate_config config;
55
56 static uint8_t to_ud(char c) {
57   if (c >= 'A' && c <= 'Z') {
58     return c - 'A' + 10;
59   } else if (c >= 'a' && c <= 'z') {
60     return c - 'a' + 10;
61   } else {
62     return c - '0';
63   }
64 }
65
66 static void decode_hex(uint8_t *dest, const char *src, size_t len) {
67   size_t i;
68   for (i = 0; i < len; i += 2) {
69     *dest++ = to_ud(src[i]) << 4 | to_ud(src[i + 1]);
70   }
71 }
72
73 static void to_json(nghttp2_hd_inflater *inflater, json_t *headers,
74                     json_t *wire, int seq, size_t old_settings_table_size) {
75   auto obj = json_object();
76   json_object_set_new(obj, "seq", json_integer(seq));
77   json_object_set(obj, "wire", wire);
78   json_object_set(obj, "headers", headers);
79   if (old_settings_table_size != inflater->settings_hd_table_bufsize_max) {
80     json_object_set_new(obj, "header_table_size",
81                         json_integer(inflater->settings_hd_table_bufsize_max));
82   }
83   if (config.dump_header_table) {
84     json_object_set_new(obj, "header_table", dump_header_table(&inflater->ctx));
85   }
86   json_dumpf(obj, stdout, JSON_INDENT(2) | JSON_PRESERVE_ORDER);
87   json_decref(obj);
88   printf("\n");
89 }
90
91 static int inflate_hd(json_t *obj, nghttp2_hd_inflater *inflater, int seq) {
92   ssize_t rv;
93   nghttp2_nv nv;
94   int inflate_flags;
95   size_t old_settings_table_size = inflater->settings_hd_table_bufsize_max;
96
97   auto wire = json_object_get(obj, "wire");
98
99   if (wire == nullptr) {
100     fprintf(stderr, "'wire' key is missing at %d\n", seq);
101     return -1;
102   }
103
104   if (!json_is_string(wire)) {
105     fprintf(stderr, "'wire' value is not string at %d\n", seq);
106     return -1;
107   }
108
109   auto table_size = json_object_get(obj, "header_table_size");
110
111   if (table_size) {
112     if (!json_is_integer(table_size)) {
113       fprintf(stderr,
114               "The value of 'header_table_size key' is not integer at %d\n",
115               seq);
116       return -1;
117     }
118     rv = nghttp2_hd_inflate_change_table_size(inflater,
119                                               json_integer_value(table_size));
120     if (rv != 0) {
121       fprintf(stderr,
122               "nghttp2_hd_change_table_size() failed with error %s at %d\n",
123               nghttp2_strerror(rv), seq);
124       return -1;
125     }
126   }
127
128   auto inputlen = strlen(json_string_value(wire));
129
130   if (inputlen & 1) {
131     fprintf(stderr, "Badly formatted output value at %d\n", seq);
132     exit(EXIT_FAILURE);
133   }
134
135   auto buflen = inputlen / 2;
136   auto buf = std::vector<uint8_t>(buflen);
137
138   decode_hex(buf.data(), json_string_value(wire), inputlen);
139
140   auto headers = json_array();
141
142   auto p = buf.data();
143   for (;;) {
144     inflate_flags = 0;
145     rv = nghttp2_hd_inflate_hd(inflater, &nv, &inflate_flags, p, buflen, 1);
146     if (rv < 0) {
147       fprintf(stderr, "inflate failed with error code %zd at %d\n", rv, seq);
148       exit(EXIT_FAILURE);
149     }
150     p += rv;
151     buflen -= rv;
152     if (inflate_flags & NGHTTP2_HD_INFLATE_EMIT) {
153       json_array_append_new(
154           headers, dump_header(nv.name, nv.namelen, nv.value, nv.valuelen));
155     }
156     if (inflate_flags & NGHTTP2_HD_INFLATE_FINAL) {
157       break;
158     }
159   }
160   assert(buflen == 0);
161   nghttp2_hd_inflate_end_headers(inflater);
162   to_json(inflater, headers, wire, seq, old_settings_table_size);
163   json_decref(headers);
164
165   return 0;
166 }
167
168 static int perform(void) {
169   nghttp2_hd_inflater *inflater = NULL;
170   json_error_t error;
171
172   auto json = json_loadf(stdin, 0, &error);
173
174   if (json == nullptr) {
175     fprintf(stderr, "JSON loading failed\n");
176     exit(EXIT_FAILURE);
177   }
178
179   auto cases = json_object_get(json, "cases");
180
181   if (cases == nullptr) {
182     fprintf(stderr, "Missing 'cases' key in root object\n");
183     exit(EXIT_FAILURE);
184   }
185
186   if (!json_is_array(cases)) {
187     fprintf(stderr, "'cases' must be JSON array\n");
188     exit(EXIT_FAILURE);
189   }
190
191   nghttp2_hd_inflate_new(&inflater);
192   output_json_header();
193   auto len = json_array_size(cases);
194
195   for (size_t i = 0; i < len; ++i) {
196     auto obj = json_array_get(cases, i);
197     if (!json_is_object(obj)) {
198       fprintf(stderr, "Unexpected JSON type at %zu. It should be object.\n", i);
199       continue;
200     }
201     if (inflate_hd(obj, inflater, i) != 0) {
202       continue;
203     }
204     if (i + 1 < len) {
205       printf(",\n");
206     }
207   }
208   output_json_footer();
209   nghttp2_hd_inflate_del(inflater);
210   json_decref(json);
211
212   return 0;
213 }
214
215 static void print_help(void) {
216   std::cout << R"(HPACK HTTP/2 header decoder
217 Usage: inflatehd [OPTIONS] < INPUT
218
219 Reads JSON  data from stdin  and outputs inflated name/value  pairs in
220 JSON.
221
222 The root JSON object must contain "context" key, which indicates which
223 compression context is used.  If  it is "request", request compression
224 context  is used.   Otherwise, response  compression context  is used.
225 The value  of "cases" key  contains the sequence of  compressed header
226 block.  They share  the same compression context and  are processed in
227 the order they appear.  Each item in the sequence is a JSON object and
228 it must  have at least "wire"  key.  Its value is  a string containing
229 compressed header block in hex string.
230
231 Example:
232
233 {
234   "context": "request",
235   "cases":
236   [
237     { "wire": "0284f77778ff" },
238     { "wire": "0185fafd3c3c7f81" }
239   ]
240 }
241
242 The output of this program can be used as input for deflatehd.
243
244 OPTIONS:
245     -d, --dump-header-table
246                       Output dynamic header table.)" << std::endl;
247   ;
248 }
249
250 static struct option long_options[] = {
251     {"dump-header-table", no_argument, nullptr, 'd'}, {nullptr, 0, nullptr, 0}};
252
253 int main(int argc, char **argv) {
254   config.dump_header_table = 0;
255   while (1) {
256     int option_index = 0;
257     int c = getopt_long(argc, argv, "dh", long_options, &option_index);
258     if (c == -1) {
259       break;
260     }
261     switch (c) {
262     case 'h':
263       print_help();
264       exit(EXIT_SUCCESS);
265     case 'd':
266       // --dump-header-table
267       config.dump_header_table = 1;
268       break;
269     case '?':
270       exit(EXIT_FAILURE);
271     default:
272       break;
273     }
274   }
275   perform();
276   return 0;
277 }