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