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