fluentd 1.19.1 → 1.19.3

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +60 -0
  3. data/Rakefile +7 -0
  4. data/lib/fluent/command/fluentd.rb +1 -0
  5. data/lib/fluent/config/v1_parser.rb +13 -5
  6. data/lib/fluent/config/yaml_parser/loader.rb +7 -2
  7. data/lib/fluent/config/yaml_parser.rb +2 -2
  8. data/lib/fluent/config.rb +5 -5
  9. data/lib/fluent/engine.rb +1 -1
  10. data/lib/fluent/event.rb +2 -1
  11. data/lib/fluent/plugin/buf_file.rb +3 -3
  12. data/lib/fluent/plugin/buf_file_single.rb +2 -2
  13. data/lib/fluent/plugin/buf_memory.rb +1 -1
  14. data/lib/fluent/plugin/buffer/chunk.rb +2 -1
  15. data/lib/fluent/plugin/buffer/file_chunk.rb +2 -2
  16. data/lib/fluent/plugin/buffer/file_single_chunk.rb +2 -2
  17. data/lib/fluent/plugin/buffer/memory_chunk.rb +1 -1
  18. data/lib/fluent/plugin/buffer.rb +3 -0
  19. data/lib/fluent/plugin/compressable.rb +7 -62
  20. data/lib/fluent/plugin/extractor.rb +121 -0
  21. data/lib/fluent/plugin/filter_record_transformer.rb +1 -1
  22. data/lib/fluent/plugin/in_debug_agent.rb +1 -1
  23. data/lib/fluent/plugin/in_forward.rb +3 -1
  24. data/lib/fluent/plugin/in_http.rb +8 -4
  25. data/lib/fluent/plugin/in_monitor_agent.rb +19 -22
  26. data/lib/fluent/plugin/in_tail.rb +1 -1
  27. data/lib/fluent/plugin/out_file.rb +10 -0
  28. data/lib/fluent/plugin/out_forward/socket_cache.rb +45 -10
  29. data/lib/fluent/plugin/out_forward.rb +10 -0
  30. data/lib/fluent/plugin/out_http.rb +31 -1
  31. data/lib/fluent/plugin/output.rb +45 -1
  32. data/lib/fluent/plugin/parser_csv.rb +5 -0
  33. data/lib/fluent/plugin/storage_local.rb +2 -2
  34. data/lib/fluent/plugin_helper/http_server/app.rb +2 -0
  35. data/lib/fluent/supervisor.rb +30 -4
  36. data/lib/fluent/test/base.rb +5 -0
  37. data/lib/fluent/version.rb +1 -1
  38. metadata +34 -9
  39. data/.deepsource.toml +0 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f7d0597141166c9db2f30b7ea1761610ec6dc2e3c5b8443f3ff980530c4afd33
4
- data.tar.gz: eb02d36ab94976985da49b0abe845dfe17056806f02e99cb8fa38a88addb79be
3
+ metadata.gz: 9613f1dacdb60cfccb245e988e67cec002daee2f81cbe38c2b80c30d525ad7b8
4
+ data.tar.gz: 4a85aa6c3bdaebd9cd069d508ac969d8792586247bef1d73d25cc906f6cd7c87
5
5
  SHA512:
6
- metadata.gz: cd04fb1ed5a9407cae8d150fdb5875cac15d5e906356ba76232e82cb4c28053199f9b09fa403fad5733623b758ec713defb205d8bcc6058e067a71f01c0ec30c
7
- data.tar.gz: eba375ae694bf113a9a70bce6c77575ed42e6d8f14d5c463890ae570c14dfe48576eb7eecd90fb401480ebfd910ecd45fdfed1130f909b8c541440bdf7a3c407
6
+ metadata.gz: c2d63e0bd8cf51a61d254cfda559f38ed1bcc79ceb1bf1f0d0c80b21b922e67091d2ee8c291a0dc0fcac0110873d0045874df297e0795c0cd604af64f431f845
7
+ data.tar.gz: 5fe9e2450048bacc978389621b0e3f2c8aed0e9136ffe11192945a754173c1ce13ff50c310cfcdf1633d7dc784e14e9a4fc5a6de519da3bc81decb12f6d5b28d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,65 @@
1
1
  # v1.19
2
2
 
