passenger 3.0.2 → 3.0.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of passenger might be problematic. Click here for more details.

Files changed (220) hide show
  1. data/NEWS +29 -0
  2. data/build/agents.rb +1 -0
  3. data/build/apache2.rb +2 -1
  4. data/build/cxx_tests.rb +7 -0
  5. data/build/rpm.rb +17 -22
  6. data/doc/cxxapi/Constants_8h_source.html +1 -1
  7. data/doc/cxxapi/Exceptions_8h_source.html +18 -4
  8. data/doc/cxxapi/Logging_8h_source.html +332 -262
  9. data/doc/cxxapi/annotated.html +1 -0
  10. data/doc/cxxapi/classes.html +4 -4
  11. data/doc/cxxapi/group__Exceptions.html +2 -0
  12. data/doc/cxxapi/hierarchy.html +1 -0
  13. data/doc/cxxapi/inherit__graph__35.map +1 -3
  14. data/doc/cxxapi/inherit__graph__35.md5 +1 -1
  15. data/doc/cxxapi/inherit__graph__35.png +0 -0
  16. data/doc/cxxapi/inherit__graph__36.map +3 -1
  17. data/doc/cxxapi/inherit__graph__36.md5 +1 -1
  18. data/doc/cxxapi/inherit__graph__36.png +0 -0
  19. data/doc/cxxapi/inherit__graph__37.map +1 -1
  20. data/doc/cxxapi/inherit__graph__37.md5 +1 -1
  21. data/doc/cxxapi/inherit__graph__37.png +0 -0
  22. data/doc/cxxapi/inherit__graph__38.map +1 -1
  23. data/doc/cxxapi/inherit__graph__38.md5 +1 -1
  24. data/doc/cxxapi/inherit__graph__38.png +0 -0
  25. data/doc/cxxapi/inherit__graph__39.map +1 -1
  26. data/doc/cxxapi/inherit__graph__39.md5 +1 -1
  27. data/doc/cxxapi/inherit__graph__39.png +0 -0
  28. data/doc/cxxapi/inherit__graph__40.map +1 -1
  29. data/doc/cxxapi/inherit__graph__40.md5 +1 -1
  30. data/doc/cxxapi/inherit__graph__40.png +0 -0
  31. data/doc/cxxapi/inherits.html +9 -6
  32. data/doc/cxxapi/namespacePassenger.html +2 -0
  33. data/doc/cxxapi/tree.html +2 -0
  34. data/doc/rdoc/classes/ConditionVariable.html +215 -0
  35. data/doc/rdoc/classes/Exception.html +120 -0
  36. data/doc/rdoc/classes/GC.html +113 -0
  37. data/doc/rdoc/classes/IO.html +221 -0
  38. data/doc/rdoc/classes/PhusionPassenger.html +397 -0
  39. data/doc/rdoc/classes/PhusionPassenger/AbstractInstaller.html +180 -0
  40. data/doc/rdoc/classes/PhusionPassenger/AbstractRequestHandler.html +647 -0
  41. data/doc/rdoc/classes/PhusionPassenger/AbstractServer.html +654 -0
  42. data/doc/rdoc/classes/PhusionPassenger/AbstractServer/InvalidPassword.html +92 -0
  43. data/doc/rdoc/classes/PhusionPassenger/AbstractServer/ServerAlreadyStarted.html +97 -0
  44. data/doc/rdoc/classes/PhusionPassenger/AbstractServer/ServerError.html +96 -0
  45. data/doc/rdoc/classes/PhusionPassenger/AbstractServer/ServerNotStarted.html +97 -0
  46. data/doc/rdoc/classes/PhusionPassenger/AbstractServer/UnknownMessage.html +96 -0
  47. data/doc/rdoc/classes/PhusionPassenger/AbstractServerCollection.html +619 -0
  48. data/doc/rdoc/classes/PhusionPassenger/AdminTools.html +142 -0
  49. data/doc/rdoc/classes/PhusionPassenger/AdminTools/MemoryStats.html +368 -0
  50. data/doc/rdoc/classes/PhusionPassenger/AdminTools/MemoryStats/Process.html +231 -0
  51. data/doc/rdoc/classes/PhusionPassenger/AdminTools/ServerInstance.html +588 -0
  52. data/doc/rdoc/classes/PhusionPassenger/AdminTools/ServerInstance/CorruptedDirectoryError.html +92 -0
  53. data/doc/rdoc/classes/PhusionPassenger/AdminTools/ServerInstance/GenerationsAbsentError.html +92 -0
  54. data/doc/rdoc/classes/PhusionPassenger/AdminTools/ServerInstance/Group.html +147 -0
  55. data/doc/rdoc/classes/PhusionPassenger/AdminTools/ServerInstance/Process.html +279 -0
  56. data/doc/rdoc/classes/PhusionPassenger/AdminTools/ServerInstance/RoleDeniedError.html +92 -0
  57. data/doc/rdoc/classes/PhusionPassenger/AdminTools/ServerInstance/StaleDirectoryError.html +92 -0
  58. data/doc/rdoc/classes/PhusionPassenger/AdminTools/ServerInstance/Stats.html +123 -0
  59. data/doc/rdoc/classes/PhusionPassenger/AdminTools/ServerInstance/UnsupportedGenerationStructureVersionError.html +92 -0
  60. data/doc/rdoc/classes/PhusionPassenger/AnalyticsLogger.html +368 -0
  61. data/doc/rdoc/classes/PhusionPassenger/AnalyticsLogger/Log.html +299 -0
  62. data/doc/rdoc/classes/PhusionPassenger/AnalyticsLogger/SharedData.html +206 -0
  63. data/doc/rdoc/classes/PhusionPassenger/AppInitError.html +155 -0
  64. data/doc/rdoc/classes/PhusionPassenger/AppProcess.html +367 -0
  65. data/doc/rdoc/classes/PhusionPassenger/ClassicRails.html +95 -0
  66. data/doc/rdoc/classes/PhusionPassenger/ClassicRails/ApplicationSpawner.html +351 -0
  67. data/doc/rdoc/classes/PhusionPassenger/ClassicRails/ApplicationSpawner/Error.html +98 -0
  68. data/doc/rdoc/classes/PhusionPassenger/ClassicRails/CGIFixed.html +200 -0
  69. data/doc/rdoc/classes/PhusionPassenger/ClassicRails/FrameworkSpawner.html +410 -0
  70. data/doc/rdoc/classes/PhusionPassenger/ClassicRails/FrameworkSpawner/Error.html +98 -0
  71. data/doc/rdoc/classes/PhusionPassenger/ClassicRails/RequestHandler.html +156 -0
  72. data/doc/rdoc/classes/PhusionPassenger/ClassicRailsExtensions.html +115 -0
  73. data/doc/rdoc/classes/PhusionPassenger/ClassicRailsExtensions/AnalyticsLogging.html +202 -0
  74. data/doc/rdoc/classes/PhusionPassenger/ConsoleTextTemplate.html +172 -0
  75. data/doc/rdoc/classes/PhusionPassenger/DebugLogging.html +273 -0
  76. data/doc/rdoc/classes/PhusionPassenger/FrameworkInitError.html +145 -0
  77. data/doc/rdoc/classes/PhusionPassenger/HTMLTemplate.html +162 -0
  78. data/doc/rdoc/classes/PhusionPassenger/InitializationError.html +141 -0
  79. data/doc/rdoc/classes/PhusionPassenger/InvalidPath.html +92 -0
  80. data/doc/rdoc/classes/PhusionPassenger/MessageChannel.html +673 -0
  81. data/doc/rdoc/classes/PhusionPassenger/MessageChannel/InvalidHashError.html +92 -0
  82. data/doc/rdoc/classes/PhusionPassenger/MessageClient.html +415 -0
  83. data/doc/rdoc/classes/PhusionPassenger/NativeSupportLoader.html +134 -0
  84. data/doc/rdoc/classes/PhusionPassenger/Packaging.html +129 -0
  85. data/doc/rdoc/classes/PhusionPassenger/PlatformInfo.html +1972 -0
  86. data/doc/rdoc/classes/PhusionPassenger/Plugin.html +237 -0
  87. data/doc/rdoc/classes/PhusionPassenger/Rack.html +91 -0
  88. data/doc/rdoc/classes/PhusionPassenger/Rack/ApplicationSpawner.html +312 -0
  89. data/doc/rdoc/classes/PhusionPassenger/Rack/ApplicationSpawner/Error.html +98 -0
  90. data/doc/rdoc/classes/PhusionPassenger/Rack/RequestHandler.html +218 -0
  91. data/doc/rdoc/classes/PhusionPassenger/Rails3Extensions.html +114 -0
  92. data/doc/rdoc/classes/PhusionPassenger/Rails3Extensions/AnalyticsLogging.html +259 -0
  93. data/doc/rdoc/classes/PhusionPassenger/Rails3Extensions/AnalyticsLogging/ACExtension.html +139 -0
  94. data/doc/rdoc/classes/PhusionPassenger/Rails3Extensions/AnalyticsLogging/ASBenchmarkableExtension.html +118 -0
  95. data/doc/rdoc/classes/PhusionPassenger/Rails3Extensions/AnalyticsLogging/ExceptionLogger.html +135 -0
  96. data/doc/rdoc/classes/PhusionPassenger/SpawnManager.html +378 -0
  97. data/doc/rdoc/classes/PhusionPassenger/Standalone.html +111 -0
  98. data/doc/rdoc/classes/PhusionPassenger/Standalone/AppFinder.html +252 -0
  99. data/doc/rdoc/classes/PhusionPassenger/Standalone/Command.html +161 -0
  100. data/doc/rdoc/classes/PhusionPassenger/Standalone/ConfigFile.html +368 -0
  101. data/doc/rdoc/classes/PhusionPassenger/Standalone/ConfigFile/DisallowedContextError.html +132 -0
  102. data/doc/rdoc/classes/PhusionPassenger/Standalone/HelpCommand.html +151 -0
  103. data/doc/rdoc/classes/PhusionPassenger/Standalone/Main.html +189 -0
  104. data/doc/rdoc/classes/PhusionPassenger/Standalone/PackageRuntimeCommand.html +177 -0
  105. data/doc/rdoc/classes/PhusionPassenger/Standalone/RuntimeInstaller.html +341 -0
  106. data/doc/rdoc/classes/PhusionPassenger/Standalone/StartCommand.html +203 -0
  107. data/doc/rdoc/classes/PhusionPassenger/Standalone/StatusCommand.html +156 -0
  108. data/doc/rdoc/classes/PhusionPassenger/Standalone/StopCommand.html +168 -0
  109. data/doc/rdoc/classes/PhusionPassenger/Standalone/Utils.html +86 -0
  110. data/doc/rdoc/classes/PhusionPassenger/Standalone/VersionCommand.html +135 -0
  111. data/doc/rdoc/classes/PhusionPassenger/UnknownError.html +125 -0
  112. data/doc/rdoc/classes/PhusionPassenger/Utils.html +1543 -0
  113. data/doc/rdoc/classes/PhusionPassenger/Utils/FileSystemWatcher.html +204 -0
  114. data/doc/rdoc/classes/PhusionPassenger/Utils/FileSystemWatcher/DirInfo.html +171 -0
  115. data/doc/rdoc/classes/PhusionPassenger/Utils/FileSystemWatcher/FileInfo.html +140 -0
  116. data/doc/rdoc/classes/PhusionPassenger/Utils/HostsFileParser.html +260 -0
  117. data/doc/rdoc/classes/PhusionPassenger/Utils/PseudoIO.html +194 -0
  118. data/doc/rdoc/classes/PhusionPassenger/Utils/RewindableInput.html +265 -0
  119. data/doc/rdoc/classes/PhusionPassenger/Utils/RewindableInput/Tempfile.html +120 -0
  120. data/doc/rdoc/classes/PhusionPassenger/Utils/UnseekableSocket.html +561 -0
  121. data/doc/rdoc/classes/PhusionPassenger/VersionNotFound.html +140 -0
  122. data/doc/rdoc/classes/PhusionPassenger/WSGI.html +89 -0
  123. data/doc/rdoc/classes/PhusionPassenger/WSGI/ApplicationSpawner.html +182 -0
  124. data/doc/rdoc/classes/Process.html +115 -0
  125. data/doc/rdoc/classes/Signal.html +139 -0
  126. data/doc/rdoc/created.rid +1 -0
  127. data/doc/rdoc/files/DEVELOPERS_TXT.html +280 -0
  128. data/doc/rdoc/files/README.html +157 -0
  129. data/doc/rdoc/files/lib/phusion_passenger/abstract_installer_rb.html +130 -0
  130. data/doc/rdoc/files/lib/phusion_passenger/abstract_request_handler_rb.html +135 -0
  131. data/doc/rdoc/files/lib/phusion_passenger/abstract_server_collection_rb.html +126 -0
  132. data/doc/rdoc/files/lib/phusion_passenger/abstract_server_rb.html +128 -0
  133. data/doc/rdoc/files/lib/phusion_passenger/admin_tools/memory_stats_rb.html +126 -0
  134. data/doc/rdoc/files/lib/phusion_passenger/admin_tools/server_instance_rb.html +132 -0
  135. data/doc/rdoc/files/lib/phusion_passenger/admin_tools_rb.html +122 -0
  136. data/doc/rdoc/files/lib/phusion_passenger/analytics_logger_rb.html +129 -0
  137. data/doc/rdoc/files/lib/phusion_passenger/app_process_rb.html +127 -0
  138. data/doc/rdoc/files/lib/phusion_passenger/classic_rails/application_spawner_rb.html +141 -0
  139. data/doc/rdoc/files/lib/phusion_passenger/classic_rails/cgi_fixed_rb.html +126 -0
  140. data/doc/rdoc/files/lib/phusion_passenger/classic_rails/framework_spawner_rb.html +146 -0
  141. data/doc/rdoc/files/lib/phusion_passenger/classic_rails/request_handler_rb.html +125 -0
  142. data/doc/rdoc/files/lib/phusion_passenger/classic_rails_extensions/init_rb.html +132 -0
  143. data/doc/rdoc/files/lib/phusion_passenger/console_text_template_rb.html +126 -0
  144. data/doc/rdoc/files/lib/phusion_passenger/constants_rb.html +122 -0
  145. data/doc/rdoc/files/lib/phusion_passenger/debug_logging_rb.html +122 -0
  146. data/doc/rdoc/files/lib/phusion_passenger/dependencies_rb.html +147 -0
  147. data/doc/rdoc/files/lib/phusion_passenger/exceptions_rb.html +122 -0
  148. data/doc/rdoc/files/lib/phusion_passenger/html_template_rb.html +127 -0
  149. data/doc/rdoc/files/lib/phusion_passenger/message_channel_rb.html +120 -0
  150. data/doc/rdoc/files/lib/phusion_passenger/message_client_rb.html +127 -0
  151. data/doc/rdoc/files/lib/phusion_passenger/native_support_rb.html +132 -0
  152. data/doc/rdoc/files/lib/phusion_passenger/packaging_rb.html +122 -0
  153. data/doc/rdoc/files/lib/phusion_passenger/platform_info/apache_rb.html +127 -0
  154. data/doc/rdoc/files/lib/phusion_passenger/platform_info/binary_compatibility_rb.html +129 -0
  155. data/doc/rdoc/files/lib/phusion_passenger/platform_info/compiler_rb.html +127 -0
  156. data/doc/rdoc/files/lib/phusion_passenger/platform_info/curl_rb.html +126 -0
  157. data/doc/rdoc/files/lib/phusion_passenger/platform_info/documentation_tools_rb.html +126 -0
  158. data/doc/rdoc/files/lib/phusion_passenger/platform_info/linux_rb.html +126 -0
  159. data/doc/rdoc/files/lib/phusion_passenger/platform_info/operating_system_rb.html +127 -0
  160. data/doc/rdoc/files/lib/phusion_passenger/platform_info/ruby_rb.html +128 -0
  161. data/doc/rdoc/files/lib/phusion_passenger/platform_info/zlib_rb.html +126 -0
  162. data/doc/rdoc/files/lib/phusion_passenger/platform_info_rb.html +122 -0
  163. data/doc/rdoc/files/lib/phusion_passenger/plugin_rb.html +127 -0
  164. data/doc/rdoc/files/lib/phusion_passenger/public_api_rb.html +127 -0
  165. data/doc/rdoc/files/lib/phusion_passenger/rack/application_spawner_rb.html +137 -0
  166. data/doc/rdoc/files/lib/phusion_passenger/rack/request_handler_rb.html +125 -0
  167. data/doc/rdoc/files/lib/phusion_passenger/rails3_extensions/init_rb.html +127 -0
  168. data/doc/rdoc/files/lib/phusion_passenger/simple_benchmarking_rb.html +122 -0
  169. data/doc/rdoc/files/lib/phusion_passenger/spawn_manager_rb.html +160 -0
  170. data/doc/rdoc/files/lib/phusion_passenger/standalone/app_finder_rb.html +127 -0
  171. data/doc/rdoc/files/lib/phusion_passenger/standalone/command_rb.html +136 -0
  172. data/doc/rdoc/files/lib/phusion_passenger/standalone/config_file_rb.html +126 -0
  173. data/doc/rdoc/files/lib/phusion_passenger/standalone/help_command_rb.html +126 -0
  174. data/doc/rdoc/files/lib/phusion_passenger/standalone/main_rb.html +126 -0
  175. data/doc/rdoc/files/lib/phusion_passenger/standalone/package_runtime_command_rb.html +127 -0
  176. data/doc/rdoc/files/lib/phusion_passenger/standalone/runtime_installer_rb.html +133 -0
  177. data/doc/rdoc/files/lib/phusion_passenger/standalone/start_command_rb.html +136 -0
  178. data/doc/rdoc/files/lib/phusion_passenger/standalone/status_command_rb.html +126 -0
  179. data/doc/rdoc/files/lib/phusion_passenger/standalone/stop_command_rb.html +126 -0
  180. data/doc/rdoc/files/lib/phusion_passenger/standalone/utils_rb.html +126 -0
  181. data/doc/rdoc/files/lib/phusion_passenger/standalone/version_command_rb.html +127 -0
  182. data/doc/rdoc/files/lib/phusion_passenger/utils/file_system_watcher_rb.html +126 -0
  183. data/doc/rdoc/files/lib/phusion_passenger/utils/hosts_file_parser_rb.html +120 -0
  184. data/doc/rdoc/files/lib/phusion_passenger/utils/rewindable_input_rb.html +100 -0
  185. data/doc/rdoc/files/lib/phusion_passenger/utils/tmpdir_rb.html +122 -0
  186. data/doc/rdoc/files/lib/phusion_passenger/utils/unseekable_socket_rb.html +126 -0
  187. data/doc/rdoc/files/lib/phusion_passenger/utils_rb.html +179 -0
  188. data/doc/rdoc/files/lib/phusion_passenger/wsgi/application_spawner_rb.html +132 -0
  189. data/doc/rdoc/fr_class_index.html +139 -0
  190. data/doc/rdoc/fr_file_index.html +108 -0
  191. data/doc/rdoc/fr_method_index.html +439 -0
  192. data/doc/rdoc/index.html +26 -0
  193. data/doc/rdoc/rdoc-style.css +187 -0
  194. data/ext/apache2/Configuration.cpp +41 -0
  195. data/ext/apache2/Configuration.hpp +19 -0
  196. data/ext/apache2/Hooks.cpp +67 -5
  197. data/ext/common/Constants.h +1 -1
  198. data/ext/common/Exceptions.h +14 -0
  199. data/ext/common/Logging.h +76 -6
  200. data/ext/common/LoggingAgent/FilterSupport.h +1317 -0
  201. data/ext/common/LoggingAgent/LoggingServer.h +93 -4
  202. data/ext/common/LoggingAgent/RemoteSender.h +5 -5
  203. data/ext/common/Utils/StrIntUtils.cpp +12 -1
  204. data/ext/common/Utils/StrIntUtils.h +2 -1
  205. data/ext/common/Utils/StringMap.h +100 -0
  206. data/ext/nginx/Configuration.c +0 -1
  207. data/ext/nginx/ContentHandler.c +33 -7
  208. data/lib/phusion_passenger.rb +2 -2
  209. data/lib/phusion_passenger/abstract_request_handler.rb +5 -0
  210. data/lib/phusion_passenger/analytics_logger.rb +77 -11
  211. data/lib/phusion_passenger/dependencies.rb +11 -1
  212. data/lib/phusion_passenger/platform_info.rb +1 -1
  213. data/lib/phusion_passenger/utils.rb +9 -1
  214. data/test/cxx/FilterSupportTest.cpp +276 -0
  215. data/test/cxx/LoggingTest.cpp +28 -0
  216. data/test/cxx/StringMapTest.cpp +70 -0
  217. data/test/integration_tests/cgi_environment_spec.rb +10 -0
  218. metadata +170 -8
  219. data/ext/common/libboost_oxt/aggregate.cpp +0 -10
  220. data/ext/common/libpassenger_common/aggregate.cpp +0 -15
