passenger 5.3.7 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +14 -0
  3. data/build/agent.rb +4 -2
  4. data/build/support/cxx_dependency_map.rb +134 -0
  5. data/resources/templates/standalone/server.erb +1 -0
  6. data/src/agent/AgentMain.cpp +4 -0
  7. data/src/agent/Core/ApplicationPool/Group/StateInspection.cpp +1 -1
  8. data/src/agent/Core/ApplicationPool/Options.h +7 -7
  9. data/src/agent/Core/ApplicationPool/Process.h +3 -0
  10. data/src/agent/Core/Config.h +9 -2
  11. data/src/agent/Core/Controller/Config.h +27 -6
  12. data/src/agent/Core/Controller/InitRequest.cpp +12 -7
  13. data/src/agent/Core/Controller/InitializationAndShutdown.cpp +2 -0
  14. data/src/agent/Core/CoreMain.cpp +62 -33
  15. data/src/agent/Core/OptionParser.h +6 -0
  16. data/src/agent/Core/SpawningKit/Spawner.h +20 -5
  17. data/src/agent/Core/SpawningKit/UserSwitchingRules.h +13 -6
  18. data/src/agent/Core/TelemetryCollector.h +1 -0
  19. data/src/agent/FileReadHelper/FileReadHelperMain.cpp +198 -0
  20. data/src/agent/Watchdog/Config.h +1 -0
  21. data/src/apache2_module/ConfigGeneral/AutoGeneratedDefinitions.cpp +5 -0
  22. data/src/apache2_module/ConfigGeneral/AutoGeneratedSetterFuncs.cpp +15 -0
  23. data/src/apache2_module/DirConfig/AutoGeneratedCreateFunction.cpp +5 -0
  24. data/src/apache2_module/DirConfig/AutoGeneratedManifestGeneration.cpp +13 -0
  25. data/src/apache2_module/DirConfig/AutoGeneratedMergeFunction.cpp +7 -0
  26. data/src/apache2_module/DirConfig/AutoGeneratedStruct.h +13 -0
  27. data/src/apache2_module/DirectoryMapper.h +14 -3
  28. data/src/apache2_module/Hooks.cpp +15 -4
  29. data/src/cxx_supportlib/AppLocalConfigFileUtils.h +148 -0
  30. data/src/cxx_supportlib/AppTypeDetector/CBindings.cpp +12 -1
  31. data/src/cxx_supportlib/AppTypeDetector/CBindings.h +2 -0
  32. data/src/cxx_supportlib/AppTypeDetector/Detector.h +38 -4
  33. data/src/cxx_supportlib/Constants.h +1 -1
  34. data/src/nginx_module/ConfigGeneral/AutoGeneratedDefinitions.c +16 -0
  35. data/src/nginx_module/ConfigGeneral/AutoGeneratedManifestDefaultsInitialization.c +6 -0
  36. data/src/nginx_module/ConfigGeneral/AutoGeneratedSetterFuncs.c +12 -0
  37. data/src/nginx_module/Configuration.c +20 -0
  38. data/src/nginx_module/ContentHandler.c +301 -23
  39. data/src/nginx_module/ContentHandler.h +5 -0
  40. data/src/nginx_module/LocationConfig/AutoGeneratedCreateFunction.c +10 -0
  41. data/src/nginx_module/LocationConfig/AutoGeneratedManifestGeneration.c +27 -0
  42. data/src/nginx_module/LocationConfig/AutoGeneratedMergeFunction.c +3 -0
  43. data/src/nginx_module/LocationConfig/AutoGeneratedStruct.h +7 -0
  44. data/src/nginx_module/ngx_http_passenger_module.h +6 -1
  45. data/src/ruby_supportlib/phusion_passenger.rb +6 -5
  46. data/src/ruby_supportlib/phusion_passenger/apache2/config_options.rb +6 -0
  47. data/src/ruby_supportlib/phusion_passenger/nginx/config_options.rb +14 -0
  48. data/src/ruby_supportlib/phusion_passenger/standalone/app_finder.rb +1 -0
  49. data/src/ruby_supportlib/phusion_passenger/standalone/config_options_list.rb +11 -1
  50. data/src/ruby_supportlib/phusion_passenger/standalone/start_command/builtin_engine.rb +1 -0
  51. metadata +4 -2