3
+ ## Release v1.19.3 - 2026/06/25
4
+
5
+ ### Bug Fix
6
+
7
+ * out_http: add strict host validation for dynamic endpoints https://github.com/fluent/fluentd/pull/5394
8
+ * buffer, in_http: enforce size limits on decompressed payloads https://github.com/fluent/fluentd/pull/5393
9
+ * in_monitor_agent: change default visibility of config, retry, and debug info https://github.com/fluent/fluentd/pull/5392
10
+ * output: enforce strict path boundary validation for tag https://github.com/fluent/fluentd/pull/5391
11
+ * engine: remove duplicated word in unreloadable plugin error message https://github.com/fluent/fluentd/pull/5389
12
+ * storage_local: fix encoding error when reading non-ASCII characters https://github.com/fluent/fluentd/pull/5382
13
+ * parser_csv: skip empty or unparseable lines https://github.com/fluent/fluentd/pull/5359
14
+ * out_forward: avoid reusing closed keepalive sockets after remote disconnects https://github.com/fluent/fluentd/pull/5343
15
+ * buffer: resume buffer correctly even though path contains [] https://github.com/fluent/fluentd/pull/5305
16
+ * in_debug_agent: accept only from local machine by default https://github.com/fluent/fluentd/pull/5279
17
+
18
+ ### Misc
19
+
20
+ * gem: add win32-registry as runtime dependency for Ruby 4.1 https://github.com/fluent/fluentd/pull/5317
21
+ * output windows: check shorter service timeout on shutdown https://github.com/fluent/fluentd/pull/5306
22
+ * buffer: warn if default timekey (1d) will be used https://github.com/fluent/fluentd/pull/5291
23
+ * warn recommended exclusion path for antivirus https://github.com/fluent/fluentd/pull/5280
24
+ * CI fixes
25
+ * https://github.com/fluent/fluentd/pull/5387
26
+ * https://github.com/fluent/fluentd/pull/5386
27
+ * https://github.com/fluent/fluentd/pull/5366
28
+ * https://github.com/fluent/fluentd/pull/5342
29
+ * https://github.com/fluent/fluentd/pull/5341
30
+ * https://github.com/fluent/fluentd/pull/5340
31
+ * https://github.com/fluent/fluentd/pull/5339
32
+ * https://github.com/fluent/fluentd/pull/5338
33
+ * https://github.com/fluent/fluentd/pull/5337
34
+ * https://github.com/fluent/fluentd/pull/5336
35
+ * https://github.com/fluent/fluentd/pull/5335
36
+ * https://github.com/fluent/fluentd/pull/5334
37
+ * https://github.com/fluent/fluentd/pull/5333
38
+
39
+ ## Release v1.19.2 - 2026/02/13
40
+
41
+ ### Bug Fix
42
+
43
+ * out_forward: add timeout to establish_connection to prevent infinite loop https://github.com/fluent/fluentd/pull/5138
44
+ * gem: use latest net-http to solve IPv6 addr error https://github.com/fluent/fluentd/pull/5192
45
+ * in_tail: fix error when files without read permission are included in glob patterns https://github.com/fluent/fluentd/pull/5222
46
+ * command/fluentd: load win32/registry when edit registry for Ruby 4.0 https://github.com/fluent/fluentd/pull/5221
47
+ * plugin_helper/http_server: Ensure request body is closed to prevent socket leaks in POST requests https://github.com/fluent/fluentd/pull/5234
48
+ * config: fix duplicate config file loading in config_include_dir https://github.com/fluent/fluentd/pull/5235
49
+ * gem: add ostruct gem as dependency for Ruby 4.0 https://github.com/fluent/fluentd/pull/5251
50
+
51
+ ### Misc
52
+
53
+ * config: warn when backed-up config file will be included https://github.com/fluent/fluentd/pull/5252
54
+ * filter_record_transformer: use cgi/escape to avoid Ruby 4.0 deprecation https://github.com/fluent/fluentd/pull/5256
55
+ * CI fixes
56
+ * https://github.com/fluent/fluentd/pull/5257
57
+ * https://github.com/fluent/fluentd/pull/5229
58
+ * https://github.com/fluent/fluentd/pull/5225
59
+ * https://github.com/fluent/fluentd/pull/5186
60
+ * https://github.com/fluent/fluentd/pull/5184
61
+ * https://github.com/fluent/fluentd/pull/5176
62
+
3
63
  ## Release v1.19.1 - 2025/11/06
4
64
 
5
65
  ### Bug Fix