@@ -430,6 +430,47 @@ public:
430
430
 
431
431
  class AnalyticsLogger {
432
432
  private:
433
+ /** A special lock type for AnalyticsLoggerSharedData that also
434
+ * keeps a smart pointer to the data structure so that the mutex
435
+ * is not destroyed prematurely.
436
+ */
437
+ struct SharedDataLock {
438
+ AnalyticsLoggerSharedDataPtr sharedData;
439
+ bool locked;
440
+
441
+ SharedDataLock(const AnalyticsLoggerSharedDataPtr &d)
442
+ : sharedData(d)
443
+ {
444
+ d->lock.lock();
445
+ locked = true;
446
+ }
447
+
448
+ ~SharedDataLock() {
449
+ if (locked) {
450
+ sharedData->lock.unlock();
451
+ }
452
+ }
453
+
454
+ void reset(const AnalyticsLoggerSharedDataPtr &d, bool lockNow = true) {
455
+ if (locked) {
456
+ sharedData->lock.unlock();
457
+ }
458
+ sharedData = d;
459
+ if (lockNow) {
460
+ sharedData->lock.lock();
461
+ locked = true;
462
+ } else {
463
+ locked = false;
464
+ }
465
+ }
466
+
467
+ void lock() {
468
+ assert(!locked);
469
+ sharedData->lock.lock();
470
+ locked = true;
471
+ }
472
+ };
473
+
433
474
  static const int RETRY_SLEEP = 200000; // microseconds
434
475
 
435
476
  string serverAddress;
@@ -437,10 +478,13 @@ private:
437
478
  string password;
438
479
  string nodeName;
439
480
  RandomGenerator randomGenerator;
481
+
482
+ /** Lock protecting the fields that follow, but not the contents of the shared data. */
483
+ mutable boost::mutex lock;
484
+
440
485
  unsigned int maxConnectTries;
441
486
  unsigned long long reconnectTimeout;
442
487
  unsigned long long nextReconnectTime;
443
-
444
488
  /** @invariant sharedData != NULL */
445
489
  AnalyticsLoggerSharedDataPtr sharedData;
446
490
 
@@ -511,7 +555,8 @@ public:
511
555
  }