@@ -0,0 +1,148 @@
1
+ /*
2
+ * Phusion Passenger - https://www.phusionpassenger.com/
3
+ * Copyright (c) 2018 Phusion Holding B.V.
4
+ *
5
+ * "Passenger", "Phusion Passenger" and "Union Station" are registered
6
+ * trademarks of Phusion Holding B.V.
7
+ *
8
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ * of this software and associated documentation files (the "Software"), to deal
10
+ * in the Software without restriction, including without limitation the rights
11
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ * copies of the Software, and to permit persons to whom the Software is
13
+ * furnished to do so, subject to the following conditions:
14
+ *
15
+ * The above copyright notice and this permission notice shall be included in
16
+ * all copies or substantial portions of the Software.
17
+ *
18
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
+ * THE SOFTWARE.
25
+ */
26
+ #ifndef _PASSENGER_APP_LOCAL_CONFIG_FILE_UTILS_H_
27
+ #define _PASSENGER_APP_LOCAL_CONFIG_FILE_UTILS_H_
28
+
29
+ #include <oxt/system_calls.hpp>
30
+ #include <oxt/backtrace.hpp>
31
+
32
+ #include <cerrno>
33
+ #include <fcntl.h>
34
+
35
+ #include <jsoncpp/json.h>
36
+
37
+ #include <StaticString.h>
38
+ #include <Constants.h>
39
+ #include <Exceptions.h>
40
+ #include <IOTools/IOUtils.h>
41
+ #include <Utils/ScopeGuard.h>
42
+
43
+ namespace Passenger {
44
+
45
+ using namespace std;
46
+
47
+
48
+ struct AppLocalConfig {
49
+ string appStartCommand;
50
+ bool appSupportsKuriaProtocol;
51
+
52
+ AppLocalConfig()
53
+ : appSupportsKuriaProtocol(false)
54
+ { }
55
+ };
56
+
57
+
58
+ inline AppLocalConfig
59
+ parseAppLocalConfigFile(const StaticString appRoot) {
60
+ TRACE_POINT();
61
+ string path = appRoot + "/Passengerfile.json";
62
+
63
+ // Reading from Passengerfile.json from a root process is unsafe
64
+ // because of symlink attacks and other kinds of attacks. See the
65
+ // comments for safeReadFile().
66
+ //
67
+ // We are unable to use safeReadFile() here because we do not
68
+ // control the safety of the directories leading up to appRoot.
69
+ //
70
+ // What we can do is preventing the contents of an arbitrary
71
+ // file read from leaking out. Therefore, our result struct
72
+ // only contains a limited number of fields, that are known
73
+ // not to contain sensitive information. We also don't propagate
74
+ // JSON parsing error messages, which may contain the content.
75
+
76
+ int fd = syscalls::open(path.c_str(), O_RDONLY | O_NONBLOCK);
77
+ if (fd == -1) {
78
+ if (errno == ENOENT) {
79
+ return AppLocalConfig();
80
+ } else {
81
+ int e = errno;
82
+ throw FileSystemException("Error opening '" + path
83
+ + "' for reading", e, path);
84
+ }
85
+ }
86
+
87
+ UPDATE_TRACE_POINT();
88
+ FdGuard fdGuard(fd, __FILE__, __LINE__);
89
+ pair<string, bool> content;
90
+ try {
91
+ content = readAll(fd, 1024 * 512);
92
+ } catch (const SystemException &e) {
93
+ throw FileSystemException("Error reading from '" + path + "'",
94
+ e.code(), path);
95
+ }
96
+ if (!content.second) {
97
+ throw SecurityException("Error parsing " + path
98
+ + ": file exceeds size limit of 512 KB");
99
+ }
100
+ fdGuard.runNow();
101
+
102
+ UPDATE_TRACE_POINT();
103
+ Json::Reader reader;
104
+ Json::Value config;
105
+ if (!reader.parse(content.first, config)) {
106
+ if (geteuid() == 0) {
107
+ throw RuntimeException("Error parsing " + path
108
+ + " (error messages suppressed for security reasons)");
109
+ } else {
110
+ throw RuntimeException("Error parsing " + path + ": "
111
+ + reader.getFormattedErrorMessages());
112
+ }
113
+ }
114
+ // We no longer need the raw data so free the memory.
115
+ content.first.resize(0);
116
+
117
+
118
+ UPDATE_TRACE_POINT();
119
+ AppLocalConfig result;
120
+
121
+ if (!config.isObject()) {
122
+ throw RuntimeException("Config file " + path
123
+ + " is not valid: top-level JSON object expected");
124
+ }
125
+ if (config.isMember("app_start_command")) {
126
+ if (config["app_start_command"].isString()) {
127
+ result.appStartCommand = config["app_start_command"].asString();
128
+ } else {
129
+ throw RuntimeException("Config file " + path
130
+ + " is not valid: key 'app_start_command' must be a boolean");
131
+ }
132
+ }
133
+ if (config.isMember("app_supports_kuria_protocol")) {
134
+ if (config["app_supports_kuria_protocol"].isBool()) {
135
+ result.appSupportsKuriaProtocol = config["app_supports_kuria_protocol"].asBool();
136
+ } else {
137
+ throw RuntimeException("Config file " + path
138
+ + " is not valid: key 'app_supports_kuria_protocol' must be a boolean");
139
+ }
140
+ }
141
+
142
+ return result;
143
+ }
144
+
145
+
146
+ } // namespace Passenger
147
+
148
+ #endif /* _PASSENGER_APP_LOCAL_CONFIG_FILE_UTILS_H_ */
@@ -74,6 +74,17 @@ psg_app_type_detector_result_set_wrapper_registry_entry(PsgAppTypeDetectorResult
74
74
  cxxResult->wrapperRegistryEntry = static_cast<const WrapperRegistry::Entry *>(entry);
75
75
  }
76
76
 
77
+ const char *
78
+ psg_app_type_detector_result_get_app_start_command(const PsgAppTypeDetectorResult *result,
79
+ size_t *len)
80
+ {
81
+ const Detector::Result *cxxResult = static_cast<const Detector::Result *>(result);
82
+ if (len != NULL) {
83
+ *len = cxxResult->appStartCommand.size();
84
+ }
85
+ return cxxResult->appStartCommand.data();
86
+ }
87
+
77
88
 
78
89
  PsgAppTypeDetector *