data/Rakefile CHANGED
@@ -78,4 +78,11 @@ task :coverity do
78
78
  FileUtils.rm_rf(['./cov-int', 'cov-fluentd.tar.gz'])
79
79
  end
80
80
 
81
+ task :check_env do
82
+ unless ENV['GEM_HOST_API_KEY']
83
+ abort "Missing required environment variable: GEM_HOST_API_KEY\nSee https://guides.rubygems.org/api-key-scopes/"
84
+ end
85
+ end
86
+ Rake::Task["release:rubygem_push"].enhance(["check_env"])
87
+
81
88
  task default: [:test, :build]
@@ -344,6 +344,7 @@ if winsvcinstmode = opts[:regwinsvc]
344
344
  end
345
345
 
346
346
  if fluentdopt = opts[:fluentdopt]
347
+ require "win32/registry"
347
348
  Win32::Registry::HKEY_LOCAL_MACHINE.open("SYSTEM\\CurrentControlSet\\Services\\#{opts[:winsvc_name]}", Win32::Registry::KEY_ALL_ACCESS) do |reg|
348
349
  reg['fluentdopt', Win32::Registry::REG_SZ] = fluentdopt
349
350
  end
@@ -27,17 +27,18 @@ module Fluent
27
27
  class V1Parser < LiteralParser
28
28
  ELEMENT_NAME = /[a-zA-Z0-9_]+/
29
29
 
30
- def self.parse(data, fname, basepath = Dir.pwd, eval_context = nil)
30
+ def self.parse(data, fname, basepath = Dir.pwd, eval_context = nil, on_file_parsed: nil)
31
31
  ss = StringScanner.new(data)
32
- ps = V1Parser.new(ss, basepath, fname, eval_context)
32
+ ps = V1Parser.new(ss, basepath, fname, eval_context, on_file_parsed: on_file_parsed)
33
33
  ps.parse!
34
34
  end
35
35
 
36
- def initialize(strscan, include_basepath, fname, eval_context)
36
+ def initialize(strscan, include_basepath, fname, eval_context, on_file_parsed: nil)
37
37
  super(strscan, eval_context)
38
38
  @include_basepath = include_basepath
39
39
  @fname = fname
40
40
  @logger = defined?($log) ? $log : nil
41
+ @on_file_parsed = on_file_parsed
41
42
  end
42
43
 
43
44
  def parse!
@@ -138,6 +139,8 @@ module Fluent
138
139
  end
139
140
  end
140
141
 
142
+ @on_file_parsed&.call(File.expand_path(File.join(@include_basepath, @fname))) if root_element
143
+
141
144
  return attrs, elems
142
145
  end
143
146
 
@@ -163,10 +166,15 @@ module Fluent
163
166
  Dir.glob(pattern).sort.each { |entry|
164
167
  basepath = File.dirname(entry)
165
168
  fname = File.basename(entry)
169
+ suspicious_backup_extensions = %w(bak old backup orig prev conf tmp temp debug wip)
170
+ if path.end_with?('*.conf') and
171
+ suspicious_backup_extensions.any? { |ext| fname.end_with?(".#{ext}.conf", "_#{ext}.conf") }
172
+ @logger.warn "There is a possibility that '@include #{uri}' includes duplicated backed-up config file such as <#{fname}>" if @logger
173
+ end
166
174
  data = File.read(entry)
167
175
  data.force_encoding('UTF-8')
168
176
  ss = StringScanner.new(data)
169
- V1Parser.new(ss, basepath, fname, @eval_context).parse_element(true, nil, attrs, elems)
177
+ V1Parser.new(ss, basepath, fname, @eval_context, on_file_parsed: @on_file_parsed).parse_element(true, nil, attrs, elems)
170
178
  }
171
179
  else
172
180
  require 'open-uri'
@@ -175,7 +183,7 @@ module Fluent
175
183
  data = u.open { |f| f.read }
176
184
  data.force_encoding('UTF-8')
177
185
  ss = StringScanner.new(data)
178
- V1Parser.new(ss, basepath, fname, @eval_context).parse_element(true, nil, attrs, elems)
186
+ V1Parser.new(ss, basepath, fname, @eval_context, on_file_parsed: @on_file_parsed).parse_element(true, nil, attrs, elems)
179
187
  end
180
188
  rescue SystemCallError => e
181
189
  cpe = ConfigParseError.new("include error #{uri} - #{e}")
