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