• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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