@@ -30,9 +30,10 @@ module Fluent
30
30
  FLUENT_STR_TAG = 'tag:fluent/s'.freeze
31
31
  SHOVEL = '<<'.freeze
32
32
 
33
- def initialize(context = Kernel.binding)
33
+ def initialize(context = Kernel.binding, on_file_parsed: nil)
34
34
  @context = context
35
35
  @current_path = nil
36
+ @on_file_parsed = on_file_parsed
36
37
  end
37
38
 
38
39
  # @param [String] path
@@ -55,9 +56,13 @@ module Fluent
55
56
  Fluent::Config::YamlParser::FluentValue::StringValue.new(val, @context)
56
57
  end
57
58
 
58
- path.open do |f|
59
+ config = path.open do |f|
59
60
  visitor.accept(Psych.parse(f))
60
61
  end
62
+
63
+ @on_file_parsed&.call(File.expand_path(path.to_s))
64
+
65
+ config
61
66
  end
62
67
 
63
68
  def eval_include(path, parent)
@@ -21,7 +21,7 @@ require 'pathname'
21
21
  module Fluent
22
22
  module Config
23
23
  module YamlParser
24
- def self.parse(path)
24
+ def self.parse(path, on_file_parsed: nil)
25
25
  context = Kernel.binding
26
26
 
27
27
  unless context.respond_to?(:use_nil)
@@ -48,7 +48,7 @@ module Fluent
48
48
  end
49
49
  end
50
50
 
51
- s = Fluent::Config::YamlParser::Loader.new(context).load(Pathname.new(path))
51
+ s = Fluent::Config::YamlParser::Loader.new(context, on_file_parsed: on_file_parsed).load(Pathname.new(path))
52
52
  Fluent::Config::YamlParser::Parser.new(s).build.to_element
53
53
  end
54
54
  end
data/lib/fluent/config.rb CHANGED
@@ -26,7 +26,7 @@ module Fluent
26
26
  # @param additional_config [String] config which is added to last of config body
27
27
  # @param use_v1_config [Bool] config is formatted with v1 or not
28
28
  # @return [Fluent::Config]
29
- def self.build(config_path:, encoding: 'utf-8', additional_config: nil, use_v1_config: true, type: nil)
29
+ def self.build(config_path:, encoding: 'utf-8', additional_config: nil, use_v1_config: true, type: nil, on_file_parsed: nil)
30
30
  if type == :guess
31
31
  config_file_ext = File.extname(config_path)
32
32
  if config_file_ext == '.yaml' || config_file_ext == '.yml'
@@ -35,7 +35,7 @@ module Fluent
35
35
  end
36
36
 
37
37
  if type == :yaml || type == :yml
38
- return Fluent::Config::YamlParser.parse(config_path)
38
+ return Fluent::Config::YamlParser.parse(config_path, on_file_parsed: on_file_parsed)
39
39
  end
40
40
 
41
41
  config_fname = File.basename(config_path)
@@ -49,10 +49,10 @@ module Fluent
49
49
  s
50
50
  end
51
51
 
52
- Fluent::Config.parse(config_data, config_fname, config_basedir, use_v1_config)
52
+ Fluent::Config.parse(config_data, config_fname, config_basedir, use_v1_config, on_file_parsed: on_file_parsed)
53
53
  end
54
54
 
55
- def self.parse(str, fname, basepath = Dir.pwd, v1_config = nil, syntax: :v1)
55
+ def self.parse(str, fname, basepath = Dir.pwd, v1_config = nil, syntax: :v1, on_file_parsed: nil)
56
56
  parser = if fname =~ /\.rb$/ || syntax == :ruby
57
57
  :ruby
58
58
  elsif v1_config.nil?
@@ -68,7 +68,7 @@ module Fluent
68
68
  case parser
69
69
  when :v1
70
70
  require 'fluent/config/v1_parser'
71
- V1Parser.parse(str, fname, basepath, Kernel.binding)
71
+ V1Parser.parse(str, fname, basepath, Kernel.binding, on_file_parsed: on_file_parsed)
72
72
  when :v0
73
73
  # TODO: show deprecated message in v1
74
74
  require 'fluent/config/parser'
data/lib/fluent/engine.rb CHANGED
@@ -186,7 +186,7 @@ module Fluent
186
186
 
187
187
  ret.all_plugins.each do |plugin|
