• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * nghttp2 - HTTP/2 C Library
3  *
4  * Copyright (c) 2012 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 #include "nghttp2_config.h"
26 
27 #ifdef __sgi
28 #  define daemon _daemonize
29 #endif
30 
31 #ifdef HAVE_UNISTD_H
32 #  include <unistd.h>
33 #endif // HAVE_UNISTD_H
34 #include <signal.h>
35 #include <getopt.h>
36 
37 #include <cstdlib>
38 #include <cstring>
39 #include <cassert>
40 #include <string>
41 #include <iostream>
42 #include <string>
43 
44 #include <openssl/ssl.h>
45 #include <openssl/err.h>
46 #include <nghttp2/nghttp2.h>
47 
48 #include "app_helper.h"
49 #include "HttpServer.h"
50 #include "util.h"
51 #include "tls.h"
52 
53 namespace nghttp2 {
54 
55 namespace {
parse_push_config(Config & config,const char * optarg)56 int parse_push_config(Config &config, const char *optarg) {
57   const char *eq = strchr(optarg, '=');
58   if (eq == nullptr) {
59     return -1;
60   }
61   auto &paths = config.push[std::string(optarg, eq)];
62   auto optarg_end = optarg + strlen(optarg);
63   auto i = eq + 1;
64   for (;;) {
65     const char *j = strchr(i, ',');
66     if (j == nullptr) {
67       j = optarg_end;
68     }
69     paths.emplace_back(i, j);
70     if (j == optarg_end) {
71       break;
72     }
73     i = j;
74     ++i;
75   }
76 
77   return 0;
78 }
79 } // namespace
80 
81 namespace {
print_version(std::ostream & out)82 void print_version(std::ostream &out) {
83   out << "nghttpd nghttp2/" NGHTTP2_VERSION << std::endl;
84 }
85 } // namespace
86 
87 namespace {
print_usage(std::ostream & out)88 void print_usage(std::ostream &out) {
89   out << "Usage: nghttpd [OPTION]... <PORT> [<PRIVATE_KEY> <CERT>]\n"
90       << "HTTP/2 server" << std::endl;
91 }
92 } // namespace
93 
94 namespace {
print_help(std::ostream & out)95 void print_help(std::ostream &out) {
96   Config config;
97   print_usage(out);
98   out << R"(
99   <PORT>      Specify listening port number.
100   <PRIVATE_KEY>
101               Set  path  to  server's private  key.   Required  unless
102               --no-tls is specified.
103   <CERT>      Set  path  to  server's  certificate.   Required  unless
104               --no-tls is specified.
105 Options:
106   -a, --address=<ADDR>
107               The address to bind to.  If not specified the default IP
108               address determined by getaddrinfo is used.
109   -D, --daemon
110               Run in a background.  If -D is used, the current working
111               directory is  changed to '/'.  Therefore  if this option
112               is used, -d option must be specified.
113   -V, --verify-client
114               The server  sends a client certificate  request.  If the
115               client did  not return  a certificate, the  handshake is
116               terminated.   Currently,  this  option just  requests  a
117               client certificate and does not verify it.
118   -d, --htdocs=<PATH>
119               Specify document root.  If this option is not specified,
120               the document root is the current working directory.
121   -v, --verbose
122               Print debug information  such as reception/ transmission
123               of frames and name/value pairs.
124   --no-tls    Disable SSL/TLS.
125   -c, --header-table-size=<SIZE>
126               Specify decoder header table size.
127   --encoder-header-table-size=<SIZE>
128               Specify encoder header table size.  The decoder (client)
129               specifies  the maximum  dynamic table  size it  accepts.
130               Then the negotiated dynamic table size is the minimum of
131               this option value and the value which client specified.
132   --color     Force colored log output.
133   -p, --push=<PATH>=<PUSH_PATH,...>
134               Push  resources <PUSH_PATH>s  when <PATH>  is requested.
135               This option  can be used repeatedly  to specify multiple
136               push  configurations.    <PATH>  and   <PUSH_PATH>s  are
137               relative  to   document  root.   See   --htdocs  option.
138               Example: -p/=/foo.png -p/doc=/bar.css
139   -b, --padding=<N>
140               Add at  most <N>  bytes to a  frame payload  as padding.
141               Specify 0 to disable padding.
142   -m, --max-concurrent-streams=<N>
143               Set the maximum number of  the concurrent streams in one
144               HTTP/2 session.
145               Default: )"
146       << config.max_concurrent_streams << R"(
147   -n, --workers=<N>
148               Set the number of worker threads.
149               Default: 1
150   -e, --error-gzip
151               Make error response gzipped.
152   -w, --window-bits=<N>
153               Sets the stream level initial window size to 2**<N>-1.
154   -W, --connection-window-bits=<N>
155               Sets  the  connection  level   initial  window  size  to
156               2**<N>-1.
157   --dh-param-file=<PATH>
158               Path to file that contains  DH parameters in PEM format.
159               Without  this   option,  DHE   cipher  suites   are  not
160               available.
161   --early-response
162               Start sending response when request HEADERS is received,
163               rather than complete request is received.
164   --trailer=<HEADER>
165               Add a trailer  header to a response.   <HEADER> must not
166               include pseudo header field  (header field name starting
167               with ':').  The  trailer is sent only if  a response has
168               body part.  Example: --trailer 'foo: bar'.
169   --hexdump   Display the  incoming traffic in  hexadecimal (Canonical
170               hex+ASCII display).  If SSL/TLS  is used, decrypted data
171               are used.
172   --echo-upload
173               Send back uploaded content if method is POST or PUT.
174   --mime-types-file=<PATH>
175               Path  to file  that contains  MIME media  types and  the
176               extensions that represent them.
177               Default: )"
178       << config.mime_types_file << R"(
179   --no-content-length
180               Don't send content-length header field.
181   --ktls      Enable ktls.
182   --no-rfc7540-pri
183               Disable RFC7540 priorities.
184   --version   Display version information and exit.
185   -h, --help  Display this help and exit.
186 
187 --
188 
189   The <SIZE> argument is an integer and an optional unit (e.g., 10K is
190   10 * 1024).  Units are K, M and G (powers of 1024).)"
191       << std::endl;
192 }
193 } // namespace
194 
main(int argc,char ** argv)195 int main(int argc, char **argv) {
196   Config config;
197   bool color = false;
198   auto mime_types_file_set_manually = false;
199 
200   while (1) {
201     static int flag = 0;
202     constexpr static option long_options[] = {
203         {"address", required_argument, nullptr, 'a'},
204         {"daemon", no_argument, nullptr, 'D'},
205         {"htdocs", required_argument, nullptr, 'd'},
206         {"help", no_argument, nullptr, 'h'},
207         {"verbose", no_argument, nullptr, 'v'},
208         {"verify-client", no_argument, nullptr, 'V'},
209         {"header-table-size", required_argument, nullptr, 'c'},
210         {"push", required_argument, nullptr, 'p'},
211         {"padding", required_argument, nullptr, 'b'},
212         {"max-concurrent-streams", required_argument, nullptr, 'm'},
213         {"workers", required_argument, nullptr, 'n'},
214         {"error-gzip", no_argument, nullptr, 'e'},
215         {"window-bits", required_argument, nullptr, 'w'},
216         {"connection-window-bits", required_argument, nullptr, 'W'},
217         {"no-tls", no_argument, &flag, 1},
218         {"color", no_argument, &flag, 2},
219         {"version", no_argument, &flag, 3},
220         {"dh-param-file", required_argument, &flag, 4},
221         {"early-response", no_argument, &flag, 5},
222         {"trailer", required_argument, &flag, 6},
223         {"hexdump", no_argument, &flag, 7},
224         {"echo-upload", no_argument, &flag, 8},
225         {"mime-types-file", required_argument, &flag, 9},
226         {"no-content-length", no_argument, &flag, 10},
227         {"encoder-header-table-size", required_argument, &flag, 11},
228         {"ktls", no_argument, &flag, 12},
229         {"no-rfc7540-pri", no_argument, &flag, 13},
230         {nullptr, 0, nullptr, 0}};
231     int option_index = 0;
232     int c = getopt_long(argc, argv, "DVb:c:d:ehm:n:p:va:w:W:", long_options,
233                         &option_index);
234     if (c == -1) {
235       break;
236     }
237     switch (c) {
238     case 'a':
239       config.address = optarg;
240       break;
241     case 'D':
242       config.daemon = true;
243       break;
244     case 'V':
245       config.verify_client = true;
246       break;
247     case 'b': {
248       auto n = util::parse_uint(optarg);
249       if (!n) {
250         std::cerr << "-b: Bad option value: " << optarg << std::endl;
251         exit(EXIT_FAILURE);
252       }
253       config.padding = *n;
254       break;
255     }
256     case 'd':
257       config.htdocs = optarg;
258       break;
259     case 'e':
260       config.error_gzip = true;
261       break;
262     case 'm': {
263       // max-concurrent-streams option
264       auto n = util::parse_uint(optarg);
265       if (!n) {
266         std::cerr << "-m: invalid argument: " << optarg << std::endl;
267         exit(EXIT_FAILURE);
268       }
269       config.max_concurrent_streams = *n;
270       break;
271     }
272     case 'n': {
273 #ifdef NOTHREADS
274       std::cerr << "-n: WARNING: Threading disabled at build time, "
275                 << "no threads created." << std::endl;
276 #else
277       auto n = util::parse_uint(optarg);
278       if (!n) {
279         std::cerr << "-n: Bad option value: " << optarg << std::endl;
280         exit(EXIT_FAILURE);
281       }
282       config.num_worker = *n;
283 #endif // NOTHREADS
284       break;
285     }
286     case 'h':
287       print_help(std::cout);
288       exit(EXIT_SUCCESS);
289     case 'v':
290       config.verbose = true;
291       break;
292     case 'c': {
293       auto n = util::parse_uint_with_unit(optarg);
294       if (!n) {
295         std::cerr << "-c: Bad option value: " << optarg << std::endl;
296         exit(EXIT_FAILURE);
297       }
298       if (n > std::numeric_limits<uint32_t>::max()) {
299         std::cerr << "-c: Value too large.  It should be less than or equal to "
300                   << std::numeric_limits<uint32_t>::max() << std::endl;
301         exit(EXIT_FAILURE);
302       }
303       config.header_table_size = *n;
304       break;
305     }
306     case 'p':
307       if (parse_push_config(config, optarg) != 0) {
308         std::cerr << "-p: Bad option value: " << optarg << std::endl;
309       }
310       break;
311     case 'w':
312     case 'W': {
313       auto n = util::parse_uint(optarg);
314       if (!n || n > 30) {
315         std::cerr << "-" << static_cast<char>(c)
316                   << ": specify the integer in the range [0, 30], inclusive"
317                   << std::endl;
318         exit(EXIT_FAILURE);
319       }
320 
321       if (c == 'w') {
322         config.window_bits = *n;
323       } else {
324         config.connection_window_bits = *n;
325       }
326 
327       break;
328     }
329     case '?':
330       util::show_candidates(argv[optind - 1], long_options);
331       exit(EXIT_FAILURE);
332     case 0:
333       switch (flag) {
334       case 1:
335         // no-tls option
336         config.no_tls = true;
337         break;
338       case 2:
339         // color option
340         color = true;
341         break;
342       case 3:
343         // version
344         print_version(std::cout);
345         exit(EXIT_SUCCESS);
346       case 4:
347         // dh-param-file
348         config.dh_param_file = optarg;
349         break;
350       case 5:
351         // early-response
352         config.early_response = true;
353         break;
354       case 6: {
355         // trailer option
356         auto header = optarg;
357         auto value = strchr(optarg, ':');
358         if (!value) {
359           std::cerr << "--trailer: invalid header: " << optarg << std::endl;
360           exit(EXIT_FAILURE);
361         }
362         *value = 0;
363         value++;
364         while (isspace(*value)) {
365           value++;
366         }
367         if (*value == 0) {
368           // This could also be a valid case for suppressing a header
369           // similar to curl
370           std::cerr << "--trailer: invalid header - value missing: " << optarg
371                     << std::endl;
372           exit(EXIT_FAILURE);
373         }
374         config.trailer.emplace_back(header, value, false);
375         util::inp_strlower(config.trailer.back().name);
376         break;
377       }
378       case 7:
379         // hexdump option
380         config.hexdump = true;
381         break;
382       case 8:
383         // echo-upload option
384         config.echo_upload = true;
385         break;
386       case 9:
387         // mime-types-file option
388         mime_types_file_set_manually = true;
389         config.mime_types_file = optarg;
390         break;
391       case 10:
392         // no-content-length option
393         config.no_content_length = true;
394         break;
395       case 11: {
396         // encoder-header-table-size option
397         auto n = util::parse_uint_with_unit(optarg);
398         if (!n) {
399           std::cerr << "--encoder-header-table-size: Bad option value: "
400                     << optarg << std::endl;
401           exit(EXIT_FAILURE);
402         }
403         if (n > std::numeric_limits<uint32_t>::max()) {
404           std::cerr << "--encoder-header-table-size: Value too large.  It "
405                        "should be less than or equal to "
406                     << std::numeric_limits<uint32_t>::max() << std::endl;
407           exit(EXIT_FAILURE);
408         }
409         config.encoder_header_table_size = *n;
410         break;
411       }
412       case 12:
413         // tls option
414         config.ktls = true;
415         break;
416       case 13:
417         // no-rfc7540-pri option
418         config.no_rfc7540_pri = true;
419         break;
420       }
421       break;
422     default:
423       break;
424     }
425   }
426   if (argc - optind < (config.no_tls ? 1 : 3)) {
427     print_usage(std::cerr);
428     std::cerr << "Too few arguments" << std::endl;
429     exit(EXIT_FAILURE);
430   }
431 
432   {
433     auto portStr = argv[optind++];
434     auto n = util::parse_uint(portStr);
435     if (!n || n > std::numeric_limits<uint16_t>::max()) {
436       std::cerr << "<PORT>: Bad value: " << portStr << std::endl;
437       exit(EXIT_FAILURE);
438     }
439     config.port = *n;
440   }
441 
442   if (!config.no_tls) {
443     config.private_key_file = argv[optind++];
444     config.cert_file = argv[optind++];
445   }
446 
447   if (config.daemon) {
448     if (config.htdocs.empty()) {
449       print_usage(std::cerr);
450       std::cerr << "-d option must be specified when -D is used." << std::endl;
451       exit(EXIT_FAILURE);
452     }
453 #ifdef __sgi
454     if (daemon(0, 0, 0, 0) == -1) {
455 #else
456     if (util::daemonize(0, 0) == -1) {
457 #endif
458       perror("daemon");
459       exit(EXIT_FAILURE);
460     }
461   }
462   if (config.htdocs.empty()) {
463     config.htdocs = "./";
464   }
465 
466   if (util::read_mime_types(config.mime_types,
467                             config.mime_types_file.c_str()) != 0) {
468     if (mime_types_file_set_manually) {
469       std::cerr << "--mime-types-file: Could not open mime types file: "
470                 << config.mime_types_file << std::endl;
471     }
472   }
473 
474   auto &trailer_names = config.trailer_names;
475   for (auto &h : config.trailer) {
476     trailer_names += h.name;
477     trailer_names += ", ";
478   }
479   if (trailer_names.size() >= 2) {
480     trailer_names.resize(trailer_names.size() - 2);
481   }
482 
483   set_color_output(color || isatty(fileno(stdout)));
484 
485   struct sigaction act {};
486   act.sa_handler = SIG_IGN;
487   sigaction(SIGPIPE, &act, nullptr);
488 
489   reset_timer();
490 
491   HttpServer server(&config);
492   if (server.run() != 0) {
493     exit(EXIT_FAILURE);
494   }
495   return 0;
496 }
497 
498 } // namespace nghttp2
499 
main(int argc,char ** argv)500 int main(int argc, char **argv) {
501   return nghttp2::run_app(nghttp2::main, argc, argv);
502 }
503