passenger 5.3.7 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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);