188
188
  if plugin.respond_to?(:reloadable_plugin?) && !plugin.reloadable_plugin?
189
- raise Fluent::ConfigError, "Unreloadable plugin plugin: #{Fluent::Plugin.lookup_type_from_class(plugin.class)}, plugin_id: #{plugin.plugin_id}, class_name: #{plugin.class})"
189
+ raise Fluent::ConfigError, "Unreloadable plugin: #{Fluent::Plugin.lookup_type_from_class(plugin.class)}, plugin_id: #{plugin.plugin_id}, class_name: #{plugin.class})"
190
190
  end
191
191
  end
192
192
 
data/lib/fluent/event.rb CHANGED
@@ -268,11 +268,12 @@ module Fluent
268
268
  end
269
269
 
270
270
  class CompressedMessagePackEventStream < MessagePackEventStream
271
- def initialize(data, cached_unpacker = nil, size = 0, unpacked_times: nil, unpacked_records: nil, compress: :gzip)
271
+ def initialize(data, cached_unpacker = nil, size = 0, unpacked_times: nil, unpacked_records: nil, compress: :gzip, decompression_size_limit: Fluent::Plugin::Compressable::DEFAULT_DECOMPRESSION_SIZE_LIMIT)
272
272
  super(data, cached_unpacker, size, unpacked_times: unpacked_times, unpacked_records: unpacked_records)
273
273
  @decompressed_data = nil
274
274
  @compressed_data = data
275
275
  @type = compress
276
+ @decompression_size_limit = decompression_size_limit
276
277
  end
277
278
 
278
279
  def empty?
@@ -205,7 +205,7 @@ module Fluent
205
205
  def generate_chunk(metadata)
206
206
  # FileChunk generates real path with unique_id
207
207
  perm = @file_permission || system_config.file_permission
208
- chunk = Fluent::Plugin::Buffer::FileChunk.new(metadata, @path, :create, perm: perm, compress: @compress)
208
+ chunk = Fluent::Plugin::Buffer::FileChunk.new(metadata, @path, :create, perm: perm, compress: @compress, decompression_size_limit: @decompression_size_limit)
209
209
  log.debug "Created new chunk", chunk_id: dump_unique_id_hex(chunk.unique_id), metadata: metadata
210
210
 
211
211
  return chunk
@@ -247,8 +247,8 @@ module Fluent
247
247
 
248
248
  def escaped_patterns(patterns)
249
249
  patterns.map { |pattern|
250
- # '{' '}' are special character in Dir.glob
251
- pattern.gsub(/[\{\}]/) { |c| "\\#{c}" }
250
+ # '{', '}', '[' and ']' are special character in Dir.glob
251
+ pattern.gsub(/[\{\}\[\]]/) { |c| "\\#{c}" }
252
252
  }
253
253
  end
254
254
  end
@@ -183,7 +183,7 @@ module Fluent
183
183
  end
184
184
 
185
185
  begin
186
- chunk = Fluent::Plugin::Buffer::FileSingleChunk.new(m, path, mode, @key_in_path, compress: @compress)
186
+ chunk = Fluent::Plugin::Buffer::FileSingleChunk.new(m, path, mode, @key_in_path, compress: @compress, decompression_size_limit: @decompression_size_limit)
187
187
  chunk.restore_size(@chunk_format) if @calc_num_records
188
188
  rescue Fluent::Plugin::Buffer::FileSingleChunk::FileChunkError => e
189
189
  exist_broken_file = true
@@ -216,7 +216,7 @@ module Fluent
216
216
  def generate_chunk(metadata)
217
217
  # FileChunk generates real path with unique_id
218
218
  perm = @file_permission || system_config.file_permission
219
- chunk = Fluent::Plugin::Buffer::FileSingleChunk.new(metadata, @path, :create, @key_in_path, perm: perm, compress: @compress)
219
+ chunk = Fluent::Plugin::Buffer::FileSingleChunk.new(metadata, @path, :create, @key_in_path, perm: perm, compress: @compress, decompression_size_limit: @decompression_size_limit)
220
220
 
221
221
  log.debug "Created new chunk", chunk_id: dump_unique_id_hex(chunk.unique_id), metadata: metadata
222
222
 
@@ -27,7 +27,7 @@ module Fluent
27
27
  end
28
28
 
29
29
  def generate_chunk(metadata)
