• 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 #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