512
556
 
513
557
  AnalyticsLogPtr newTransaction(const string &groupName, const string &category = "requests",
514
- const string &unionStationKey = string())
558
+ const string &unionStationKey = string(),
559
+ const string &filters = string())
515
560
  {
516
561
  if (serverAddress.empty()) {
517
562
  return ptr(new AnalyticsLog());
@@ -546,7 +591,8 @@ public:
546
591
 
547
592
  integerToHexatri<unsigned long long>(timestamp, timestampStr);
548
593
 
549
- lock_guard<boost::mutex> l(sharedData->lock);
594
+ unique_lock<boost::mutex> l(lock);
595
+ SharedDataLock sl(sharedData);
550
596
 
551
597
  if (SystemTime::getUsec() >= nextReconnectTime) {
552
598
  unsigned int tryCount = 0;
@@ -565,7 +611,20 @@ public:
565
611
  timestampStr,
566
612
  unionStationKey.c_str(),
567
613
  "true",
614
+ "true",
615
+ filters.c_str(),
568
616
  NULL);
617
+
618
+ vector<string> args;
619
+ sharedData->client.read(args);
620
+ if (args.size() == 2 && args[0] == "error") {
621
+ disconnect();
622
+ throw IOException("The logging server responded with an error: " + args[1]);
623
+ } else if (args.empty() || args[0] != "ok") {
624
+ disconnect();
625
+ throw IOException("The logging server sent an unexpected reply.");
626
+ }
627
+
569
628
  return ptr(new AnalyticsLog(sharedData,
570
629
  string(txnId, end - txnId),
571
630
  groupName, category,
@@ -575,9 +634,13 @@ public:
575
634
  if (e.code() == ENOENT || isNetworkError(e.code())) {
576
635
  tryCount++;
577
636
  disconnect(true);
637
+ sl.reset(sharedData, false);
638
+ l.unlock();
578
639
  if (tryCount < maxConnectTries) {
579
640
  syscalls::usleep(RETRY_SLEEP);
580
641
  }
642
+ l.lock();
643
+ sl.lock();
581
644
  } else {
582
645
  disconnect();
583
646
  throw;
@@ -603,7 +666,8 @@ public:
603
666
  char timestampStr[2 * sizeof(unsigned long long) + 1];
604
667
  integerToHexatri<unsigned long long>(SystemTime::getUsec(), timestampStr);
605
668
 
606
- lock_guard<boost::mutex> l(sharedData->lock);
669
+ unique_lock<boost::mutex> l(lock);
670
+ SharedDataLock sl(sharedData);
607
671
 
608
672
  if (SystemTime::getUsec() >= nextReconnectTime) {
609
673
  unsigned int tryCount = 0;
@@ -631,9 +695,13 @@ public:
631
695
  if (e.code() == EPIPE || isNetworkError(e.code())) {
632
696
  tryCount++;
633
697
  disconnect(true);
698
+ sl.reset(sharedData, false);
699
+ l.unlock();
634
700
  if (tryCount < maxConnectTries) {
635
701
  syscalls::usleep(RETRY_SLEEP);
636
702
  }
703
+ l.lock();
704
+ sl.lock();
637
705
  } else {
638
706
  disconnect();
639
707
  throw;
@@ -650,12 +718,12 @@ public:
650
718
  }
651
719
 
652
720
  void setMaxConnectTries(unsigned int value) {
653
- lock_guard<boost::mutex> l(sharedData->lock);
721
+ lock_guard<boost::mutex> l(lock);
654
722
  maxConnectTries = value;
655
723
  }
656
724
 
657
725
  void setReconnectTimeout(unsigned long long usec) {
658
- lock_guard<boost::mutex> l(sharedData->lock);
726
+ lock_guard<boost::mutex> l(lock);
659
727
  reconnectTimeout = usec;
660
728
  }
661
729
 
@@ -676,6 +744,8 @@ public:
676
744
  }
677
745
 
678
746
  FileDescriptor getConnection() const {
747
+ lock_guard<boost::mutex> l(lock);
748
+ lock_guard<boost::mutex> l2(sharedData->lock);
679
749
  return sharedData->client.getConnection();
680
750
  }
681
751
 
@@ -0,0 +1,1317 @@
1
+ /*
2
+ * Phusion Passenger - http://www.modrails.com/
3
+ * Copyright (c) 2011 Phusion
4
+ *
5
+ * "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui.
6
+ *
7
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ * of this software and associated documentation files (the "Software"), to deal
9
+ * in the Software without restriction, including without limitation the rights
10
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ * copies of the Software, and to permit persons to whom the Software is
12
+ * furnished to do so, subject to the following conditions:
13
+ *
14
+ * The above copyright notice and this permission notice shall be included in
15
+ * all copies or substantial portions of the Software.
16
+ *
17
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ * THE SOFTWARE.
24
+ */
25
+ #ifndef _PASSENGER_FILTER_SUPPORT_H_
26
+ #define _PASSENGER_FILTER_SUPPORT_H_
27
+
28
+ #include <boost/shared_ptr.hpp>
29
+ #include <boost/make_shared.hpp>
30
+ #include <oxt/tracable_exception.hpp>
31
+
32
+ #include <string>
33
+ #include <set>
34
+ #include <regex.h>
35
+ #include <cstring>
36
+
37
+ #include <StaticString.h>
38
+ #include <Utils/StrIntUtils.h>
39
+
40
+ namespace Passenger {
41
+ namespace FilterSupport {
42
+
43
+ using namespace std;
44
+ using namespace boost;
45
+ using namespace oxt;
46
+
47
+
48
+ class Tokenizer {
49
+ public:
50
+ enum TokenType {
51
+ NONE,
52
+ NOT,
53
+ AND,
54
+ OR,
55
+ MATCHES,
56
+ NOT_MATCHES,
57
+ EQUALS,
58
+ NOT_EQUALS,
59
+ GREATER_THAN,
60
+ GREATER_THAN_OR_EQUALS,
61
+ LESS_THAN,
62
+ LESS_THAN_OR_EQUALS,
63
+ LPARENTHESIS,
64
+ RPARENTHESIS,
65
+ COMMA,
66
+ REGEXP,
67
+ STRING,
68
+ INTEGER,
69
+ IDENTIFIER,
70
+ END_OF_DATA
71
+ };
72
+
73
+ enum TokenOptions {
74
+ NO_OPTIONS = 0,
75
+ REGEXP_OPTION_CASE_INSENSITIVE = 1
76
+ };
77
+
78
+ struct Token {
79
+ TokenType type;
80
+ int options;
81
+ unsigned int pos;
82
+ unsigned int size;
83
+ StaticString rawValue;
84
+
85
+ Token() {
86
+ type = NONE;
87
+ }
88
+
89
+ Token(TokenType _type, unsigned int _pos, unsigned int _size, const StaticString &_rawValue)
90
+ : type(_type),
91
+ options(NO_OPTIONS),
92
+ pos(_pos),
93
+ size(_size),
94
+ rawValue(_rawValue)
95
+ { }
96
+
97
+ string toString() const {
98
+ return Tokenizer::typeToString(type);
99
+ }
100
+ };
101
+
102
+ private:
103
+ StaticString data;
104
+ bool debug;
105
+ unsigned int pos;
106
+
107
+ static bool isWhitespace(char ch) {
108
+ return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n';
109
+ }
110
+
111
+ void skipWhitespaces() {
112
+ while (pos < data.size() && isWhitespace(data[pos])) {
113
+ pos++;
114
+ }
115
+ }
116
+
117
+ unsigned int available() const {
118
+ return data.size() - pos;
119
+ }
120
+
121
+ char current() const {
122
+ return data[pos];
123
+ }
124
+
125
+ char next() const {
126
+ return data[pos + 1];
127
+ }
128
+
129
+ static bool isLiteralChar(char ch) {
130
+ return (ch >= 'a' && ch <= 'z')
131
+ || (ch >= 'A' && ch <= 'Z')
132
+ || (ch >= '0' && ch <= '9')
133
+ || ch == '_';
134
+ }
135
+
136
+ static bool isDigit(char ch) {
137
+ return ch >= '0' && ch <= '9';
138
+ }
139
+
140
+ Token logToken(const Token &token) const {
141
+ if (debug) {
142
+ printf("Token: %s\n", token.toString().c_str());
143
+ }
144
+ return token;
145
+ }
146
+
147
+ void raiseSyntaxError(const string &message = "") {
148
+ string msg = "Syntax error at character " + toString(pos + 1);
149
+ if (!message.empty()) {
150
+ msg.append(": ");
151
+ msg.append(message);
152
+ }
153
+ throw SyntaxError(msg);
154
+ }
155
+
156
+ void expectingAtLeast(unsigned int size) {
157
+ if (available() < size) {
158
+ raiseSyntaxError("at least " + toString(size) +
159
+ " more characters expected");
160
+ }
161
+ }
162
+
163
+ void expectingNextChar(char ch) {
164
+ expectingAtLeast(2);
165
+ if (next() != ch) {
166
+ raiseSyntaxError("expected '" + toString(ch) +
167
+ "', but found '" + toString(next()) +
168
+ "'");
169
+ }
170
+ }
171
+
172
+ Token matchToken(TokenType type, unsigned int size = 0) {
173
+ unsigned int oldPos = pos;
174
+ pos += size;
175
+ return Token(type, oldPos, size, data.substr(oldPos, size));
176
+ }
177
+
178
+ Token matchTokensStartingWithNegation() {
179
+ expectingAtLeast(2);
180
+ switch (next()) {
181
+ case '~':
182
+ return matchToken(NOT_MATCHES, 2);
183
+ case '=':
184
+ return matchToken(NOT_EQUALS, 2);
185
+ default:
186
+ raiseSyntaxError("unrecognized operator '" + data.substr(pos, 2) + "'");
187
+ return Token(); // Shut up compiler warning.
188
+ };
189
+ }
190
+
191
+ Token matchAnd() {
192
+ expectingNextChar('&');
193
+ return matchToken(AND, 2);
194
+ }
195
+
196
+ Token matchOr() {
197
+ expectingNextChar('|');
198
+ return matchToken(OR, 2);
199
+ }
200
+
201
+ Token matchTokensStartingWithEquals() {
202
+ expectingAtLeast(2);
203
+ switch (next()) {
204
+ case '~':
205
+ return matchToken(MATCHES, 2);
206
+ case '=':
207
+ return matchToken(EQUALS, 2);
208
+ default:
209
+ raiseSyntaxError("unrecognized operator '" + data.substr(pos, 2) + "'");
210
+ return Token(); // Shut up compiler warning.
211
+ }
212
+ }
213
+
214
+ Token matchTokensStartingWithGreaterThan() {
215
+ if (available() == 0 || next() != '=') {
216
+ return matchToken(GREATER_THAN, 1);
217
+ } else {
218
+ return matchToken(GREATER_THAN_OR_EQUALS, 2);
219
+ }
220
+ }
221
+
222
+ Token matchTokensStartingWithLessThan() {
223
+ if (available() == 0 || next() != '=') {
224
+ return matchToken(LESS_THAN, 1);
225
+ } else {
226
+ return matchToken(LESS_THAN_OR_EQUALS, 2);
227
+ }
228
+ }
229
+
230
+ Token matchRegexp() {
231
+ unsigned int start = pos;
232
+ bool endFound = false;
233
+
234
+ // Match initial quote slash.
235
+ pos++;
236
+
237
+ // Match rest of regexp including terminating slash.
238
+ while (pos < data.size() && !endFound) {
239
+ char ch = current();
240
+ if (ch == '\\') {
241
+ pos++;
242
+ if (pos >= data.size()) {
243
+ raiseSyntaxError("unterminated regular expression");
244
+ } else {
245
+ pos++;
246
+ }
247
+ } else if (ch == '/') {
248
+ pos++;
249
+ endFound = true;
250
+ } else {
251
+ pos++;
252
+ }
253
+ }
254
+
255
+ if (endFound) {
256
+ Token t(REGEXP, start, pos - start, data.substr(start, pos - start));
257
+
258
+ // Match regexp options.
259
+ endFound = false;
260
+ while (pos < data.size() && !endFound) {
261
+ char ch = current();
262
+ if (ch == 'i') {
263
+ t.options |= Tokenizer::REGEXP_OPTION_CASE_INSENSITIVE;
264
+ } else if (isWhitespace(ch)) {
265
+ endFound = true;
266
+ }
267
+ pos++;
268
+ }
269
+
270
+ return t;
271
+ } else {
272
+ raiseSyntaxError("unterminated regular expression");
273
+ return Token(); // Shut up compiler warning.
274
+ }
275
+ }
276
+
277
+ Token matchString() {
278
+ unsigned int start = pos;
279
+ bool endFound = false;
280
+
281
+ // Match initial quote character.
282
+ pos++;
283
+
284
+ // Match rest of string including terminating quote.
285
+ while (pos < data.size() && !endFound) {
286
+ char ch = current();
287
+ if (ch == '\\') {
288
+ pos++;
289
+ if (pos >= data.size()) {
290
+ raiseSyntaxError("unterminated string");
291
+ } else {
292
+ pos++;
293
+ }
294
+ } else if (ch == '"') {
295
+ pos++;
296
+ endFound = true;
297
+ } else {
298
+ pos++;
299
+ }
300
+ }
301
+
302
+ if (endFound) {
303
+ return Token(STRING, start, pos - start, data.substr(start, pos - start));
304
+ } else {
305
+ raiseSyntaxError("unterminated string");
306
+ return Token(); // Shut up compiler warning.
307
+ }
308
+ }
309
+
310
+ Token matchInteger() {
311
+ unsigned int start = pos;
312
+
313
+ // Accept initial minus or digit.
314
+ pos++;
315
+
316
+ while (pos < data.size() && isDigit(data[pos])) {
317
+ pos++;
318
+ }
319
+
320
+ return Token(INTEGER, start, pos - start, data.substr(start, pos - start));
321
+ }
322
+
323
+ Token matchIdentifier() {
324
+ char ch = current();
325
+ if ((ch >= 'a' && ch <= 'z') ||
326
+ (ch >= 'A' && ch <= 'Z') ||
327
+ ch == '_') {
328
+ unsigned int start = pos;
329
+ pos++;
330
+ while (pos < data.size() && isLiteralChar(current())) {
331
+ pos++;
332
+ }
333
+ return Token(IDENTIFIER, start, pos - start, data.substr(start, pos - start));
334
+ } else {
335
+ raiseSyntaxError();
336
+ return Token(); // Shut up compiler warning.
337
+ }
338
+ }
339
+
340
+ public:
341
+ Tokenizer(const StaticString &data, bool debug = false) {
342
+ this->data = data;
343
+ this->debug = debug;
344
+ pos = 0;
345
+ }
346
+
347
+ Token getNext() {
348
+ skipWhitespaces();
349
+ if (pos >= data.size()) {
350
+ return logToken(Token(END_OF_DATA, data.size(), 0, ""));
351
+ }
352
+
353
+ switch (current()) {
354
+ case '!':
355
+ return logToken(matchTokensStartingWithNegation());
356
+ case '&':
357
+ return logToken(matchAnd());
358
+ case '|':
359
+ return logToken(matchOr());
360
+ case '=':
361
+ return logToken(matchTokensStartingWithEquals());
362
+ case '>':
363
+ return logToken(matchTokensStartingWithGreaterThan());
364
+ case '<':
365
+ return logToken(matchTokensStartingWithLessThan());
366
+ case '(':
367
+ return logToken(matchToken(LPARENTHESIS, 1));
368
+ case ')':
369
+ return logToken(matchToken(RPARENTHESIS, 1));
370
+ case ',':
371
+ return logToken(matchToken(COMMA, 1));
372
+ case '/':
373
+ return logToken(matchRegexp());
374
+ case '"':
375
+ return logToken(matchString());
376
+ case '-':
377
+ return logToken(matchInteger());
378
+ default:
379
+ if (isDigit(current())) {
380
+ return logToken(matchInteger());
381
+ } else {
382
+ return logToken(matchIdentifier());
383
+ }
384
+ }
385
+ }
386
+
387
+ static string typeToString(TokenType type) {
388
+ switch (type) {
389
+ case NONE:
390
+ return "NONE";
391
+ case NOT:
392
+ return "NOT";
393
+ case AND:
394
+ return "AND";
395
+ case OR:
396
+ return "OR";
397
+ case MATCHES:
398
+ return "MATCHES";
399
+ case NOT_MATCHES:
400
+ return "NOT_MATCHES";
401
+ case EQUALS:
402
+ return "EQUALS";
403
+ case NOT_EQUALS:
404
+ return "NOT_EQUALS";
405
+ case GREATER_THAN:
406
+ return "GREATER_THAN";
407
+ case GREATER_THAN_OR_EQUALS:
408
+ return "GREATER_THAN_OR_EQUALS";
409
+ case LESS_THAN:
410
+ return "LESS_THAN";
411
+ case LESS_THAN_OR_EQUALS:
412
+ return "LESS_THAN_OR_EQUALS";
413
+ case LPARENTHESIS:
414
+ return "LPARENTHESIS";
415
+ case RPARENTHESIS:
416
+ return "RPARENTHESIS";
417
+ case COMMA:
418
+ return "COMMA";
419
+ case REGEXP:
420
+ return "REGEXP";
421
+ case STRING:
422
+ return "STRING";
423
+ case INTEGER:
424
+ return "INTEGER";
425
+ case IDENTIFIER:
426
+ return "IDENTIFIER";
427
+ case END_OF_DATA:
428
+ return "END_OF_DATA";
429
+ default:
430
+ return "(unknown)";
431
+ }
432
+ }
433
+ };
434
+
435
+
436
+ enum ValueType {
437
+ REGEXP_TYPE,
438
+ STRING_TYPE,
439
+ INTEGER_TYPE,
440
+ UNKNOWN_TYPE
441
+ };
442
+
443
+
444
+ class Context {
445
+ public:
446
+ enum FieldIdentifier {
447
+ URI,
448
+ CONTROLLER,
449
+ RESPONSE_TIME
450
+ };
451
+
452
+ virtual string getURI() const = 0;
453
+ virtual string getController() const = 0;
454
+ virtual int getResponseTime() const = 0;
455
+ virtual bool hasHint(const string &name) const = 0;
456
+
457
+ string queryStringField(FieldIdentifier id) const {
458
+ switch (id) {
459
+ case URI:
460
+ return getURI();
461
+ case CONTROLLER:
462
+ return getController();
463
+ case RESPONSE_TIME:
464
+ return toString(getResponseTime());
465
+ default:
466
+ return "";
467
+ }
468
+ }
469
+
470
+ int queryIntField(FieldIdentifier id) const {
471
+ switch (id) {
472
+ case RESPONSE_TIME:
473
+ return getResponseTime();
474
+ default:
475
+ return 0;
476
+ }
477
+ }
478
+
479
+ static ValueType getFieldType(FieldIdentifier id) {
480
+ switch (id) {
481
+ case URI:
482
+ case CONTROLLER:
483
+ return STRING_TYPE;
484
+ case RESPONSE_TIME:
485
+ return INTEGER_TYPE;
486
+ default:
487
+ return UNKNOWN_TYPE;
488
+ }
489
+ }
490
+ };
491
+
492
+ class SimpleContext: public Context {
493
+ public:
494
+ string uri;
495
+ string controller;
496
+ int responseTime;
497
+ set<string> hints;
498
+
499
+ SimpleContext() {
500
+ responseTime = 0;
501
+ }
502
+
503
+ virtual string getURI() const {
504
+ return uri;
505
+ }
506
+
507
+ virtual string getController() const {
508
+ return controller;
509
+ }
510
+
511
+ virtual int getResponseTime() const {
512
+ return responseTime;
513
+ }
514
+
515
+ virtual bool hasHint(const string &name) const {
516
+ return hints.find(name) != hints.end();
517
+ }
518
+ };
519
+
520
+ class ContextFromLog: public Context {
521
+ private:
522
+ StaticString logData;
523
+ mutable SimpleContext *parsedData;
524
+
525
+ struct ParseState {
526
+ unsigned long long requestProcessingStart;
527
+ unsigned long long requestProcessingEnd;
528
+ };
529
+
530
+ static void parseLine(const StaticString &txnId, unsigned long long timestamp,
531
+ const StaticString &data, SimpleContext &ctx, ParseState &state)
532
+ {
533
+ if (startsWith(data, "BEGIN: request processing")) {
534
+ state.requestProcessingStart = extractEventTimestamp(data);
535
+ } else if (startsWith(data, "END: request processing")
536
+ || startsWith(data, "FAIL: request processing")) {
537
+ state.requestProcessingEnd = extractEventTimestamp(data);
538
+ } else if (startsWith(data, "URI: ")) {
539
+ ctx.uri = data.substr(data.find(':') + 2);
540
+ } else if (startsWith(data, "Controller action: ")) {
541
+ StaticString value = data.substr(data.find(':') + 2);
542
+ size_t pos = value.find('#');
543
+ if (pos != string::npos) {
544
+ ctx.controller = value.substr(0, pos);
545
+ }
546
+ }
547
+ }
548
+
549
+ static void reallyParse(const StaticString &data, SimpleContext &ctx) {
550
+ const char *current = data.data();
551
+ const char *end = data.data() + data.size();
552
+ ParseState state;
553
+
554
+ memset(&state, 0, sizeof(state));
555
+ while (current < end) {
556
+ current = skipNewlines(current, end);
557
+ if (current < end) {
558
+ const char *endOfLine = findEndOfLine(current, end);
559
+ StaticString line(current, endOfLine - current);
560
+ if (!line.empty()) {
561
+ StaticString txnId;
562
+ unsigned long long timestamp;
563
+ unsigned int writeCount;
564
+ StaticString lineData;
565
+
566
+ // If we want to do more complicated analysis we should sort
567
+ // the lines but for the purposes of ContextFromLog
568
+ // analyzing the data without sorting is good enough.
569
+ if (splitLine(line, txnId, timestamp, writeCount, lineData)) {
570
+ parseLine(txnId, timestamp, lineData, ctx,
571
+ state);
572
+ }
573
+ }
574
+ current = endOfLine;
575
+ }
576
+ }
577
+
578
+ if (state.requestProcessingEnd != 0) {
579
+ ctx.responseTime = int(state.requestProcessingEnd -
580
+ state.requestProcessingStart);
581
+ }
582
+ }
583
+
584
+ static bool splitLine(const StaticString &line, StaticString &txnId,
585
+ unsigned long long &timestamp, unsigned int &writeCount,
586
+ StaticString &data)
587
+ {
588
+ size_t firstDelim = line.find(' ');
589
+ if (firstDelim == string::npos) {
590
+ return false;
591
+ }
592
+
593
+ size_t secondDelim = line.find(' ', firstDelim + 1);
594
+ if (secondDelim == string::npos) {
595
+ return false;
596
+ }
597
+
598
+ size_t thirdDelim = line.find(' ', secondDelim + 1);
599
+ if (thirdDelim == string::npos) {
600
+ return false;
601
+ }
602
+
603
+ txnId = line.substr(0, firstDelim);
604
+ timestamp = hexatriToULL(line.substr(firstDelim + 1, secondDelim - firstDelim - 1));
605
+ writeCount = (unsigned int) hexatriToULL(line.substr(secondDelim + 1,
606
+ thirdDelim - secondDelim - 1));
607
+ data = line.substr(thirdDelim + 1);
608
+ return true;
609
+ }
610
+
611
+ static unsigned long long extractEventTimestamp(const StaticString &data) {
612
+ size_t pos = data.find('(');
613
+ if (pos == string::npos) {
614
+ return 0;
615
+ } else {
616
+ pos++;
617
+ size_t start = pos;
618
+ while (pos < data.size() && isDigit(data[pos])) {
619
+ pos++;
620
+ }
621
+ if (pos >= data.size()) {
622
+ return 0;
623
+ } else {
624
+ return hexatriToULL(data.substr(start, pos - start));
625
+ }
626
+ }
627
+ }
628
+
629
+ static bool isNewline(char ch) {
630
+ return ch == '\n' || ch == '\r';
631
+ }
632
+
633
+ static bool isDigit(char ch) {
634
+ return ch >= '0' && ch <= '9';
635
+ }
636
+
637
+ static const char *skipNewlines(const char *current, const char *end) {
638
+ while (current < end && isNewline(*current)) {
639
+ current++;
640
+ }
641
+ return current;
642
+ }
643
+
644
+ static const char *findEndOfLine(const char *current, const char *end) {
645
+ while (current < end && !isNewline(*current)) {
646
+ current++;
647
+ }
648
+ return current;
649
+ }
650
+
651
+ SimpleContext *parse() const {
652
+ if (parsedData == NULL) {
653
+ auto_ptr<SimpleContext> ctx(new SimpleContext());
654
+ reallyParse(logData, *ctx.get());
655
+ parsedData = ctx.release();
656
+ }
657
+ return parsedData;
658
+ }
659
+
660
+ public:
661
+ ContextFromLog(const StaticString &logData) {
662
+ this->logData = logData;
663
+ parsedData = NULL;
664
+ }
665
+
666
+ ~ContextFromLog() {
667
+ delete parsedData;
668
+ }
669
+
670
+ virtual string getURI() const {
671
+ return parse()->uri;
672
+ }
673
+
674
+ virtual string getController() const {
675
+ return parse()->getController();
676
+ }
677
+
678
+ virtual int getResponseTime() const {
679
+ return parse()->getResponseTime();
680
+ }
681
+
682
+ virtual bool hasHint(const string &name) const {
683
+ return parse()->hasHint(name);
684
+ }
685
+ };
686
+
687
+
688
+ class Filter {
689
+ private:
690
+ typedef Tokenizer::Token Token;
691
+ typedef Tokenizer::TokenType TokenType;
692
+
693
+ struct BooleanComponent;
694
+ struct MultiExpression;
695
+ struct Comparison;
696
+ struct FunctionCall;
697
+ typedef shared_ptr<BooleanComponent> BooleanComponentPtr;
698
+ typedef shared_ptr<MultiExpression> MultiExpressionPtr;
699
+ typedef shared_ptr<Comparison> ComparisonPtr;
700
+ typedef shared_ptr<FunctionCall> FunctionCallPtr;
701
+
702
+ struct BooleanComponent {
703
+ virtual bool evaluate(const Context &ctx) = 0;
704
+ };
705
+
706
+ enum LogicalOperator {
707
+ AND,
708
+ OR
709
+ };
710
+
711
+ enum Comparator {
712
+ MATCHES,
713
+ NOT_MATCHES,
714
+ EQUALS,
715
+ NOT_EQUALS,
716
+ GREATER_THAN,
717
+ GREATER_THAN_OR_EQUALS,
718
+ LESS_THAN,
719
+ LESS_THAN_OR_EQUALS
720
+ };
721
+
722
+ struct MultiExpression: public BooleanComponent {
723
+ struct Part {
724
+ LogicalOperator theOperator;
725
+ BooleanComponentPtr expression;
726
+ };
727
+
728
+ BooleanComponentPtr firstExpression;
729
+ vector<Part> rest;
730
+
731
+ virtual bool evaluate(const Context &ctx) {
732
+ bool result = firstExpression->evaluate(ctx);
733
+ vector<Part>::iterator it = rest.begin(), end = rest.end();
734
+ while (it != end && result) {
735
+ Part &part = *it;
736
+ if (part.theOperator == AND) {
737
+ result = result && part.expression->evaluate(ctx);
738
+ } else {
739
+ result = result || part.expression->evaluate(ctx);
740
+ }
741
+ it++;
742
+ }
743
+ return result;
744
+ }
745
+ };
746
+
747
+ struct Negation: public BooleanComponent {
748
+ BooleanComponentPtr expr;
749
+
750
+ Negation(const BooleanComponentPtr &e)
751
+ : expr(e)
752
+ { }
753
+
754
+ virtual bool evaluate(const Context &ctx) {
755
+ return !expr->evaluate(ctx);
756
+ }
757
+ };
758
+
759
+ struct Value {
760
+ enum Source {
761
+ REGEXP_LITERAL,
762
+ STRING_LITERAL,
763
+ INTEGER_LITERAL,
764
+ CONTEXT_FIELD_IDENTIFIER
765
+ };
766
+
767
+ Source source;
768
+ union {
769
+ struct {
770
+ char stringStorage[sizeof(string)];
771
+ struct {
772
+ regex_t regexp;
773
+ int options;
774
+ } regexp;
775
+ } stringOrRegexpValue;
776
+ int intValue;
777
+ Context::FieldIdentifier contextFieldIdentifier;
778
+ } u;
779
+
780
+ Value() {
781
+ source = INTEGER_LITERAL;
782
+ u.intValue = 0;
783
+ }
784
+
785
+ Value(const Value &other) {
786
+ initializeFrom(other);
787
+ }
788
+
789
+ Value(bool regexp, const StaticString &value, bool caseInsensitive = false) {
790
+ if (regexp) {
791
+ source = REGEXP_LITERAL;
792
+ } else {
793
+ source = STRING_LITERAL;
794
+ }
795
+ new (u.stringOrRegexpValue.stringStorage) string(value.data(), value.size());
796
+ if (regexp) {
797
+ int options = REG_EXTENDED;
798
+ if (caseInsensitive) {
799
+ options |= REG_ICASE;
800
+ u.stringOrRegexpValue.regexp.options |=
801
+ Tokenizer::REGEXP_OPTION_CASE_INSENSITIVE;
802
+ }
803
+ regcomp(&u.stringOrRegexpValue.regexp.regexp,
804
+ value.toString().c_str(),
805
+ options);
806
+ }
807
+ }
808
+
809
+ Value(int val) {
810
+ source = INTEGER_LITERAL;
811
+ u.intValue = val;
812
+ }
813
+
814
+ Value(Context::FieldIdentifier identifier) {
815
+ source = CONTEXT_FIELD_IDENTIFIER;
816
+ u.contextFieldIdentifier = identifier;
817
+ }
818
+
819
+ ~Value() {
820
+ freeStorage();
821
+ }
822
+
823
+ Value &operator=(const Value &other) {
824
+ freeStorage();
825
+ initializeFrom(other);
826
+ return *this;
827
+ }
828
+
829
+ regex_t *getRegexpValue(const Context &ctx) const {
830
+ if (source == REGEXP_LITERAL) {
831
+ return &storedRegexp();
832
+ } else {
833
+ return NULL;
834
+ }
835
+ }
836
+
837
+ string getStringValue(const Context &ctx) const {
838
+ switch (source) {
839
+ case REGEXP_LITERAL:
840
+ case STRING_LITERAL:
841
+ return storedString();
842
+ case INTEGER_LITERAL:
843
+ return toString(u.intValue);
844
+ case CONTEXT_FIELD_IDENTIFIER:
845
+ return ctx.queryStringField(u.contextFieldIdentifier);
846
+ default:
847
+ return "";
848
+ }
849
+ }
850
+
851
+ int getIntegerValue(const Context &ctx) const {
852
+ switch (source) {
853
+ case REGEXP_LITERAL:
854
+ return 0;
855
+ case STRING_LITERAL:
856
+ return atoi(storedString());
857
+ case INTEGER_LITERAL:
858
+ return u.intValue;
859
+ case CONTEXT_FIELD_IDENTIFIER:
860
+ return ctx.queryIntField(u.contextFieldIdentifier);
861
+ default:
862
+ return 0;
863
+ }
864
+ }
865
+
866
+ ValueType getType() const {
867
+ switch (source) {
868
+ case REGEXP_LITERAL:
869
+ return REGEXP_TYPE;
870
+ case STRING_LITERAL:
871
+ return STRING_TYPE;
872
+ case INTEGER_LITERAL:
873
+ return INTEGER_TYPE;
874
+ case CONTEXT_FIELD_IDENTIFIER:
875
+ return Context::getFieldType(u.contextFieldIdentifier);
876
+ default:
877
+ return UNKNOWN_TYPE;
878
+ }
879
+ }
880
+
881
+ private:
882
+ const string &storedString() const {
883
+ return *((string *) u.stringOrRegexpValue.stringStorage);
884
+ }
885
+
886
+ regex_t &storedRegexp() const {
887
+ return (regex_t &) u.stringOrRegexpValue.regexp.regexp;
888
+ }
889
+
890
+ void freeStorage() {
891
+ if (source == REGEXP_LITERAL || source == STRING_LITERAL) {
892
+ storedString().~string();
893
+ if (source == REGEXP_LITERAL) {
894
+ regfree(&storedRegexp());
895
+ }
896
+ }
897
+ }
898
+
899
+ void initializeFrom(const Value &other) {
900
+ int options;
901
+ source = other.source;
902
+ switch (source) {
903
+ case REGEXP_LITERAL:
904
+ new (u.stringOrRegexpValue.stringStorage) string(other.storedString());
905
+ options = REG_EXTENDED;
906
+ if (other.u.stringOrRegexpValue.regexp.options & Tokenizer::REGEXP_OPTION_CASE_INSENSITIVE) {
907
+ options |= REG_ICASE;
908
+ }
909
+ regcomp(&u.stringOrRegexpValue.regexp.regexp,
910
+ storedString().c_str(),
911
+ options);
912
+ u.stringOrRegexpValue.regexp.options = other.u.stringOrRegexpValue.regexp.options;
913
+ break;
914
+ case STRING_LITERAL:
915
+ new (u.stringOrRegexpValue.stringStorage) string(other.storedString());
916
+ break;
917
+ case INTEGER_LITERAL:
918
+ u.intValue = other.u.intValue;
919
+ break;
920
+ case CONTEXT_FIELD_IDENTIFIER:
921
+ u.contextFieldIdentifier = other.u.contextFieldIdentifier;
922
+ break;
923
+ }
924
+ }
925
+ };
926
+
927
+ struct Comparison: public BooleanComponent {
928
+ Value subject;
929
+ Comparator comparator;
930
+ Value object;
931
+
932
+ virtual bool evaluate(const Context &ctx) {
933
+ switch (subject.getType()) {
934
+ case STRING_TYPE:
935
+ return compareStringOrRegexp(subject.getStringValue(ctx), ctx);
936
+ case INTEGER_TYPE:
937
+ return compareInteger(subject.getIntegerValue(ctx), ctx);
938
+ default:
939
+ // error
940
+ return false;
941
+ }
942
+ }
943
+
944
+ private:
945
+ bool compareStringOrRegexp(const string &str, const Context &ctx) {
946
+ switch (comparator) {
947
+ case MATCHES:
948
+ return regexec(object.getRegexpValue(ctx), str.c_str(), 0, NULL, 0) == 0;
949
+ case NOT_MATCHES:
950
+ return regexec(object.getRegexpValue(ctx), str.c_str(), 0, NULL, 0) != 0;
951
+ case EQUALS:
952
+ return str == object.getStringValue(ctx);
953
+ case NOT_EQUALS:
954
+ return str != object.getStringValue(ctx);
955
+ default:
956
+ // error
957
+ return false;
958
+ }
959
+ }
960
+
961
+ bool compareInteger(int value, const Context &ctx) {
962
+ int value2 = object.getIntegerValue(ctx);
963
+ switch (comparator) {
964
+ case EQUALS:
965
+ return value == value2;
966
+ case NOT_EQUALS:
967
+ return value != value2;
968
+ case GREATER_THAN:
969
+ return value > value2;
970
+ case GREATER_THAN_OR_EQUALS:
971
+ return value >= value2;
972
+ case LESS_THAN:
973
+ return value < value2;
974
+ case LESS_THAN_OR_EQUALS:
975
+ return value <= value2;
976
+ default:
977
+ // error
978
+ return false;
979
+ }
980
+ }
981
+ };
982
+
983
+ struct FunctionCall: public BooleanComponent {
984
+ vector<Value> arguments;
985
+
986
+ virtual void checkArguments() const = 0;
987
+ };
988
+
989
+ struct StartsWithFunctionCall: public FunctionCall {
990
+ virtual bool evaluate(const Context &ctx) {
991
+ return startsWith(arguments[0].getStringValue(ctx),
992
+ arguments[1].getStringValue(ctx));
993
+ }
994
+
995
+ virtual void checkArguments() const {
996
+ if (arguments.size() != 2) {
997
+ throw SyntaxError("you passed " + toString(arguments.size()) +
998
+ " argument(s) to starts_with(), but it accepts exactly 2 arguments");
999
+ }
1000
+ }
1001
+ };
1002
+
1003
+ struct HasHintFunctionCall: public FunctionCall {
1004
+ virtual bool evaluate(const Context &ctx) {
1005
+ return ctx.hasHint(arguments[0].getStringValue(ctx));
1006
+ }
1007
+
1008
+ virtual void checkArguments() const {
1009
+ if (arguments.size() != 1) {
1010
+ throw SyntaxError("you passed " + toString(arguments.size()) +
1011
+ " argument(s) to has_hint(), but it accepts exactly 1 argument");
1012
+ }
1013
+ }
1014
+ };
1015
+
1016
+ Tokenizer tokenizer;
1017
+ BooleanComponentPtr root;
1018
+ Token lookahead;
1019
+
1020
+ static bool isLiteralToken(const Token &token) {
1021
+ return token.type == Tokenizer::REGEXP
1022
+ || token.type == Tokenizer::STRING
1023
+ || token.type == Tokenizer::INTEGER;
1024
+ }
1025
+
1026
+ static bool isValueToken(const Token &token) {
1027
+ return isLiteralToken(token) || token.type == Tokenizer::IDENTIFIER;
1028
+ }
1029
+
1030
+ static bool isLogicalOperatorToken(const Token &token) {
1031
+ return token.type == Tokenizer::AND
1032
+ || token.type == Tokenizer::OR;
1033
+ }
1034
+
1035
+ static bool comparatorAcceptsValueTypes(Comparator cmp, ValueType subjectType, ValueType objectType) {
1036
+ switch (cmp) {
1037
+ case MATCHES:
1038
+ case NOT_MATCHES:
1039
+ return subjectType == STRING_TYPE && objectType == REGEXP_TYPE;
1040
+ case EQUALS:
1041
+ case NOT_EQUALS:
1042
+ return (subjectType == STRING_TYPE || subjectType == INTEGER_TYPE)
1043
+ && subjectType == objectType;
1044
+ case GREATER_THAN:
1045
+ case GREATER_THAN_OR_EQUALS:
1046
+ case LESS_THAN:
1047
+ case LESS_THAN_OR_EQUALS:
1048
+ return subjectType == INTEGER_TYPE && objectType == INTEGER_TYPE;
1049
+ default:
1050
+ abort();
1051
+ return false; // Shut up compiler warning.
1052
+ }
1053
+ }
1054
+
1055
+ static string unescapeCString(const StaticString &data) {
1056
+ string result;
1057
+ result.reserve(data.size());
1058
+
1059
+ const char *current = data.data();
1060
+ const char *end = data.data() + data.size();
1061
+ while (current < end) {
1062
+ char ch = *current;
1063
+ if (ch == '\\') {
1064
+ current++;
1065
+ if (current < end) {
1066
+ ch = *current;
1067
+ switch (ch) {
1068
+ case 'r':
1069
+ result.append(1, '\r');
1070
+ break;
1071
+ case 'n':
1072
+ result.append(1, '\n');
1073
+ break;
1074
+ case 't':
1075
+ result.append(1, '\t');
1076
+ break;
1077
+ default:
1078
+ result.append(1, ch);
1079
+ break;
1080
+ }
1081
+ current++;
1082
+ }
1083
+ } else {
1084
+ result.append(1, ch);
1085
+ current++;
1086
+ }
1087
+ }
1088
+
1089
+ return result;
1090
+ }
1091
+
1092
+ Token peek() const {
1093
+ return lookahead;
1094
+ }
1095
+
1096
+ bool peek(Tokenizer::TokenType type) const {
1097
+ return lookahead.type == type;
1098
+ }
1099
+
1100
+ Token match(TokenType type) {
1101
+ if (lookahead.type == type) {
1102
+ return match();
1103
+ } else {
1104
+ raiseSyntaxError("Expected a " + Tokenizer::typeToString(type) +
1105
+ " token, but got " + lookahead.toString(),
1106
+ lookahead);
1107
+ return Token(); // Shut up compiler warning.
1108
+ }
1109
+ }
1110
+
1111
+ Token match() {
1112
+ Token old = lookahead;
1113
+ lookahead = tokenizer.getNext();
1114
+ return old;
1115
+ }
1116
+
1117
+ void raiseSyntaxError(const string &msg = "", const Token &token = Token()) {
1118
+ if (token.type != Tokenizer::NONE) {
1119
+ string message = "at character " + toString(token.pos + 1);
1120
+ if (!msg.empty()) {
1121
+ message.append(": ");
1122
+ message.append(msg);
1123
+ }
1124
+ throw SyntaxError(message);
1125
+ } else {
1126
+ throw SyntaxError(msg);
1127
+ }
1128
+ }
1129
+
1130
+ BooleanComponentPtr matchMultiExpression() {
1131
+ MultiExpressionPtr result = make_shared<MultiExpression>();
1132
+
1133
+ result->firstExpression = matchExpression();
1134
+ while (isLogicalOperatorToken(peek())) {
1135
+ MultiExpression::Part part;
1136
+ part.theOperator = matchOperator();
1137
+ part.expression = matchExpression();
1138
+ result->rest.push_back(part);
1139
+ }
1140
+
1141
+ return result;
1142
+ }
1143
+
1144
+ BooleanComponentPtr matchExpression() {
1145
+ bool negate = false;
1146
+
1147
+ if (peek(Tokenizer::NOT)) {
1148
+ match();
1149
+ negate = true;
1150
+ }
1151
+
1152
+ Token next = peek();
1153
+ if (next.type == Tokenizer::LPARENTHESIS) {
1154
+ BooleanComponentPtr expression = matchMultiExpression();
1155
+ match(Tokenizer::RPARENTHESIS);
1156
+ if (negate) {
1157
+ return make_shared<Negation>(expression);
1158
+ } else {
1159
+ return expression;
1160
+ }
1161
+ } else if (isValueToken(next)) {
1162
+ BooleanComponentPtr component;
1163
+ next = match();
1164
+
1165
+ if (peek(Tokenizer::LPARENTHESIS)) {
1166
+ component = matchFunctionCall(next);
1167
+ } else {
1168
+ component = matchComparison(next);
1169
+ }
1170
+
1171
+ if (negate) {
1172
+ return make_shared<Negation>(component);
1173
+ } else {
1174
+ return component;
1175
+ }
1176
+ } else {
1177
+ raiseSyntaxError("expected a left parenthesis or an identifier", next);
1178
+ return BooleanComponentPtr(); // Shut up compiler warning.
1179
+ }
1180
+ }
1181
+
1182
+ ComparisonPtr matchComparison(const Token &subjectToken) {
1183
+ ComparisonPtr comparison = make_shared<Comparison>();
1184
+ comparison->subject = matchValue(subjectToken);
1185
+ comparison->comparator = matchComparator();
1186
+ comparison->object = matchValue(match());
1187
+ if (!comparatorAcceptsValueTypes(comparison->comparator, comparison->subject.getType(), comparison->object.getType())) {
1188
+ raiseSyntaxError("the comparator cannot operate on the given combination of types", subjectToken);
1189
+ }
1190
+ return comparison;
1191
+ }
1192
+
1193
+ FunctionCallPtr matchFunctionCall(const Token &id) {
1194
+ FunctionCallPtr function;
1195
+
1196
+ if (id.rawValue == "starts_with") {
1197
+ function = make_shared<StartsWithFunctionCall>();
1198
+ } else if (id.rawValue == "has_hint") {
1199
+ function = make_shared<HasHintFunctionCall>();
1200
+ } else {
1201
+ raiseSyntaxError("unknown function '" + id.rawValue + "'", id);
1202
+ }
1203
+
1204
+ match(Tokenizer::LPARENTHESIS);
1205
+ if (isValueToken(peek())) {
1206
+ function->arguments.push_back(matchValue(match()));
1207
+ while (peek(Tokenizer::COMMA)) {
1208
+ match();
1209
+ function->arguments.push_back(matchValue(match()));
1210
+ }
1211
+ }
1212
+ match(Tokenizer::RPARENTHESIS);
1213
+ function->checkArguments();
1214
+ return function;
1215
+ }
1216
+
1217
+ Value matchValue(const Token &token) {
1218
+ if (isLiteralToken(token)) {
1219
+ return matchLiteral(token);
1220
+ } else if (token.type == Tokenizer::IDENTIFIER) {
1221
+ return matchContextFieldIdentifier(token);
1222
+ } else {
1223
+ raiseSyntaxError("", token);
1224
+ return Value(); // Shut up compiler warning.
1225
+ }
1226
+ }
1227
+
1228
+ LogicalOperator matchOperator() {
1229
+ if (peek(Tokenizer::AND)) {
1230
+ match();
1231
+ return AND;
1232
+ } else if (peek(Tokenizer::OR)) {
1233
+ match();
1234
+ return OR;
1235
+ } else {
1236
+ raiseSyntaxError("", peek());
1237
+ return AND; // Shut up compiler warning.
1238
+ }
1239
+ }
1240
+
1241
+ Comparator matchComparator() {
1242
+ if (peek(Tokenizer::MATCHES)) {
1243
+ match();
1244
+ return MATCHES;
1245
+ } else if (peek(Tokenizer::NOT_MATCHES)) {
1246
+ match();
1247
+ return NOT_MATCHES;
1248
+ } else if (peek(Tokenizer::EQUALS)) {
1249
+ match();
1250
+ return EQUALS;
1251
+ } else if (peek(Tokenizer::NOT_EQUALS)) {
1252
+ match();
1253
+ return NOT_EQUALS;
1254
+ } else if (peek(Tokenizer::GREATER_THAN)) {
1255
+ match();
1256
+ return GREATER_THAN;
1257
+ } else if (peek(Tokenizer::GREATER_THAN_OR_EQUALS)) {
1258
+ match();
1259
+ return GREATER_THAN_OR_EQUALS;
1260
+ } else if (peek(Tokenizer::LESS_THAN)) {
1261
+ match();
1262
+ return LESS_THAN;
1263
+ } else if (peek(Tokenizer::LESS_THAN_OR_EQUALS)) {
1264
+ match();
1265
+ return LESS_THAN_OR_EQUALS;
1266
+ } else {
1267
+ raiseSyntaxError("", peek());
1268
+ return MATCHES; // Shut up compiler warning.
1269
+ }
1270
+ }
1271
+
1272
+ Value matchLiteral(const Token &token) {
1273
+ if (token.type == Tokenizer::REGEXP) {
1274
+ return Value(true, unescapeCString(token.rawValue.substr(1, token.rawValue.size() - 2)),
1275
+ token.options & Tokenizer::REGEXP_OPTION_CASE_INSENSITIVE);
1276
+ } else if (token.type == Tokenizer::STRING) {
1277
+ return Value(false, unescapeCString(token.rawValue.substr(1, token.rawValue.size() - 2)));
1278
+ } else if (token.type == Tokenizer::INTEGER) {
1279
+ return Value(atoi(token.rawValue.toString()));
1280
+ } else {
1281
+ raiseSyntaxError("regular expression, string or integer expected", token);
1282
+ return Value(); // Shut up compiler warning.
1283
+ }
1284
+ }
1285
+
1286
+ Value matchContextFieldIdentifier(const Token &token) {
1287
+ if (token.rawValue == "uri") {
1288
+ return Value(Context::URI);
1289
+ } else if (token.rawValue == "controller") {
1290
+ return Value(Context::CONTROLLER);
1291
+ } else if (token.rawValue == "response_time") {
1292
+ return Value(Context::RESPONSE_TIME);
1293
+ } else {
1294
+ raiseSyntaxError("unknown field '" + token.rawValue + "'", token);
1295
+ return Value(); // Shut up compiler warning.
1296
+ }
1297
+ }
1298
+
1299
+ public:
1300
+ Filter(const StaticString &source, bool debug = false)
1301
+ : tokenizer(source, debug)
1302
+ {
1303
+ lookahead = tokenizer.getNext();
1304
+ root = matchMultiExpression();
1305
+ match(Tokenizer::END_OF_DATA);
1306
+ }
1307
+
1308
+ bool run(const Context &ctx) {
1309
+ return root->evaluate(ctx);
1310
+ }
1311
+ };
1312
+
1313
+
1314
+ } // namespace FilterSupport
1315
+ } // namespace Passenger
1316
+
1317
+ #endif /* _PASSENGER_FILTER_SUPPORT_H_ */