30
- Fluent::Plugin::Buffer::MemoryChunk.new(metadata, compress: @compress)
30
+ Fluent::Plugin::Buffer::MemoryChunk.new(metadata, compress: @compress, decompression_size_limit: @decompression_size_limit)
31
31
  end
32
32
  end
33
33
  end
@@ -48,7 +48,7 @@ module Fluent
48
48
 
49
49
  # TODO: CompressedPackedMessage of forward protocol?
50
50
 
51
- def initialize(metadata, compress: :text)
51
+ def initialize(metadata, compress: :text, decompression_size_limit: Compressable::DEFAULT_DECOMPRESSION_SIZE_LIMIT)
52
52
  super()
53
53
  @unique_id = generate_unique_id
54
54
  @metadata = metadata
@@ -64,6 +64,7 @@ module Fluent
64
64
  elsif compress == :zstd
65
65
  extend ZstdDecompressable
66
66
  end
67
+ @decompression_size_limit = decompression_size_limit
67
68
  end
68
69
 
69
70
  attr_reader :unique_id, :metadata, :state
@@ -39,8 +39,8 @@ module Fluent
39
39
 
40
40
  attr_reader :path, :meta_path, :permission
41
41
 
42
- def initialize(metadata, path, mode, perm: nil, compress: :text)
43
- super(metadata, compress: compress)
42
+ def initialize(metadata, path, mode, perm: nil, compress: :text, decompression_size_limit: Compressable::DEFAULT_DECOMPRESSION_SIZE_LIMIT)
43
+ super(metadata, compress: compress, decompression_size_limit: decompression_size_limit)
44
44
  perm ||= Fluent::DEFAULT_FILE_PERMISSION
45
45
  @permission = perm.is_a?(String) ? perm.to_i(8) : perm
46
46
  @bytesize = @size = @adding_bytes = @adding_size = 0
@@ -35,8 +35,8 @@ module Fluent
35
35
 
36
36
  attr_reader :path, :permission
37
37
 
38
- def initialize(metadata, path, mode, key, perm: Fluent::DEFAULT_FILE_PERMISSION, compress: :text)
39
- super(metadata, compress: compress)
38
+ def initialize(metadata, path, mode, key, perm: Fluent::DEFAULT_FILE_PERMISSION, compress: :text, decompression_size_limit: Compressable::DEFAULT_DECOMPRESSION_SIZE_LIMIT)
39
+ super(metadata, compress: compress, decompression_size_limit: decompression_size_limit)
40
40
  @key = key
41
41
  perm ||= Fluent::DEFAULT_FILE_PERMISSION
42
42
  @permission = perm.is_a?(String) ? perm.to_i(8) : perm
@@ -20,7 +20,7 @@ module Fluent
20
20
  module Plugin
21
21
  class Buffer
22
22
  class MemoryChunk < Chunk
23
- def initialize(metadata, compress: :text)
23
+ def initialize(metadata, compress: :text, decompression_size_limit: Compressable::DEFAULT_DECOMPRESSION_SIZE_LIMIT)
24
24
  super
25
25
  @chunk = ''.force_encoding(Encoding::ASCII_8BIT)
26
26
  @chunk_bytes = 0
@@ -66,6 +66,9 @@ module Fluent
66
66
  desc 'Compress buffered data.'
67
67
  config_param :compress, :enum, list: [:text, :gzip, :zstd], default: :text
68
68
 
69
+ desc 'The size limit of the decompressed element.'
70
+ config_param :decompression_size_limit, :size, default: Compressable::DEFAULT_DECOMPRESSION_SIZE_LIMIT
71
+
69
72
  desc 'If true, chunks are thrown away when unrecoverable error happens'
70
73
  config_param :disable_chunk_backup, :bool, default: false
71
74
 
@@ -14,6 +14,7 @@
14
14
  # limitations under the License.
15
15
  #
16
16
 
17
+ require 'fluent/plugin/extractor'
17
18
  require 'stringio'
18
19
  require 'zlib'
19
20
  require 'zstd-ruby'
@@ -21,6 +22,8 @@ require 'zstd-ruby'
21
22
  module Fluent
22
23
  module Plugin
23
24
  module Compressable
25
+ DEFAULT_DECOMPRESSION_SIZE_LIMIT = 256 * 1024 * 1024
26
+
24
27
  def compress(data, type: :gzip, **kwargs)
25
28
  output_io = kwargs[:output_io]
