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 <cassert>
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 #include "util.h"
49
50 namespace nghttp2 {
51
52 typedef struct {
53 size_t table_size;
54 size_t deflate_table_size;
55 int http1text;
56 int dump_header_table;
57 } deflate_config;
58
59 static deflate_config config;
60
61 static size_t input_sum;
62 static size_t output_sum;
63
to_hex_digit(uint8_t n)64 static char to_hex_digit(uint8_t n) {
65 if (n > 9) {
66 return n - 10 + 'a';
67 }
68 return n + '0';
69 }
70
to_hex(char * dest,const uint8_t * src,size_t len)71 static void to_hex(char *dest, const uint8_t *src, size_t len) {
72 size_t i;
73 for (i = 0; i < len; ++i) {
74 *dest++ = to_hex_digit(src[i] >> 4);
75 *dest++ = to_hex_digit(src[i] & 0xf);
76 }
77 }
78
output_to_json(nghttp2_hd_deflater * deflater,const uint8_t * buf,size_t buflen,size_t inputlen,const std::vector<nghttp2_nv> & nva,int seq)79 static void output_to_json(nghttp2_hd_deflater *deflater, const uint8_t *buf,
80 size_t buflen, size_t inputlen,
81 const std::vector<nghttp2_nv> &nva, int seq) {
82 auto hex = std::vector<char>(buflen * 2);
83 auto obj = json_object();
84 auto comp_ratio = inputlen == 0 ? 0.0 : (double)buflen / inputlen * 100;
85
86 json_object_set_new(obj, "seq", json_integer(seq));
87 json_object_set_new(obj, "input_length", json_integer(inputlen));
88 json_object_set_new(obj, "output_length", json_integer(buflen));
89 json_object_set_new(obj, "percentage_of_original_size",
90 json_real(comp_ratio));
91
92 if (buflen == 0) {
93 json_object_set_new(obj, "wire", json_string(""));
94 } else {
95 to_hex(hex.data(), buf, buflen);
96 json_object_set_new(obj, "wire", json_pack("s#", hex.data(), hex.size()));
97 }
98 json_object_set_new(obj, "headers", dump_headers(nva.data(), nva.size()));
99 if (seq == 0) {
100 // We only change the header table size only once at the beginning
101 json_object_set_new(obj, "header_table_size",
102 json_integer(config.table_size));
103 }
104 if (config.dump_header_table) {
105 json_object_set_new(obj, "header_table",
106 dump_deflate_header_table(deflater));
107 }
108 json_dumpf(obj, stdout, JSON_PRESERVE_ORDER | JSON_INDENT(2));
109 printf("\n");
110 json_decref(obj);
111 }
112
deflate_hd(nghttp2_hd_deflater * deflater,const std::vector<nghttp2_nv> & nva,size_t inputlen,int seq)113 static void deflate_hd(nghttp2_hd_deflater *deflater,
114 const std::vector<nghttp2_nv> &nva, size_t inputlen,
115 int seq) {
116 ssize_t rv;
117 std::array<uint8_t, 64_k> buf;
118
119 rv = nghttp2_hd_deflate_hd(deflater, buf.data(), buf.size(),
120 (nghttp2_nv *)nva.data(), nva.size());
121 if (rv < 0) {
122 fprintf(stderr, "deflate failed with error code %zd at %d\n", rv, seq);
123 exit(EXIT_FAILURE);
124 }
125
126 input_sum += inputlen;
127 output_sum += rv;
128
129 output_to_json(deflater, buf.data(), rv, inputlen, nva, seq);
130 }
131
deflate_hd_json(json_t * obj,nghttp2_hd_deflater * deflater,int seq)132 static int deflate_hd_json(json_t *obj, nghttp2_hd_deflater *deflater,
133 int seq) {
134 size_t inputlen = 0;
135
136 auto js = json_object_get(obj, "headers");
137 if (js == nullptr) {
138 fprintf(stderr, "'headers' key is missing at %d\n", seq);
139 return -1;
140 }
141 if (!json_is_array(js)) {
142 fprintf(stderr, "The value of 'headers' key must be an array at %d\n", seq);
143 return -1;
144 }
145
146 auto len = json_array_size(js);
147 auto nva = std::vector<nghttp2_nv>(len);
148
149 for (size_t i = 0; i < len; ++i) {
150 auto nv_pair = json_array_get(js, i);
151 const char *name;
152 json_t *value;
153
154 if (!json_is_object(nv_pair) || json_object_size(nv_pair) != 1) {
155 fprintf(stderr, "bad formatted name/value pair object at %d\n", seq);
156 return -1;
157 }
158
159 json_object_foreach(nv_pair, name, value) {
160 nva[i].name = (uint8_t *)name;
161 nva[i].namelen = strlen(name);
162
163 if (!json_is_string(value)) {
164 fprintf(stderr, "value is not string at %d\n", seq);
165 return -1;
166 }
167
168 nva[i].value = (uint8_t *)json_string_value(value);
169 nva[i].valuelen = strlen(json_string_value(value));
170
171 nva[i].flags = NGHTTP2_NV_FLAG_NONE;
172 }
173
174 inputlen += nva[i].namelen + nva[i].valuelen;
175 }
176
177 deflate_hd(deflater, nva, inputlen, seq);
178
179 return 0;
180 }
181
init_deflater()182 static nghttp2_hd_deflater *init_deflater() {
183 nghttp2_hd_deflater *deflater;
184 nghttp2_hd_deflate_new(&deflater, config.deflate_table_size);
185 if (config.table_size != NGHTTP2_DEFAULT_HEADER_TABLE_SIZE) {
186 nghttp2_hd_deflate_change_table_size(deflater, config.table_size);
187 }
188 return deflater;
189 }
190
deinit_deflater(nghttp2_hd_deflater * deflater)191 static void deinit_deflater(nghttp2_hd_deflater *deflater) {
192 nghttp2_hd_deflate_del(deflater);
193 }
194
perform(void)195 static int perform(void) {
196 json_error_t error;
197
198 auto json = json_loadf(stdin, 0, &error);
199
200 if (json == nullptr) {
201 fprintf(stderr, "JSON loading failed\n");
202 exit(EXIT_FAILURE);
203 }
204
205 auto cases = json_object_get(json, "cases");
206
207 if (cases == nullptr) {
208 fprintf(stderr, "Missing 'cases' key in root object\n");
209 exit(EXIT_FAILURE);
210 }
211
212 if (!json_is_array(cases)) {
213 fprintf(stderr, "'cases' must be JSON array\n");
214 exit(EXIT_FAILURE);
215 }
216
217 auto deflater = init_deflater();
218 output_json_header();
219 auto len = json_array_size(cases);
220
221 for (size_t i = 0; i < len; ++i) {
222 auto obj = json_array_get(cases, i);
223 if (!json_is_object(obj)) {
224 fprintf(stderr, "Unexpected JSON type at %zu. It should be object.\n", i);
225 continue;
226 }
227 if (deflate_hd_json(obj, deflater, i) != 0) {
228 continue;
229 }
230 if (i + 1 < len) {
231 printf(",\n");
232 }
233 }
234 output_json_footer();
235 deinit_deflater(deflater);
236 json_decref(json);
237 return 0;
238 }
239
perform_from_http1text(void)240 static int perform_from_http1text(void) {
241 char line[1 << 14];
242 int seq = 0;
243
244 auto deflater = init_deflater();
245 output_json_header();
246 for (;;) {
247 std::vector<nghttp2_nv> nva;
248 int end = 0;
249 size_t inputlen = 0;
250
251 for (;;) {
252 char *rv = fgets(line, sizeof(line), stdin);
253 char *val, *val_end;
254 if (rv == nullptr) {
255 end = 1;
256 break;
257 } else if (line[0] == '\n') {
258 break;
259 }
260
261 nva.emplace_back();
262 auto &nv = nva.back();
263
264 val = strchr(line + 1, ':');
265 if (val == nullptr) {
266 fprintf(stderr, "Bad HTTP/1 header field format at %d.\n", seq);
267 exit(EXIT_FAILURE);
268 }
269 *val = '\0';
270 ++val;
271 for (; *val && (*val == ' ' || *val == '\t'); ++val)
272 ;
273 for (val_end = val; *val_end && (*val_end != '\r' && *val_end != '\n');
274 ++val_end)
275 ;
276 *val_end = '\0';
277
278 nv.namelen = strlen(line);
279 nv.valuelen = strlen(val);
280 nv.name = (uint8_t *)strdup(line);
281 nv.value = (uint8_t *)strdup(val);
282 nv.flags = NGHTTP2_NV_FLAG_NONE;
283
284 inputlen += nv.namelen + nv.valuelen;
285 }
286
287 if (!end) {
288 if (seq > 0) {
289 printf(",\n");
290 }
291 deflate_hd(deflater, nva, inputlen, seq);
292 }
293
294 for (auto &nv : nva) {
295 free(nv.name);
296 free(nv.value);
297 }
298
299 if (end)
300 break;
301 ++seq;
302 }
303 output_json_footer();
304 deinit_deflater(deflater);
305 return 0;
306 }
307
print_help(void)308 static void print_help(void) {
309 std::cout << R"(HPACK HTTP/2 header encoder
310 Usage: deflatehd [OPTIONS] < INPUT
311
312 Reads JSON data or HTTP/1-style header fields from stdin and outputs
313 deflated header block in JSON array.
314
315 For the JSON input, the root JSON object must contain "context" key,
316 which indicates which compression context is used. If it is
317 "request", request compression context is used. Otherwise, response
318 compression context is used. The value of "cases" key contains the
319 sequence of input header set. They share the same compression context
320 and are processed in the order they appear. Each item in the sequence
321 is a JSON object and it must have at least "headers" key. Its value
322 is an array of a JSON object containing exactly one name/value pair.
323
324 Example:
325 {
326 "context": "request",
327 "cases":
328 [
329 {
330 "headers": [
331 { ":method": "GET" },
332 { ":path": "/" }
333 ]
334 },
335 {
336 "headers": [
337 { ":method": "POST" },
338 { ":path": "/" }
339 ]
340 }
341 ]
342 }
343
344 With -t option, the program can accept more familiar HTTP/1 style
345 header field block. Each header set must be followed by one empty
346 line:
347
348 Example:
349
350 :method: GET
351 :scheme: https
352 :path: /
353
354 :method: POST
355 user-agent: nghttp2
356
357 The output of this program can be used as input for inflatehd.
358
359 OPTIONS:
360 -t, --http1text Use HTTP/1 style header field text as input.
361 Each header set is delimited by single empty
362 line.
363 -s, --table-size=<N>
364 Set dynamic table size. In the HPACK
365 specification, this value is denoted by
366 SETTINGS_HEADER_TABLE_SIZE.
367 Default: 4096
368 -S, --deflate-table-size=<N>
369 Use first N bytes of dynamic header table
370 buffer.
371 Default: 4096
372 -d, --dump-header-table
373 Output dynamic header table.)"
374 << std::endl;
375 }
376
377 constexpr static struct option long_options[] = {
378 {"http1text", no_argument, nullptr, 't'},
379 {"table-size", required_argument, nullptr, 's'},
380 {"deflate-table-size", required_argument, nullptr, 'S'},
381 {"dump-header-table", no_argument, nullptr, 'd'},
382 {nullptr, 0, nullptr, 0}};
383
main(int argc,char ** argv)384 int main(int argc, char **argv) {
385 config.table_size = 4_k;
386 config.deflate_table_size = 4_k;
387 config.http1text = 0;
388 config.dump_header_table = 0;
389 while (1) {
390 int option_index = 0;
391 int c = getopt_long(argc, argv, "S:dhs:t", long_options, &option_index);
392 if (c == -1) {
393 break;
394 }
395 switch (c) {
396 case 'h':
397 print_help();
398 exit(EXIT_SUCCESS);
399 case 't':
400 // --http1text
401 config.http1text = 1;
402 break;
403 case 's': {
404 // --table-size
405 auto n = util::parse_uint(optarg);
406 if (n == -1) {
407 fprintf(stderr, "-s: Bad option value\n");
408 exit(EXIT_FAILURE);
409 }
410 config.table_size = n;
411 break;
412 }
413 case 'S': {
414 // --deflate-table-size
415 auto n = util::parse_uint(optarg);
416 if (n == -1) {
417 fprintf(stderr, "-S: Bad option value\n");
418 exit(EXIT_FAILURE);
419 }
420 config.deflate_table_size = n;
421 break;
422 }
423 case 'd':
424 // --dump-header-table
425 config.dump_header_table = 1;
426 break;
427 case '?':
428 exit(EXIT_FAILURE);
429 default:
430 break;
431 }
432 }
433 if (config.http1text) {
434 perform_from_http1text();
435 } else {
436 perform();
437 }
438
439 auto comp_ratio = input_sum == 0 ? 0.0 : (double)output_sum / input_sum;
440
441 fprintf(stderr, "Overall: input=%zu output=%zu ratio=%.02f\n", input_sum,
442 output_sum, comp_ratio);
443 return 0;
444 }
445
446 } // namespace nghttp2
447
main(int argc,char ** argv)448 int main(int argc, char **argv) {
449 return nghttp2::run_app(nghttp2::main, argc, argv);
450 }
451