• 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   tls::libssl_init();
197 
198 #ifndef NOTHREADS
199   tls::LibsslGlobalLock lock;
200 #endif // NOTHREADS
201 
202   Config config;
203   bool color = false;
204   auto mime_types_file_set_manually = false;
205 
206   while (1) {
207     static int flag = 0;
208     constexpr static option long_options[] = {
209         {"address", required_argument, nullptr, 'a'},
210         {"daemon", no_argument, nullptr, 'D'},
211         {"htdocs", required_argument, nullptr, 'd'},
212         {"help", no_argument, nullptr, 'h'},
213         {"verbose", no_argument, nullptr, 'v'},
214         {"verify-client", no_argument, nullptr, 'V'},
215         {"header-table-size", required_argument, nullptr, 'c'},
216         {"push", required_argument, nullptr, 'p'},
217         {"padding", required_argument, nullptr, 'b'},
218         {"max-concurrent-streams", required_argument, nullptr, 'm'},
219         {"workers", required_argument, nullptr, 'n'},
220         {"error-gzip", no_argument, nullptr, 'e'},
221         {"window-bits", required_argument, nullptr, 'w'},
222         {"connection-window-bits", required_argument, nullptr, 'W'},
223         {"no-tls", no_argument, &flag, 1},
224         {"color", no_argument, &flag, 2},
225         {"version", no_argument, &flag, 3},
226         {"dh-param-file", required_argument, &flag, 4},
227         {"early-response", no_argument, &flag, 5},
228         {"trailer", required_argument, &flag, 6},
229         {"hexdump", no_argument, &flag, 7},
230         {"echo-upload", no_argument, &flag, 8},
231         {"mime-types-file", required_argument, &flag, 9},
232         {"no-content-length", no_argument, &flag, 10},
233         {"encoder-header-table-size", required_argument, &flag, 11},
234         {"ktls", no_argument, &flag, 12},
235         {"no-rfc7540-pri", no_argument, &flag, 13},
236         {nullptr, 0, nullptr, 0}};
237     int option_index = 0;
238     int c = getopt_long(argc, argv, "DVb:c:d:ehm:n:p:va:w:W:", long_options,
239                         &option_index);
240     if (c == -1) {
241       break;
242     }
243     switch (c) {
244     case 'a':
245       config.address = optarg;
246       break;
247     case 'D':
248       config.daemon = true;
249       break;
250     case 'V':
251       config.verify_client = true;
252       break;
253     case 'b': {
254       auto n = util::parse_uint(optarg);
255       if (n == -1) {
256         std::cerr << "-b: Bad option value: " << optarg << std::endl;
257         exit(EXIT_FAILURE);
258       }
259       config.padding = n;
260       break;
261     }
262     case 'd':
263       config.htdocs = optarg;
264       break;
265     case 'e':
266       config.error_gzip = true;
267       break;
268     case 'm': {
269       // max-concurrent-streams option
270       auto n = util::parse_uint(optarg);
271       if (n == -1) {
272         std::cerr << "-m: invalid argument: " << optarg << std::endl;
273         exit(EXIT_FAILURE);
274       }
275       config.max_concurrent_streams = n;
276       break;
277     }
278     case 'n': {
279 #ifdef NOTHREADS
280       std::cerr << "-n: WARNING: Threading disabled at build time, "
281                 << "no threads created." << std::endl;
282 #else
283       auto n = util::parse_uint(optarg);
284       if (n == -1) {
285         std::cerr << "-n: Bad option value: " << optarg << std::endl;
286         exit(EXIT_FAILURE);
287       }
288       config.num_worker = n;
289 #endif // NOTHREADS
290       break;
291     }
292     case 'h':
293       print_help(std::cout);
294       exit(EXIT_SUCCESS);
295     case 'v':
296       config.verbose = true;
297       break;
298     case 'c': {
299       auto n = util::parse_uint_with_unit(optarg);
300       if (n == -1) {
301         std::cerr << "-c: Bad option value: " << optarg << std::endl;
302         exit(EXIT_FAILURE);
303       }
304       if (n > std::numeric_limits<uint32_t>::max()) {
305         std::cerr << "-c: Value too large.  It should be less than or equal to "
306                   << std::numeric_limits<uint32_t>::max() << std::endl;
307         exit(EXIT_FAILURE);
308       }
309       config.header_table_size = n;
310       break;
311     }
312     case 'p':
313       if (parse_push_config(config, optarg) != 0) {
314         std::cerr << "-p: Bad option value: " << optarg << std::endl;
315       }
316       break;
317     case 'w':
318     case 'W': {
319       auto n = util::parse_uint(optarg);
320       if (n == -1 || n > 30) {
321         std::cerr << "-" << static_cast<char>(c)
322                   << ": specify the integer in the range [0, 30], inclusive"
323                   << std::endl;
324         exit(EXIT_FAILURE);
325       }
326 
327       if (c == 'w') {
328         config.window_bits = n;
329       } else {
330         config.connection_window_bits = n;
331       }
332 
333       break;
334     }
335     case '?':
336       util::show_candidates(argv[optind - 1], long_options);
337       exit(EXIT_FAILURE);
338     case 0:
339       switch (flag) {
340       case 1:
341         // no-tls option
342         config.no_tls = true;
343         break;
344       case 2:
345         // color option
346         color = true;
347         break;
348       case 3:
349         // version
350         print_version(std::cout);
351         exit(EXIT_SUCCESS);
352       case 4:
353         // dh-param-file
354         config.dh_param_file = optarg;
355         break;
356       case 5:
357         // early-response
358         config.early_response = true;
359         break;
360       case 6: {
361         // trailer option
362         auto header = optarg;
363         auto value = strchr(optarg, ':');
364         if (!value) {
365           std::cerr << "--trailer: invalid header: " << optarg << std::endl;
366           exit(EXIT_FAILURE);
367         }
368         *value = 0;
369         value++;
370         while (isspace(*value)) {
371           value++;
372         }
373         if (*value == 0) {
374           // This could also be a valid case for suppressing a header
375           // similar to curl
376           std::cerr << "--trailer: invalid header - value missing: " << optarg
377                     << std::endl;
378           exit(EXIT_FAILURE);
379         }
380         config.trailer.emplace_back(header, value, false);
381         util::inp_strlower(config.trailer.back().name);
382         break;
383       }
384       case 7:
385         // hexdump option
386         config.hexdump = true;
387         break;
388       case 8:
389         // echo-upload option
390         config.echo_upload = true;
391         break;
392       case 9:
393         // mime-types-file option
394         mime_types_file_set_manually = true;
395         config.mime_types_file = optarg;
396         break;
397       case 10:
398         // no-content-length option
399         config.no_content_length = true;
400         break;
401       case 11: {
402         // encoder-header-table-size option
403         auto n = util::parse_uint_with_unit(optarg);
404         if (n == -1) {
405           std::cerr << "--encoder-header-table-size: Bad option value: "
406                     << optarg << std::endl;
407           exit(EXIT_FAILURE);
408         }
409         if (n > std::numeric_limits<uint32_t>::max()) {
410           std::cerr << "--encoder-header-table-size: Value too large.  It "
411                        "should be less than or equal to "
412                     << std::numeric_limits<uint32_t>::max() << std::endl;
413           exit(EXIT_FAILURE);
414         }
415         config.encoder_header_table_size = n;
416         break;
417       }
418       case 12:
419         // tls option
420         config.ktls = true;
421         break;
422       case 13:
423         // no-rfc7540-pri option
424         config.no_rfc7540_pri = true;
425         break;
426       }
427       break;
428     default:
429       break;
430     }
431   }
432   if (argc - optind < (config.no_tls ? 1 : 3)) {
433     print_usage(std::cerr);
434     std::cerr << "Too few arguments" << std::endl;
435     exit(EXIT_FAILURE);
436   }
437 
438   {
439     auto portStr = argv[optind++];
440     auto n = util::parse_uint(portStr);
441     if (n == -1 || n > std::numeric_limits<uint16_t>::max()) {
442       std::cerr << "<PORT>: Bad value: " << portStr << std::endl;
443       exit(EXIT_FAILURE);
444     }
445     config.port = n;
446   }
447 
448   if (!config.no_tls) {
449     config.private_key_file = argv[optind++];
450     config.cert_file = argv[optind++];
451   }
452 
453   if (config.daemon) {
454     if (config.htdocs.empty()) {
455       print_usage(std::cerr);
456       std::cerr << "-d option must be specified when -D is used." << std::endl;
457       exit(EXIT_FAILURE);
458     }
459 #ifdef __sgi
460     if (daemon(0, 0, 0, 0) == -1) {
461 #else
462     if (util::daemonize(0, 0) == -1) {
463 #endif
464       perror("daemon");
465       exit(EXIT_FAILURE);
466     }
467   }
468   if (config.htdocs.empty()) {
469     config.htdocs = "./";
470   }
471 
472   if (util::read_mime_types(config.mime_types,
473                             config.mime_types_file.c_str()) != 0) {
474     if (mime_types_file_set_manually) {
475       std::cerr << "--mime-types-file: Could not open mime types file: "
476                 << config.mime_types_file << std::endl;
477     }
478   }
479 
480   auto &trailer_names = config.trailer_names;
481   for (auto &h : config.trailer) {
482     trailer_names += h.name;
483     trailer_names += ", ";
484   }
485   if (trailer_names.size() >= 2) {
486     trailer_names.resize(trailer_names.size() - 2);
487   }
488 
489   set_color_output(color || isatty(fileno(stdout)));
490 
491   struct sigaction act {};
492   act.sa_handler = SIG_IGN;
493   sigaction(SIGPIPE, &act, nullptr);
494 
495   reset_timer();
496 
497   HttpServer server(&config);
498   if (server.run() != 0) {
499     exit(EXIT_FAILURE);
500   }
501   return 0;
502 }
503 
504 } // namespace nghttp2
505 
main(int argc,char ** argv)506 int main(int argc, char **argv) {
507   return nghttp2::run_app(nghttp2::main, argc, argv);
508 }
509