26
29
  io = output_io || StringIO.new
@@ -60,79 +63,21 @@ module Fluent
60
63
 
61
64
  private
62
65
 
63
- def string_decompress_gzip(compressed_data)
64
- io = StringIO.new(compressed_data)
65
- out = ''
66
- loop do
67
- reader = Zlib::GzipReader.new(io)
68
- out << reader.read
69
- unused = reader.unused
70
- reader.finish
71
- unless unused.nil?
72
- adjust = unused.length
73
- io.pos -= adjust
74
- end
75
- break if io.eof?
76
- end
77
- out
78
- end
79
-
80
- def string_decompress_zstd(compressed_data)
81
- io = StringIO.new(compressed_data)
82
- reader = Zstd::StreamReader.new(io)
83
- out = ''
84
- loop do
85
- # Zstd::StreamReader needs to specify the size of the buffer
86
- out << reader.read(1024)
87
- # Zstd::StreamReader doesn't provide unused data, so we have to manually adjust the position
88
- break if io.eof?
89
- end
90
- out
91
- end
92
-
93
66
  def string_decompress(compressed_data, type = :gzip)
94
67
  if type == :gzip
95
- string_decompress_gzip(compressed_data)
68
+ Extractor.decompress_gzip(compressed_data, limit: @decompression_size_limit || DEFAULT_DECOMPRESSION_SIZE_LIMIT)
96
69
  elsif type == :zstd
97
- string_decompress_zstd(compressed_data)
70
+ Extractor.decompress_zstd(compressed_data, limit: @decompression_size_limit || DEFAULT_DECOMPRESSION_SIZE_LIMIT)
98
71
  else
99
72
  raise ArgumentError, "Unknown compression type: #{type}"
100
73
  end
101
74
  end
102
75
 
103
- def io_decompress_gzip(input, output)
104
- loop do
105
- reader = Zlib::GzipReader.new(input)
106
- v = reader.read
107
- output.write(v)
108
- unused = reader.unused
109
- reader.finish
110
- unless unused.nil?
111
- adjust = unused.length
112
- input.pos -= adjust
113
- end
114
- break if input.eof?
115
- end
116
- output
117
- end
118
-
119
- def io_decompress_zstd(input, output)
120
- reader = Zstd::StreamReader.new(input)
121
- loop do
122
- # Zstd::StreamReader needs to specify the size of the buffer
123
- v = reader.read(1024)
124
- output.write(v)
125
- # Zstd::StreamReader doesn't provide unused data, so we have to manually adjust the position
126
- break if input.eof?
127
- end
128
- output
129
- end
130
-
131
76
  def io_decompress(input, output, type = :gzip)
132
77
  if type == :gzip
133
- io_decompress_gzip(input, output)
78
+ Extractor.io_decompress_gzip(input, output)
134
79
  elsif type == :zstd
135
- io_decompress_zstd(input, output)
80
+ Extractor.io_decompress_zstd(input, output)
136
81
  else
137
82
  raise ArgumentError, "Unknown compression type: #{type}"
138
83
  end