79
90
  psg_app_type_detector_new(const PsgWrapperRegistry *registry,
@@ -81,7 +92,7 @@ psg_app_type_detector_new(const PsgWrapperRegistry *registry,
81
92
  {
82
93
  const Registry *cxxRegistry = static_cast<const Registry *>(registry);
83
94
  try {
84
- Detector *detector = new Detector(*cxxRegistry, NULL, NULL, throttleRate);
95
+ Detector *detector = new Detector(*cxxRegistry, NULL, NULL, throttleRate, NULL);
85
96
  return static_cast<PsgAppTypeDetector *>(detector);
86
97
  } catch (const std::bad_alloc &) {
87
98
  return NULL;
@@ -46,6 +46,8 @@ const PsgWrapperRegistryEntry *psg_app_type_detector_result_get_wrapper_registry
46
46
  const PsgAppTypeDetectorResult *result);
47
47
  void psg_app_type_detector_result_set_wrapper_registry_entry(PsgAppTypeDetectorResult *result,
48
48
  const PsgWrapperRegistryEntry *entry);
49
+ const char *psg_app_type_detector_result_get_app_start_command(const PsgAppTypeDetectorResult *result,
50
+ size_t *len);
49
51
 
50
52
 
51
53
  typedef void PsgAppTypeDetector;
@@ -38,30 +38,35 @@
38
38
  #include <string>
39
39
 
40
40
  #include <Exceptions.h>
41
+ #include <AppLocalConfigFileUtils.h>
41
42
  #include <WrapperRegistry/Registry.h>
42
43
  #include <FileTools/PathManip.h>
43
44
  #include <FileTools/FileManip.h>
45
+ #include <StrIntTools/StrIntUtils.h>
46
+ #include <DataStructures/StringKeyTable.h>
44
47
  #include <Utils.h>
45
48
  #include <Utils/CachedFileStat.hpp>
46
- #include <StrIntTools/StrIntUtils.h>
47
49
 
48
50
  namespace Passenger {
49
51
  namespace AppTypeDetector {
50
52
 
51
53
  using namespace std;
52
54
 
55
+ typedef AppLocalConfig* AppLocalConfigPtr;
56
+ typedef StringKeyTable<AppLocalConfig> AppLocalConfigMap;
53
57
 
54
58
  class Detector {
55
59
  public:
56
60
  struct Result {
57
61
  const WrapperRegistry::Entry *wrapperRegistryEntry;
62
+ string appStartCommand;
58
63
 
59
64
  Result()
60
65
  : wrapperRegistryEntry(NULL)
61
66
  { }
62
67
 
63
68
  bool isNull() const {
64
- return wrapperRegistryEntry == NULL;
69
+ return wrapperRegistryEntry == NULL && appStartCommand.empty();
65
70
  }
66
71
  };
67
72
 
@@ -71,6 +76,9 @@ private:
71
76
  boost::mutex *cstatMutex;
72
77
  unsigned int throttleRate;
73
78
  bool ownsCstat;
79
+ AppLocalConfigMap appLocalConfigCache;
80
+ boost::mutex *configMutex;
81
+ StringKeyTable<time_t> appRootCheckTimes;
74
82
 
75
83
  bool check(char *buf, const char *end, const StaticString &appRoot,
76
84
  const StaticString &name)
@@ -88,15 +96,33 @@ private:
88
96
  cstat, cstatMutex, throttleRate) != FT_NONEXISTANT;
89
97
  }
90
98
 
99
+ AppLocalConfigPtr getAppLocalConfigFromCache(const StaticString &appRoot) {
100
+ boost::unique_lock<boost::mutex> l;
101
+ time_t currentTime = SystemTime::get();
102
+ if (configMutex != NULL) {
103
+ l = boost::unique_lock<boost::mutex>(*configMutex);
104
+ }
105
+ if (!appLocalConfigCache.contains(appRoot)
106
+ || currentTime >= (appRootCheckTimes.lookupCopy(appRoot) + throttleRate)) {
107
+ AppLocalConfig config = parseAppLocalConfigFile(appRoot);
108
+ appLocalConfigCache.insert(appRoot, config);
109
+ appRootCheckTimes.insert(appRoot, currentTime);
110
+ }
111
+ AppLocalConfigPtr appLocalConfig;
112
+ appLocalConfigCache.lookup(appRoot, &appLocalConfig);
113
+ return appLocalConfig;
114
+ }
115
+
91
116
  public:
92
117
  Detector(const WrapperRegistry::Registry &_registry,
93
118
  CachedFileStat *_cstat = NULL, boost::mutex *_cstatMutex = NULL,
94
- unsigned int _throttleRate = 1)
119
+ unsigned int _throttleRate = 1, boost::mutex *_configMutex = NULL)
95
120
  : registry(_registry),
96
121
  cstat(_cstat),
97
122
  cstatMutex(_cstatMutex),
98
123
  throttleRate(_throttleRate),
99
- ownsCstat(false)
124
+ ownsCstat(false),
125
+ configMutex(_configMutex)
100
126
  {
101
127
  assert(_registry.isFinalized());
102
128
  if (_cstat == NULL) {
@@ -176,6 +202,14 @@ public:
176
202
  char buf[PATH_MAX + 32];
177
203
  const char *end = buf + sizeof(buf) - 1;
178
204
 
205
+ AppLocalConfigPtr appLocalConfig = getAppLocalConfigFromCache(appRoot);
206
+
207
+ if (!appLocalConfig->appStartCommand.empty()) {
208
+ Result result;
209
+ result.appStartCommand = appLocalConfig->appStartCommand;
210
+ return result;
211
+ }
212
+
179
213
  WrapperRegistry::Registry::ConstIterator it(registry.getIterator());
180
214
  while (*it != NULL) {
181
215
  const WrapperRegistry::Entry &entry = it.getValue();
@@ -81,7 +81,7 @@
81
81
  #define PASSENGER_API_VERSION_MAJOR 0
82
82
  #define PASSENGER_API_VERSION_MINOR 3
83
83
  #define PASSENGER_DEFAULT_USER "nobody"
84
- #define PASSENGER_VERSION "5.3.7"
84
+ #define PASSENGER_VERSION "6.0.0"
85
85
  #define POOL_HELPER_THREAD_STACK_SIZE 262144
86
86
  #define PROCESS_SHUTDOWN_TIMEOUT 60
87
87
  #define PROCESS_SHUTDOWN_TIMEOUT_DISPLAY "1 minute"
@@ -462,6 +462,14 @@
462
462
  offsetof(passenger_loc_conf_t, autogenerated.startup_file),
463
463
  NULL
464
464
  },
465
+ {
466
+ ngx_string("passenger_app_start_command"),
467
+ NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_HTTP_LIF_CONF | NGX_CONF_TAKE1,
468
+ passenger_conf_set_app_start_command,
469
+ NGX_HTTP_LOC_CONF_OFFSET,
470
+ offsetof(passenger_loc_conf_t, autogenerated.app_start_command),
471
+ NULL
472
+ },
465
473
  {
466
474
  ngx_string("passenger_restart_dir"),
467
475
  NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_HTTP_LIF_CONF | NGX_CONF_TAKE1,
@@ -606,6 +614,14 @@
606
614
  offsetof(passenger_loc_conf_t, upstream_config.busy_buffers_size_conf),
607
615
  NULL
608
616
  },
617
+ {
618
+ ngx_string("passenger_request_buffering"),
619
+ NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_HTTP_LIF_CONF | NGX_CONF_FLAG,
620
+ passenger_conf_set_request_buffering,
621
+ NGX_HTTP_LOC_CONF_OFFSET,
622
+ offsetof(passenger_loc_conf_t, upstream_config.request_buffering),
623
+ NULL
624
+ },
609
625
  {
610
626
  ngx_string("passenger_intercept_errors"),
611
627
  NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_HTTP_LIF_CONF | NGX_CONF_FLAG,
@@ -380,6 +380,12 @@ set_manifest_autogenerated_loc_conf_defaults(manifest_gen_ctx_t *ctx, PsgJsonVal
380
380
  "8k|16k",
381
381
  sizeof("8k|16k") - 1);
382
382
 
383
+ add_manifest_options_container_static_default_bool(ctx,
384
+ options_container,
385
+ "passenger_request_buffering",
386
+ sizeof("passenger_request_buffering") - 1,
387
+ 1);
388
+
383
389
  add_manifest_options_container_static_default_bool(ctx,
384
390
  options_container,
385
391
  "passenger_intercept_errors",
@@ -709,6 +709,18 @@ passenger_conf_set_startup_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
709
709
  return ngx_conf_set_str_slot(cf, cmd, conf);
710
710
  }
711
711
 
712
+ static char *
713
+ passenger_conf_set_app_start_command(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
714
+ passenger_loc_conf_t *passenger_conf = conf;
715
+
716
+ passenger_conf->autogenerated.app_start_command_explicitly_set = 1;
717
+ record_loc_conf_source_location(cf, passenger_conf,
718
+ &passenger_conf->autogenerated.app_start_command_source_file,
719
+ &passenger_conf->autogenerated.app_start_command_source_line);
720
+
721
+ return ngx_conf_set_str_slot(cf, cmd, conf);
722
+ }
723
+
712
724
  static char *
713
725
  passenger_conf_set_restart_dir(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
714
726
  passenger_loc_conf_t *passenger_conf = conf;
@@ -207,6 +207,7 @@ passenger_create_loc_conf(ngx_conf_t *cf)
207
207
  conf->upstream_config.next_upstream_tries = NGX_CONF_UNSET_UINT;
208
208
  #endif
209
209
  conf->upstream_config.buffering = NGX_CONF_UNSET;
210
+ conf->upstream_config.request_buffering = NGX_CONF_UNSET;
210
211
  conf->upstream_config.ignore_client_abort = NGX_CONF_UNSET;
211
212
  #if NGINX_VERSION_NUM >= 1007007
212
213
  conf->upstream_config.force_ranges = NGX_CONF_UNSET;
@@ -429,6 +430,9 @@ passenger_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
429
430
  ngx_conf_merge_value(conf->upstream_config.buffering,
430
431
  prev->upstream_config.buffering, 0);
431
432
 
433
+ ngx_conf_merge_value(conf->upstream_config.request_buffering,
434
+ prev->upstream_config.request_buffering, 1);
435
+
432
436
  ngx_conf_merge_value(conf->upstream_config.ignore_client_abort,
433
437
  prev->upstream_config.ignore_client_abort, 0);
434
438
 
@@ -1141,6 +1145,22 @@ passenger_enabled(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
1141
1145
  return NGX_CONF_OK;
1142
1146
  }
1143
1147
 
1148
+ static char *
1149
+ passenger_conf_set_request_buffering(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
1150
+ #ifdef NGINX_NO_SEND_REQUEST_BODY_INFINITE_LOOP_BUG
1151
+ passenger_loc_conf_t *passenger_conf = conf;
1152
+
1153
+ passenger_conf->autogenerated.upstream_config_request_buffering_explicitly_set = 1;
1154
+ record_loc_conf_source_location(cf, passenger_conf,
1155
+ &passenger_conf->autogenerated.upstream_config_request_buffering_source_file,
1156
+ &passenger_conf->autogenerated.upstream_config_request_buffering_source_line);
1157
+
1158
+ return ngx_conf_set_flag_slot(cf, cmd, conf);
1159
+ #else
1160
+ return "config cannot be set in Nginx < 1.15.3 due to this bug: https://trac.nginx.org/nginx/ticket/1618";
1161
+ #endif /* NGINX_NO_SEND_REQUEST_BODY_INFINITE_LOOP_BUG */
1162
+ }
1163
+
1144
1164
  static char *
1145
1165
  rails_framework_spawner_idle_time(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
1146
1166
  {
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * Copyright (C) Igor Sysoev
3
3
  * Copyright (C) 2007 Manlio Perillo (manlio.perillo@gmail.com)
4
- * Copyright (c) 2010-2017 Phusion Holding B.V.
4
+ * Copyright (c) 2010-2018 Phusion Holding B.V.
5
5
  *
6
6
  * Redistribution and use in source and binary forms, with or without
7
7
  * modification, are permitted provided that the following conditions
@@ -331,6 +331,179 @@ header_is_transfer_encoding(ngx_str_t *key)
331
331
  ngx_strncasecmp(key->data + 1, (u_char *) "ransfer-encodin", sizeof("ransfer-encodin") - 1) == 0;
332
332
  }
333
333
 
334
+ /* Given an ngx_chain_t head and tail position, appends a new chain element at the end,
335
+ * updates the head (if necessary) and returns the new element.
336
+ *
337
+ * - The element is allocated from a freelist.
338
+ * - Ensures that the element contains a buffer of at least `size` bytes.
339
+ * - Sets the given tag on the buffer in the chain element.
340
+ *
341
+ * On error, returns NULL without modifying the given chain.
342
+ */
343
+ static ngx_chain_t *
344
+ append_ngx_chain_element(ngx_pool_t *p, ngx_chain_t **head,
345
+ ngx_chain_t *tail, ngx_chain_t **freelist, ngx_buf_tag_t tag, size_t size)
346
+ {
347
+ ngx_chain_t *elem;
348
+ ngx_buf_t *buf;
349
+
350
+ elem = ngx_chain_get_free_buf(p, freelist);
351
+ if (elem == NULL) {
352
+ return NULL;
353
+ }
354
+
355
+ buf = elem->buf;
356
+ buf->tag = tag;
357
+
358
+ if (size > 0 && (buf->pos == NULL || buf->last == NULL
359
+ || (size_t) ngx_buf_size(buf) < size))
360
+ {
361
+ ngx_memzero(buf, sizeof(ngx_buf_t));
362
+
363
+ buf->start = ngx_palloc(p, size);
364
+ if (buf->start == NULL) {
365
+ return NULL;
366
+ }
367
+
368
+ /*
369
+ * set by ngx_memzero():
370
+ *
371
+ * b->file_pos = 0;
372
+ * b->file_last = 0;
373
+ * b->file = NULL;
374
+ * b->shadow = NULL;
375
+ * b->tag = 0;
376
+ * and flags
377
+ */
378
+
379
+ buf->pos = buf->start;
380
+ buf->last = buf->start;
381
+ buf->end = buf->last + size;
382
+ buf->temporary = 1;
383
+ }
384
+
385
+ if (*head == NULL) {
386
+ *head = elem;
387
+ } else {
388
+ tail->next = elem;
389
+ }
390
+ return elem;
391
+ }
392
+
393
+ /* Given a chain of buffers containing client body data,
394
+ * this filter wraps all that data into chunked encoding
395
+ * headers and footers.
396
+ */
397
+ static ngx_int_t
398
+ body_rechunk_output_filter(void *data, ngx_chain_t *input)
399
+ {
400
+ ngx_http_request_t *r = data;
401
+ ngx_chain_t *output_head = NULL, *output_tail = NULL;
402
+ ngx_int_t body_eof_reached = 0;
403
+ ngx_int_t rc;
404
+ passenger_context_t *ctx;
405
+
406
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
407
+ PROGRAM_NAME " rechunk output filter");
408
+
409
+ ctx = ngx_http_get_module_ctx(r, ngx_http_passenger_module);
410
+
411
+ if (input == NULL) {
412
+ goto out;
413
+ }
414
+
415
+ if (!ctx->header_sent) {
416
+ /* The first buffer contains the request header, so pass it unmodified. */
417
+ ctx->header_sent = 1;
418
+
419
+ while (input != NULL) {
420
+ output_tail = append_ngx_chain_element(r->pool,
421
+ &output_head, output_tail, &ctx->free,
422
+ (ngx_buf_tag_t) &body_rechunk_output_filter,
423
+ 0);
424
+ if (output_tail == NULL) {
425
+ return NGX_ERROR;
426
+ }
427
+
428
+ ngx_memcpy(output_tail->buf, input->buf, sizeof(ngx_buf_t));
429
+
430
+ body_eof_reached = input->buf->last_buf;
431
+ input = input->next;
432
+ }
433
+ } else {
434
+ while (input != NULL) {
435
+ /* Append chunked encoding size header */
436
+ output_tail = append_ngx_chain_element(r->pool,
437
+ &output_head, output_tail, &ctx->free,
438
+ (ngx_buf_tag_t) &body_rechunk_output_filter,
439
+ 32);
440
+ if (output_tail == NULL) {
441
+ return NGX_ERROR;
442
+ }
443
+
444
+ output_tail->buf->last = ngx_sprintf(output_tail->buf->last, "%xO\r\n",
445
+ ngx_buf_size(input->buf));
446
+
447
+
448
+ /* Append chunked encoding payload */
449
+ output_tail = append_ngx_chain_element(r->pool,
450
+ &output_head, output_tail, &ctx->free,
451
+ (ngx_buf_tag_t) &body_rechunk_output_filter,
452
+ 0);
453
+ if (output_tail == NULL) {
454
+ return NGX_ERROR;
455
+ }
456
+
457
+ ngx_memcpy(output_tail->buf, input->buf, sizeof(ngx_buf_t));
458
+
459
+
460
+ /* Append chunked encoding footer */
461
+ output_tail = append_ngx_chain_element(r->pool,
462
+ &output_head, output_tail, &ctx->free,
463
+ (ngx_buf_tag_t) &body_rechunk_output_filter,
464
+ 2);
465
+ if (output_tail == NULL) {
466
+ return NGX_ERROR;
467
+ }
468
+
469
+ output_tail->buf->last = ngx_copy(output_tail->buf->last, "\r\n", 2);
470
+
471
+
472
+ body_eof_reached = input->buf->last_buf;
473
+ input = input->next;
474
+ }
475
+ }
476
+
477
+ if (body_eof_reached) {
478
+ /* Append final termination chunk. */
479
+ output_tail = append_ngx_chain_element(r->pool,
480
+ &output_head, output_tail, &ctx->free,
481
+ (ngx_buf_tag_t) &body_rechunk_output_filter,
482
+ 5);
483
+ if (output_tail == NULL) {
484
+ return NGX_ERROR;
485
+ }
486
+
487
+ output_tail->buf->last = ngx_copy(output_tail->buf->last,
488
+ "0\r\n\r\n", 5);
489
+ }
490
+
491
+ out:
492
+
493
+ rc = ngx_chain_writer(&r->upstream->writer, output_head);
494
+
495
+ /*
496
+ * The previous ngx_chain_writer() call consumed some buffers.
497
+ * Find such consumped (empty) buffers in the output buffer list,
498
+ * and either free them or add them to the freelist depending on
499
+ * whether the buffer's tag matches ours.
500
+ */
501
+ ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &output_head,
502
+ (ngx_buf_tag_t) &body_rechunk_output_filter);
503
+
504
+ return rc;
505
+ }
506
+
334
507
  #define SET_NGX_STR(str, the_data) \
335
508
  do { \
336
509
  (str)->data = (u_char *) the_data; \
@@ -346,15 +519,25 @@ header_is_transfer_encoding(ngx_str_t *key)
346
519
  typedef struct {
347
520
  ngx_str_t method; /* Includes trailing space */
348
521
  ngx_str_t app_type;
522
+ ngx_str_t app_start_command;
349
523
  ngx_str_t escaped_uri;
350
- ngx_str_t content_length;
524
+ ngx_str_t content_length; /* Only used if !r->request_body_no_buffering */
351
525
  ngx_str_t core_password;
352
526
  ngx_str_t remote_port;
353
527
  } buffer_construction_state;
354
528
 
529
+ /* prepare_request_buffer_construction() and construct_request_buffer() are
530
+ * used to create an HTTP request header buffer to be sent to the Core Controller.
531
+ *
532
+ * construct_request_buffer() is actually called twice: the first time in "no-op" mode to
533
+ * calculate how many bytes it must allocate, and the second time to actually create the
534
+ * buffer. For efficiency reasons, as much preparation work as possible is split into
535
+ * the prepare_request_buffer_construction() function so that the two construct_request_buffer()
536
+ * calls don't have to perform that work twice.
537
+ */
355
538
  static ngx_int_t
356
- prepare_request_buffer_construction(ngx_http_request_t *r, passenger_context_t *context,
357
- buffer_construction_state *state)
539
+ prepare_request_buffer_construction(ngx_http_request_t *r, passenger_loc_conf_t *slcf,
540
+ passenger_context_t *context, buffer_construction_state *state)
358
541
  {
359
542
  unsigned int len;
360
543
  ngx_uint_t port;
@@ -364,6 +547,7 @@ prepare_request_buffer_construction(ngx_http_request_t *r, passenger_context_t *
364
547
  #endif
365
548
  const PsgWrapperRegistryEntry *wrapper_registry_entry;
366
549
 
550
+ /* Construct HTTP method string, including trailing space. */
367
551
  switch (r->method) {
368
552
  case NGX_HTTP_GET:
369
553
  SET_NGX_STR(&state->method, "GET ");
@@ -415,10 +599,28 @@ prepare_request_buffer_construction(ngx_http_request_t *r, passenger_context_t *
415
599
  break;
416
600
  }
417
601
 
418
- wrapper_registry_entry = psg_app_type_detector_result_get_wrapper_registry_entry(
419
- context->detector_result);
420
- state->app_type.data = (u_char *) psg_wrapper_registry_entry_get_language(wrapper_registry_entry,
421
- &state->app_type.len);
602
+ if (slcf->autogenerated.app_start_command.data != NULL) {
603
+ /* The config specified that this is either a generic app or a Kuria app. */
604
+ state->app_type.data = NULL;
605
+ state->app_type.len = 0;
606
+ state->app_start_command = slcf->autogenerated.app_start_command;
607
+ } else {
608
+ wrapper_registry_entry = psg_app_type_detector_result_get_wrapper_registry_entry(
609
+ context->detector_result);
610
+ if (wrapper_registry_entry != NULL) {
611
+ /* This is an auto-supported app. */
612
+ state->app_type.data = (u_char *) psg_wrapper_registry_entry_get_language(wrapper_registry_entry,
613
+ &state->app_type.len);
614
+ state->app_start_command.data = NULL;
615
+ state->app_start_command.len = 0;
616
+ } else {
617
+ /* This has been autodetected to be a generic app or a Kuria app. */
618
+ state->app_type.data = NULL;
619
+ state->app_type.len = 0;
620
+ state->app_start_command.data = (u_char *) psg_app_type_detector_result_get_app_start_command(
621
+ context->detector_result, &state->app_start_command.len);
622
+ }
623
+ }
422
624
 
423
625
  /*
424
626
  * Nginx unescapes URI's before passing them to Phusion Passenger,
@@ -446,7 +648,7 @@ prepare_request_buffer_construction(ngx_http_request_t *r, passenger_context_t *
446
648
  NGX_ESCAPE_URI);
447
649
  }
448
650
 
449
- if (r->headers_in.chunked) {
651
+ if (r->headers_in.chunked && !r->request_body_no_buffering) {
450
652
  /* If the request body is chunked, then Nginx sets r->headers_in.content_length_n
451
653
  * but does not set r->headers_in.headers, so we add this header ourselves.
452
654
  */
@@ -495,6 +697,7 @@ prepare_request_buffer_construction(ngx_http_request_t *r, passenger_context_t *
495
697
  return NGX_OK;
496
698
  }
497
699
 
700
+ /* See comment for prepare_request_buffer_construction() */
498
701
  static ngx_uint_t
499
702
  construct_request_buffer(ngx_http_request_t *r, passenger_loc_conf_t *slcf,
500
703
  passenger_context_t *context, buffer_construction_state *state, ngx_buf_t *b)
@@ -554,7 +757,7 @@ construct_request_buffer(ngx_http_request_t *r, passenger_loc_conf_t *slcf,
554
757
 
555
758
  if (ngx_hash_find(&slcf->headers_set_hash, header[i].hash,
556
759
  header[i].lowcase_key, header[i].key.len)
557
- || header_is_transfer_encoding(&header[i].key))
760
+ || (!r->request_body_no_buffering && header_is_transfer_encoding(&header[i].key)))
558
761
  {
559
762
  continue;
560
763
  }
@@ -568,7 +771,7 @@ construct_request_buffer(ngx_http_request_t *r, passenger_loc_conf_t *slcf,
568
771
  total_size += header[i].key.len + header[i].value.len + 4;
569
772
  }
570
773
 
571
- if (r->headers_in.chunked) {
774
+ if (r->headers_in.chunked && !r->request_body_no_buffering) {
572
775
  PUSH_STATIC_STR("Content-Length: ");
573
776
  if (b != NULL) {
574
777
  b->last = ngx_copy(b->last, state->content_length.data,
@@ -756,13 +959,23 @@ construct_request_buffer(ngx_http_request_t *r, passenger_loc_conf_t *slcf,
756
959
  PUSH_STATIC_STR("\r\n");
757
960
  }
758
961
 
759
- PUSH_STATIC_STR("!~PASSENGER_APP_TYPE: ");
760
- if (b != NULL) {
761
- b->last = ngx_copy(b->last, state->app_type.data,
762
- state->app_type.len);
962
+ if (state->app_type.len > 0) {
963
+ PUSH_STATIC_STR("!~PASSENGER_APP_TYPE: ");
964
+ if (b != NULL) {
965
+ b->last = ngx_copy(b->last, state->app_type.data,
966
+ state->app_type.len);
967
+ }
968
+ total_size += state->app_type.len;
969
+ PUSH_STATIC_STR("\r\n");
970
+ } else {
971
+ PUSH_STATIC_STR("!~PASSENGER_APP_START_COMMAND: ");
972
+ if (b != NULL) {
973
+ b->last = ngx_copy(b->last, state->app_start_command.data,
974
+ state->app_start_command.len);
975
+ }
976
+ total_size += state->app_start_command.len;
977
+ PUSH_STATIC_STR("\r\n");
763
978
  }
764
- total_size += state->app_type.len;
765
- PUSH_STATIC_STR("\r\n");
766
979
 
767
980
  if (b != NULL) {
768
981
  b->last = ngx_copy(b->last, slcf->options_cache.data, slcf->options_cache.len);
@@ -816,7 +1029,7 @@ create_request(ngx_http_request_t *r)
816
1029
 
817
1030
  /* Construct and pass request headers */
818
1031
 
819
- if (prepare_request_buffer_construction(r, context, &state) != NGX_OK) {
1032
+ if (prepare_request_buffer_construction(r, slcf, context, &state) != NGX_OK) {
820
1033
  return NGX_ERROR;
821
1034
  }
822
1035
  request_size = construct_request_buffer(r, slcf, context, &state, NULL);
@@ -825,20 +1038,45 @@ create_request(ngx_http_request_t *r)
825
1038
  if (b == NULL) {
826
1039
  return NGX_ERROR;
827
1040
  }
1041
+ construct_request_buffer(r, slcf, context, &state, b);
1042
+
828
1043
  cl = ngx_alloc_chain_link(r->pool);
829
1044
  if (cl == NULL) {
830
1045
  return NGX_ERROR;
831
1046
  }
832
1047
  cl->buf = b;
833
1048
 
834
- construct_request_buffer(r, slcf, context, &state, b);
835
1049
 
836
- /* Pass request body */
1050
+ /* Pass already received request body buffers. Make sure they come
1051
+ * after the request header buffer we just constructed.
1052
+ */
837
1053
 
838
1054
  body = r->upstream->request_bufs;
839
1055
  r->upstream->request_bufs = cl;
840
1056
 
841
1057
  while (body) {
1058
+ if (r->headers_in.chunked && r->request_body_no_buffering) {
1059
+ /* If Transfer-Encoding is chunked, then Nginx dechunks the body.
1060
+ * If at the same time request body buffering is disabled, then
1061
+ * we pass the Transfer-Encoding header to the Passenger Core,
1062
+ * and thus we also need to ensure we rechunk the body.
1063
+ */
1064
+ b = ngx_create_temp_buf(r->pool, 32);
1065
+ if (b == NULL) {
1066
+ return NGX_ERROR;
1067
+ }
1068
+
1069
+ b->last = ngx_sprintf(b->last, "%xO\r\n",
1070
+ ngx_buf_size(body->buf));
1071
+ cl->next = ngx_alloc_chain_link(r->pool);
1072
+ if (cl->next == NULL) {
1073
+ return NGX_ERROR;
1074
+ }
1075
+
1076
+ cl = cl->next;
1077
+ cl->buf = b;
1078
+ }
1079
+
842
1080
  b = ngx_alloc_buf(r->pool);
843
1081
  if (b == NULL) {
844
1082
  return NGX_ERROR;
@@ -850,15 +1088,41 @@ create_request(ngx_http_request_t *r)
850
1088
  if (cl->next == NULL) {
851
1089
  return NGX_ERROR;
852
1090
  }
853
-
854
1091
  cl = cl->next;
855
1092
  cl->buf = b;
856
1093
 
857
1094
  body = body->next;
1095
+
1096
+ if (r->headers_in.chunked && r->request_body_no_buffering) {
1097
+ b = ngx_create_temp_buf(r->pool, 2);
1098
+ if (b == NULL) {
1099
+ return NGX_ERROR;
1100
+ }
1101
+
1102
+ b->last = ngx_copy(b->last, "\r\n", 2);
1103
+ cl->next = ngx_alloc_chain_link(r->pool);
1104
+ if (cl->next == NULL) {
1105
+ return NGX_ERROR;
1106
+ }
1107
+
1108
+ cl = cl->next;
1109
+ cl->buf = b;
1110
+ }
858
1111
  }
1112
+
859
1113
  b->flush = 1;
860
1114
  cl->next = NULL;
861
1115
 
1116
+ /* Again, if Transfer-Encoding is chunked, then Nginx dechunks the body.
1117
+ * Here we install an output filter to make sure that the request body parts
1118
+ * that will be received in the future, will also be rechunked when passed
1119
+ * to the Passenger Core.
1120
+ */
1121
+ if (r->headers_in.chunked && r->request_body_no_buffering) {
1122
+ r->upstream->output.output_filter = body_rechunk_output_filter;
1123
+ r->upstream->output.filter_ctx = r;
1124
+ }
1125
+
862
1126
  return NGX_OK;
863
1127
  }
864
1128
 
@@ -1467,7 +1731,15 @@ passenger_content_handler(ngx_http_request_t *r)
1467
1731
  detector_result_cleanup->handler = cleanup_detector_result;
1468
1732
  detector_result_cleanup->data = context->detector_result;
1469
1733
 
1470
- if (slcf->autogenerated.app_type.data == NULL) {
1734
+ /* If `app_start_command` is set, then it means the config specified that it is
1735
+ * either a generic app or a Kuria app.
1736
+ */
1737
+ if (slcf->autogenerated.app_start_command.data == NULL
1738
+ && slcf->autogenerated.app_type.data == NULL)
1739
+ {
1740
+ /* If neither `app_start_command` nor `app_type` are set, then
1741
+ * autodetect what kind of app this is.
1742
+ */
1471
1743
  pp_error_init(&error);
1472
1744
  if (slcf->autogenerated.app_root.data == NULL) {
1473
1745
  psg_app_type_detector_check_document_root(
@@ -1502,7 +1774,11 @@ passenger_content_handler(ngx_http_request_t *r)
1502
1774
  pp_error_destroy(&error);
1503
1775
  return NGX_HTTP_INTERNAL_SERVER_ERROR;
1504
1776
  }
1505
- } else {
1777
+ } else if (slcf->autogenerated.app_start_command.data == NULL) {
1778
+ /* If `app_start_command` is not set but `app_type` is, then
1779
+ * verify whether the given `app_type` value is supported
1780
+ * and resolve aliases.
1781
+ */
1506
1782
  wrapper_registry_entry = psg_wrapper_registry_lookup(psg_wrapper_registry,
1507
1783
  (const char *) slcf->autogenerated.app_type.data,
1508
1784
  slcf->autogenerated.app_type.len);
@@ -1547,6 +1823,8 @@ passenger_content_handler(ngx_http_request_t *r)
1547
1823
  u->pipe->input_filter = ngx_event_pipe_copy_input_filter;
1548
1824
  u->pipe->input_ctx = r;
1549
1825
 
1826
+ r->request_body_no_buffering = !slcf->upstream_config.request_buffering;
1827
+
1550
1828
  rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init);
1551
1829
 
1552
1830
  fix_peer_address(r);