@@ -0,0 +1,121 @@
1
+ #
2
+ # Fluentd
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'stringio'
18
+ require 'zlib'
19
+ require 'zstd-ruby'
20
+ require 'fluent/error'
21
+
22
+ module Fluent
23
+ module Plugin
24
+ module Extractor
25
+ class SizeLimitError < UnrecoverableError; end
26
+
27
+ BYTES_TO_READ = 64 * 1024
28
+ INFLATE_BYTES_TO_READ = 1024
29
+
30
+ def self.decompress_gzip(compressed_data, limit:)
31
+ io = StringIO.new(compressed_data)
32
+ out = ''
33
+ loop do
34
+ reader = Zlib::GzipReader.new(io)
35
+ while (chunk = reader.read(BYTES_TO_READ))
36
+ out << chunk
37
+ if out.bytesize > limit
38
+ raise SizeLimitError, "Decompressed data exceeds limit of #{limit} bytes"
39
+ end
40
+ end
41
+
42
+ unused = reader.unused
43
+ reader.finish
44
+ unless unused.nil?
45
+ adjust = unused.length
46
+ io.pos -= adjust
47
+ end
48
+ break if io.eof?
49
+ end
50
+ out
51
+ end
52
+
53
+ def self.decompress_zstd(compressed_data, limit:)
54
+ io = StringIO.new(compressed_data)
55
+ reader = Zstd::StreamReader.new(io)
56
+ out = ''
57
+ loop do
58
+ # Zstd::StreamReader needs to specify the size of the buffer
59
+ out << reader.read(BYTES_TO_READ)
60
+ if out.bytesize > limit
61
+ raise SizeLimitError, "Decompressed data exceeds limit of #{limit} bytes"
62
+ end
63
+
64
+ # Zstd::StreamReader doesn't provide unused data, so we have to manually adjust the position
65
+ break if io.eof?
66
+ end
67
+ out
68
+ end
69
+
70
+ def self.decompress_deflate(compressed_data, limit:)
71
+ io = StringIO.new(compressed_data)
72
+ out = ''
73
+ begin
74
+ zstream = Zlib::Inflate.new
75
+ while (chunk = io.read(INFLATE_BYTES_TO_READ))
76
+ out << zstream.inflate(chunk)
77
+ if out.bytesize > limit
78
+ raise SizeLimitError, "Decompressed data exceeds limit of #{limit} bytes"
79
+ end
80
+ end
81
+ out << zstream.finish
82
+ if out.bytesize > limit
83
+ raise SizeLimitError, "Decompressed data exceeds limit of #{limit} bytes"
84
+ end
85
+ ensure
86
+ zstream&.close
87
+ end
88
+ out
89
+ end
90
+
91
+ def self.io_decompress_gzip(input, output)
92
+ loop do
93
+ reader = Zlib::GzipReader.new(input)
94
+ while (chunk = reader.read(BYTES_TO_READ))
95
+ output.write(chunk)
96
+ end
97
+ unused = reader.unused
98
+ reader.finish
99
+ unless unused.nil?
100
+ adjust = unused.length
101
+ input.pos -= adjust
102
+ end
103
+ break if input.eof?
104
+ end
105
+ output
106
+ end
107
+
108
+ def self.io_decompress_zstd(input, output)
109
+ reader = Zstd::StreamReader.new(input)
110
+ loop do
111
+ # Zstd::StreamReader needs to specify the size of the buffer
112
+ chunk = reader.read(BYTES_TO_READ)
113
+ output.write(chunk)
114
+ # Zstd::StreamReader doesn't provide unused data, so we have to manually adjust the position
115
+ break if input.eof?
116
+ end
117
+ output
118
+ end
119
+ end
120
+ end
121
+ end
@@ -71,7 +71,7 @@ module Fluent::Plugin
71
71
  # require utilities which would be used in ruby placeholders
72
72
  require 'pathname'
73
73
  require 'uri'
74
- require 'cgi'
74
+ require 'cgi/escape'
75
75
  RubyPlaceholderExpander.new(placeholder_expander_params)
76
76
  else
77
77
  PlaceholderExpander.new(placeholder_expander_params)
@@ -26,7 +26,7 @@ module Fluent::Plugin
26
26
  super
27
27
  end
28
28
 
29
- config_param :bind, :string, default: '0.0.0.0'
29
+ config_param :bind, :string, default: '127.0.0.1'
30
30
  config_param :port, :integer, default: 24230
31
31
  config_param :unix_path, :string, default: nil
32
32
  #config_param :unix_mode # TODO
@@ -54,6 +54,8 @@ module Fluent::Plugin
54
54
  config_param :chunk_size_warn_limit, :size, default: nil
55
55
  desc 'Received chunk is dropped if it is larger than this value.'
56
56
  config_param :chunk_size_limit, :size, default: nil
57
+ desc 'The size limit of the decompressed element.'
58
+ config_param :decompression_size_limit, :size, default: 256*1024*1024
57
59
  desc 'Skip an event if incoming event is invalid.'
58
60
  config_param :skip_invalid_event, :bool, default: true
59
61
 
@@ -311,7 +313,7 @@ module Fluent::Plugin
311
313
  size = option['size'] || 0
312
314
 
313
315
  if option['compressed'] && option['compressed'] != 'text'
314
- es = Fluent::CompressedMessagePackEventStream.new(entries, nil, size.to_i, compress: option['compressed'].to_sym)
316
+ es = Fluent::CompressedMessagePackEventStream.new(entries, nil, size.to_i, compress: option['compressed'].to_sym, decompression_size_limit: @decompression_size_limit)
315
317
  else
316
318
  es = Fluent::MessagePackEventStream.new(entries, nil, size.to_i)
317
319
  end