jekyll-minifier 0.2.1 → 0.2.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ff00a62cfbd5df9157bc8b81edd83e5506ef1da9153149e3b2573e305947d2eb
4
- data.tar.gz: 63a6e0282d36449a69b5880138a0c0f965afa7549c12387100765348868444d3
3
+ metadata.gz: 1f83fb1bf20cca0322484c8871091bd3ec548ea0e0011d7b77bf64e9f2b8662a
4
+ data.tar.gz: 11224edc99cf216907c72bbd7f62f19963eb8df7d27d056732dd1147a991922a
5
5
  SHA512:
6
- metadata.gz: d7a1bd534efe06624b865dcf0ff44d029eb733559c23ad546a410eedc34a89f1c8a2c3309ff6eae47cf7ae33bc14f849477ba66d31ce079a4a89dad54ac9e8de
7
- data.tar.gz: b742c65ef0b95e56a7d6f07e8b26ae843a6e9fb45a1b3fb27e872c1486cf079ff62735e2ce960857d63a339c72dd04d37430e8351e5782da13160804842b1e2a
6
+ metadata.gz: b9cd2eafb30ed5130fd0ec29f73ddc43fb30af5264e0463b321cc70e0ffa96ac82d8f5b584631814f9e651cb179655878423dd6265ba74f1f515c35c27eee413
7
+ data.tar.gz: f112bc19ee2f0556eec29aa08f221a72e1a9ccfba5f7f0a817242948250801532c132a1d38d0eef5c3260f2a54fe551e3302cc5af72e32c3215e7506ff8db481
data/.gitignore CHANGED
@@ -8,3 +8,17 @@ rdoc
8
8
  doc
9
9
  .yardoc
10
10
  .rspec
11
+ .rspec_status
12
+ *.db
13
+ *.log
14
+ *.tmp
15
+ *~
16
+ *.bak
17
+ .DS_Store
18
+ tmp/
19
+ spec/fixtures/_site/
20
+
21
+ # Test files that shouldn't be committed
22
+ bad.css
23
+ bad.js
24
+ test.css
data/CHANGELOG.md ADDED
@@ -0,0 +1,52 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.2.2] - 2024-09-04
9
+
10
+ ### Fixed
11
+ - Removed problematic content validation checks that could incorrectly reject valid files ([#64](https://github.com/digitalsparky/jekyll-minifier/issues/64))
12
+ - CSS, JavaScript, JSON, and HTML content validation is now delegated to the actual minification libraries
13
+ - These libraries have proper parsers and handle edge cases correctly
14
+ - Fixed environment validation test that was failing due to missing environment mocking
15
+ - All 166 tests now passing (100% pass rate)
16
+
17
+ ### Security
18
+ - Maintained all critical security validations:
19
+ - File size limits (50MB max)
20
+ - File encoding validation
21
+ - File path traversal protection
22
+ - ReDoS pattern detection with timeout guards
23
+
24
+ ### Changed
25
+ - Content validation is now handled by the minification libraries themselves (Terser, CSSminify2, JSON.minify, HtmlCompressor)
26
+ - Improved test environment mocking for consistent test results
27
+
28
+ ### Maintenance
29
+ - Cleaned up repository by removing tracked database files and test artifacts
30
+ - Updated .gitignore to exclude temporary files, databases, and OS-specific files
31
+ - Improved build process reliability
32
+
33
+ ## [0.2.1] - Previous Release
34
+
35
+ ### Security
36
+ - Added comprehensive ReDoS protection with pattern validation and timeout guards
37
+ - Implemented input validation system for configuration values
38
+ - Added file path security checks to prevent directory traversal
39
+
40
+ ### Features
41
+ - Enhanced CSS compression with cssminify2 v2.1.0 features
42
+ - Compressor object caching for improved performance
43
+ - Comprehensive configuration validation
44
+
45
+ ### Performance
46
+ - Implemented caching system for compressor instances
47
+ - Added cache statistics tracking
48
+ - Optimized compression workflow
49
+
50
+ ## [0.2.0] - Earlier releases
51
+
52
+ Please see the [GitHub releases page](https://github.com/digitalsparky/jekyll-minifier/releases) for earlier version history.
@@ -1,5 +1,5 @@
1
1
  module Jekyll
2
2
  module Minifier
3
- VERSION = "0.2.1"
3
+ VERSION = "0.2.2"
4
4
  end
5
5
  end
@@ -12,19 +12,19 @@ module Jekyll
12
12
 
13
13
  # Maximum safe file size for processing (50MB)
14
14
  MAX_SAFE_FILE_SIZE = 50 * 1024 * 1024
15
-
15
+
16
16
  # Maximum safe configuration value sizes
17
17
  MAX_SAFE_STRING_LENGTH = 10_000
18
18
  MAX_SAFE_ARRAY_SIZE = 1_000
19
19
  MAX_SAFE_HASH_SIZE = 100
20
-
20
+
21
21
  # Validates boolean configuration values
22
22
  # @param [Object] value The value to validate
23
23
  # @param [String] key Configuration key name for error messages
24
24
  # @return [Boolean, nil] Validated boolean value or nil for invalid
25
25
  def validate_boolean(value, key = 'unknown')
26
26
  return nil if value.nil?
27
-
27
+
28
28
  case value
29
29
  when true, false
30
30
  value
@@ -37,7 +37,7 @@ module Jekyll
37
37
  nil
38
38
  end
39
39
  end
40
-
40
+
41
41
  # Validates integer configuration values with range checking
42
42
  # @param [Object] value The value to validate
43
43
  # @param [String] key Configuration key name
@@ -46,22 +46,22 @@ module Jekyll
46
46
  # @return [Integer, nil] Validated integer or nil for invalid
47
47
  def validate_integer(value, key = 'unknown', min = 0, max = 1_000_000)
48
48
  return nil if value.nil?
49
-
49
+
50
50
  begin
51
51
  int_value = Integer(value)
52
-
52
+
53
53
  if int_value < min || int_value > max
54
54
  Jekyll.logger.warn("Jekyll Minifier:", "Integer value for '#{key}' out of range [#{min}-#{max}]: #{int_value}. Using default.")
55
55
  return nil
56
56
  end
57
-
57
+
58
58
  int_value
59
59
  rescue ArgumentError, TypeError
60
60
  Jekyll.logger.warn("Jekyll Minifier:", "Invalid integer value for '#{key}': #{value.inspect}. Using default.")
61
61
  nil
62
62
  end
63
63
  end
64
-
64
+
65
65
  # Validates string configuration values with length and safety checks
66
66
  # @param [Object] value The value to validate
67
67
  # @param [String] key Configuration key name
@@ -70,23 +70,23 @@ module Jekyll
70
70
  def validate_string(value, key = 'unknown', max_length = MAX_SAFE_STRING_LENGTH)
71
71
  return nil if value.nil?
72
72
  return nil unless value.respond_to?(:to_s)
73
-
73
+
74
74
  str_value = value.to_s
75
-
75
+
76
76
  if str_value.length > max_length
77
77
  Jekyll.logger.warn("Jekyll Minifier:", "String value for '#{key}' too long (#{str_value.length} > #{max_length}). Using default.")
78
78
  return nil
79
79
  end
80
-
80
+
81
81
  # Basic safety check for control characters
82
82
  if str_value.match?(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/)
83
83
  Jekyll.logger.warn("Jekyll Minifier:", "String value for '#{key}' contains unsafe control characters. Using default.")
84
84
  return nil
85
85
  end
86
-
86
+
87
87
  str_value
88
88
  end
89
-
89
+
90
90
  # Validates array configuration values with size and content checks
91
91
  # @param [Object] value The value to validate
92
92
  # @param [String] key Configuration key name
@@ -94,19 +94,19 @@ module Jekyll
94
94
  # @return [Array, nil] Validated array or empty array for invalid
95
95
  def validate_array(value, key = 'unknown', max_size = MAX_SAFE_ARRAY_SIZE)
96
96
  return [] if value.nil?
97
-
97
+
98
98
  # Convert single values to arrays
99
99
  array_value = value.respond_to?(:to_a) ? value.to_a : [value]
100
-
100
+
101
101
  if array_value.size > max_size
102
102
  Jekyll.logger.warn("Jekyll Minifier:", "Array value for '#{key}' too large (#{array_value.size} > #{max_size}). Truncating.")
103
103
  array_value = array_value.take(max_size)
104
104
  end
105
-
105
+
106
106
  # Filter out invalid elements
107
107
  valid_elements = array_value.filter_map do |element|
108
108
  next nil if element.nil?
109
-
109
+
110
110
  if element.respond_to?(:to_s)
111
111
  str_element = element.to_s
112
112
  next nil if str_element.empty? || str_element.length > MAX_SAFE_STRING_LENGTH
@@ -115,10 +115,10 @@ module Jekyll
115
115
  nil
116
116
  end
117
117
  end
118
-
118
+
119
119
  valid_elements
120
120
  end
121
-
121
+
122
122
  # Validates hash configuration values with size and content checks
123
123
  # @param [Object] value The value to validate
124
124
  # @param [String] key Configuration key name
@@ -127,22 +127,22 @@ module Jekyll
127
127
  def validate_hash(value, key = 'unknown', max_size = MAX_SAFE_HASH_SIZE)
128
128
  return nil if value.nil?
129
129
  return nil unless value.respond_to?(:to_h)
130
-
130
+
131
131
  begin
132
132
  hash_value = value.to_h
133
-
133
+
134
134
  if hash_value.size > max_size
135
135
  Jekyll.logger.warn("Jekyll Minifier:", "Hash value for '#{key}' too large (#{hash_value.size} > #{max_size}). Using default.")
136
136
  return nil
137
137
  end
138
-
138
+
139
139
  # Validate hash keys and values
140
140
  validated_hash = {}
141
141
  hash_value.each do |k, v|
142
142
  # Convert keys to symbols for consistency
143
143
  key_sym = k.respond_to?(:to_sym) ? k.to_sym : nil
144
144
  next unless key_sym
145
-
145
+
146
146
  # Basic validation of values
147
147
  case v
148
148
  when String
@@ -159,14 +159,14 @@ module Jekyll
159
159
  Jekyll.logger.warn("Jekyll Minifier:", "Unsupported value type for '#{key}[#{key_sym}]': #{v.class}. Skipping.")
160
160
  end
161
161
  end
162
-
162
+
163
163
  validated_hash
164
164
  rescue => e
165
165
  Jekyll.logger.warn("Jekyll Minifier:", "Failed to validate hash for '#{key}': #{e.message}. Using default.")
166
166
  nil
167
167
  end
168
168
  end
169
-
169
+
170
170
  # Validates file content size and encoding
171
171
  # @param [String] content File content to validate
172
172
  # @param [String] file_type Type of file (css, js, html, json)
@@ -175,127 +175,45 @@ module Jekyll
175
175
  def validate_file_content(content, file_type = 'unknown', file_path = 'unknown')
176
176
  return false if content.nil?
177
177
  return false unless content.respond_to?(:bytesize)
178
-
178
+
179
179
  # Check file size
180
180
  if content.bytesize > MAX_SAFE_FILE_SIZE
181
181
  Jekyll.logger.warn("Jekyll Minifier:", "File too large for safe processing: #{file_path} (#{content.bytesize} bytes > #{MAX_SAFE_FILE_SIZE})")
182
182
  return false
183
183
  end
184
-
184
+
185
185
  # Check encoding validity
186
186
  unless content.valid_encoding?
187
187
  Jekyll.logger.warn("Jekyll Minifier:", "Invalid encoding in file: #{file_path}. Skipping minification.")
188
188
  return false
189
189
  end
190
-
191
- # Basic content validation per file type
192
- case file_type
193
- when 'css'
194
- validate_css_content(content, file_path)
195
- when 'js', 'javascript'
196
- validate_js_content(content, file_path)
197
- when 'json'
198
- validate_json_content(content, file_path)
199
- when 'html', 'xml'
200
- validate_html_content(content, file_path)
201
- else
202
- true # Unknown types pass through
203
- end
204
- end
205
-
206
- # Validates CSS content for basic syntax safety
207
- # @param [String] content CSS content
208
- # @param [String] file_path File path for error messages
209
- # @return [Boolean] True if content appears safe
210
- def validate_css_content(content, file_path = 'unknown')
211
- # Check for extremely unbalanced braces (potential malformed CSS)
212
- open_braces = content.count('{')
213
- close_braces = content.count('}')
214
-
215
- if (open_braces - close_braces).abs > 100
216
- Jekyll.logger.warn("Jekyll Minifier:", "CSS file appears malformed (unbalanced braces): #{file_path}")
217
- return false
218
- end
219
-
220
- true
221
- end
222
-
223
- # Validates JavaScript content for basic syntax safety
224
- # @param [String] content JavaScript content
225
- # @param [String] file_path File path for error messages
226
- # @return [Boolean] True if content appears safe
227
- def validate_js_content(content, file_path = 'unknown')
228
- # Check for extremely unbalanced braces and parentheses
229
- open_braces = content.count('{')
230
- close_braces = content.count('}')
231
- open_parens = content.count('(')
232
- close_parens = content.count(')')
233
-
234
- if (open_braces - close_braces).abs > 100 || (open_parens - close_parens).abs > 100
235
- Jekyll.logger.warn("Jekyll Minifier:", "JavaScript file appears malformed (unbalanced braces/parens): #{file_path}")
236
- return false
237
- end
238
-
239
- true
240
- end
241
-
242
- # Validates JSON content for syntax safety
243
- # @param [String] content JSON content
244
- # @param [String] file_path File path for error messages
245
- # @return [Boolean] True if content appears safe
246
- def validate_json_content(content, file_path = 'unknown')
247
- # Basic JSON structure validation without full parsing
248
- trimmed = content.strip
249
-
250
- unless (trimmed.start_with?('{') && trimmed.end_with?('}')) ||
251
- (trimmed.start_with?('[') && trimmed.end_with?(']'))
252
- Jekyll.logger.warn("Jekyll Minifier:", "JSON file doesn't appear to have valid structure: #{file_path}")
253
- return false
254
- end
255
-
256
- true
257
- end
258
-
259
- # Validates HTML content for basic syntax safety
260
- # @param [String] content HTML content
261
- # @param [String] file_path File path for error messages
262
- # @return [Boolean] True if content appears safe
263
- def validate_html_content(content, file_path = 'unknown')
264
- # Basic HTML tag balance check
265
- open_tags = content.scan(/<[^\/>]+>/).length
266
- close_tags = content.scan(/<\/[^>]+>/).length
267
- self_closing = content.scan(/<[^>]+\/>/).length
268
-
269
- # Allow for reasonable imbalance (HTML5 void elements, etc.)
270
- if (open_tags - close_tags - self_closing).abs > 50
271
- Jekyll.logger.warn("Jekyll Minifier:", "HTML file appears malformed (unbalanced tags): #{file_path}")
272
- return false
273
- end
274
-
190
+
191
+ # Content validation is handled by the actual minification libraries
192
+ # They will properly parse and validate the content
275
193
  true
276
194
  end
277
-
195
+
278
196
  # Validates file paths for security issues
279
197
  # @param [String] path File path to validate
280
198
  # @return [Boolean] True if path is safe
281
199
  def validate_file_path(path)
282
200
  return false if path.nil? || path.empty?
283
201
  return false unless path.respond_to?(:to_s)
284
-
202
+
285
203
  path_str = path.to_s
286
-
204
+
287
205
  # Check for directory traversal attempts
288
206
  if path_str.include?('../') || path_str.include?('..\\') || path_str.include?('~/')
289
207
  Jekyll.logger.warn("Jekyll Minifier:", "Unsafe file path detected: #{path_str}")
290
208
  return false
291
209
  end
292
-
210
+
293
211
  # Check for null bytes
294
212
  if path_str.include?("\0")
295
213
  Jekyll.logger.warn("Jekyll Minifier:", "File path contains null byte: #{path_str}")
296
214
  return false
297
215
  end
298
-
216
+
299
217
  true
300
218
  end
301
219
  end
@@ -329,7 +247,7 @@ module Jekyll
329
247
  def get_or_create(type, cache_key, &factory_block)
330
248
  @cache_mutex.synchronize do
331
249
  cache = @compressor_caches[type]
332
-
250
+
333
251
  if cache.key?(cache_key)
334
252
  # Cache hit - move to end for LRU
335
253
  compressor = cache.delete(cache_key)
@@ -339,14 +257,14 @@ module Jekyll
339
257
  else
340
258
  # Cache miss - create new compressor
341
259
  compressor = factory_block.call
342
-
260
+
343
261
  # Evict oldest entry if cache is full
344
262
  if cache.size >= MAX_CACHE_SIZE
345
263
  evicted_key = cache.keys.first
346
264
  cache.delete(evicted_key)
347
265
  @cache_stats[:evictions] += 1
348
266
  end
349
-
267
+
350
268
  cache[cache_key] = compressor
351
269
  @cache_stats[:misses] += 1
352
270
  compressor
@@ -359,7 +277,7 @@ module Jekyll
359
277
  # @return [String] Unique cache key
360
278
  def generate_cache_key(config_hash)
361
279
  return 'default' if config_hash.nil? || config_hash.empty?
362
-
280
+
363
281
  # Sort keys for consistent hashing
364
282
  sorted_config = config_hash.sort.to_h
365
283
  # Use SHA256 for consistent, collision-resistant keys
@@ -489,7 +407,7 @@ module Jekyll
489
407
  # Create sub-compressors first (outside the HTML cache lock)
490
408
  css_compressor = create_css_compressor_uncached(config)
491
409
  js_compressor = create_js_compressor_uncached(config)
492
-
410
+
493
411
  # Create fresh args hash for this instance
494
412
  fresh_html_args = html_args.dup
495
413
  fresh_html_args[:css_compressor] = css_compressor
@@ -531,7 +449,7 @@ module Jekyll
531
449
  Jekyll.logger.warn("Jekyll Minifier:", "Skipping CSS compression for unsafe content: #{file_path}")
532
450
  return content
533
451
  end
534
-
452
+
535
453
  begin
536
454
  if config.css_enhanced_mode? && config.css_enhanced_options
537
455
  CSSminify2.compress_enhanced(content, config.css_enhanced_options)
@@ -557,7 +475,7 @@ module Jekyll
557
475
  Jekyll.logger.warn("Jekyll Minifier:", "Skipping JavaScript compression for unsafe content: #{file_path}")
558
476
  return content
559
477
  end
560
-
478
+
561
479
  begin
562
480
  compressor = create_js_compressor(config)
563
481
  compressor.compile(content)
@@ -577,7 +495,7 @@ module Jekyll
577
495
  Jekyll.logger.warn("Jekyll Minifier:", "Skipping JSON compression for unsafe content: #{file_path}")
578
496
  return content
579
497
  end
580
-
498
+
581
499
  begin
582
500
  JSON.minify(content)
583
501
  rescue => e
@@ -591,7 +509,7 @@ module Jekyll
591
509
  class CompressionConfig
592
510
  # Configuration key constants to eliminate magic strings
593
511
  CONFIG_ROOT = 'jekyll-minifier'
594
-
512
+
595
513
  # HTML Compression Options
596
514
  HTML_REMOVE_SPACES_INSIDE_TAGS = 'remove_spaces_inside_tags'
597
515
  HTML_REMOVE_MULTI_SPACES = 'remove_multi_spaces'
@@ -612,12 +530,12 @@ module Jekyll
612
530
  HTML_PRESERVE_LINE_BREAKS = 'preserve_line_breaks'
613
531
  HTML_SIMPLE_BOOLEAN_ATTRIBUTES = 'simple_boolean_attributes'
614
532
  HTML_COMPRESS_JS_TEMPLATES = 'compress_js_templates'
615
-
533
+
616
534
  # File Type Compression Toggles
617
535
  COMPRESS_CSS = 'compress_css'
618
536
  COMPRESS_JAVASCRIPT = 'compress_javascript'
619
537
  COMPRESS_JSON = 'compress_json'
620
-
538
+
621
539
  # Enhanced CSS Compression Options (cssminify2 v2.1.0+)
622
540
  CSS_MERGE_DUPLICATE_SELECTORS = 'css_merge_duplicate_selectors'
623
541
  CSS_OPTIMIZE_SHORTHAND_PROPERTIES = 'css_optimize_shorthand_properties'
@@ -625,28 +543,28 @@ module Jekyll
625
543
  CSS_PRESERVE_IE_HACKS = 'css_preserve_ie_hacks'
626
544
  CSS_COMPRESS_VARIABLES = 'css_compress_variables'
627
545
  CSS_ENHANCED_MODE = 'css_enhanced_mode'
628
-
546
+
629
547
  # JavaScript/Terser Configuration
630
548
  TERSER_ARGS = 'terser_args'
631
549
  UGLIFIER_ARGS = 'uglifier_args' # Backward compatibility
632
-
550
+
633
551
  # Pattern Preservation
634
552
  PRESERVE_PATTERNS = 'preserve_patterns'
635
553
  PRESERVE_PHP = 'preserve_php'
636
-
554
+
637
555
  # File Exclusions
638
556
  EXCLUDE = 'exclude'
639
557
 
640
558
  def initialize(site_config)
641
559
  @config = site_config || {}
642
560
  @raw_minifier_config = @config[CONFIG_ROOT] || {}
643
-
561
+
644
562
  # Validate and sanitize the configuration
645
563
  @minifier_config = validate_configuration(@raw_minifier_config)
646
-
564
+
647
565
  # Pre-compute commonly used values for performance
648
566
  @computed_values = {}
649
-
567
+
650
568
  # Pre-compile terser arguments for JavaScript compression
651
569
  _compute_terser_args
652
570
  end
@@ -710,7 +628,7 @@ module Jekyll
710
628
  # Generate enhanced CSS compression options hash
711
629
  def css_enhanced_options
712
630
  return nil unless css_enhanced_mode?
713
-
631
+
714
632
  {
715
633
  merge_duplicate_selectors: css_merge_duplicate_selectors?,
716
634
  optimize_shorthand_properties: css_optimize_shorthand_properties?,
@@ -733,7 +651,7 @@ module Jekyll
733
651
  def preserve_patterns
734
652
  patterns = get_array(PRESERVE_PATTERNS)
735
653
  return patterns unless patterns.empty?
736
-
654
+
737
655
  # Return empty array if no patterns configured
738
656
  []
739
657
  end
@@ -763,11 +681,11 @@ module Jekyll
763
681
  private
764
682
 
765
683
  def base_html_args
766
- {
767
- remove_comments: true,
768
- compress_css: true,
769
- compress_javascript: true,
770
- preserve_patterns: []
684
+ {
685
+ remove_comments: true,
686
+ compress_css: true,
687
+ compress_javascript: true,
688
+ preserve_patterns: []
771
689
  }
772
690
  end
773
691
 
@@ -804,7 +722,7 @@ module Jekyll
804
722
 
805
723
  def apply_preserve_patterns(args)
806
724
  args[:preserve_patterns] += [php_preserve_pattern] if preserve_php?
807
-
725
+
808
726
  configured_patterns = preserve_patterns
809
727
  if !configured_patterns.empty? && configured_patterns.respond_to?(:map)
810
728
  compiled_patterns = compile_preserve_patterns(configured_patterns)
@@ -817,23 +735,23 @@ module Jekyll
817
735
  # @return [Hash] Validated and sanitized configuration
818
736
  def validate_configuration(raw_config)
819
737
  return {} unless raw_config.respond_to?(:to_h)
820
-
738
+
821
739
  validated_config = {}
822
-
740
+
823
741
  raw_config.each do |key, value|
824
742
  validated_key = ValidationHelpers.validate_string(key, "config_key", 100)
825
743
  next unless validated_key
826
-
744
+
827
745
  validated_value = validate_config_value(validated_key, value)
828
746
  validated_config[validated_key] = validated_value unless validated_value.nil?
829
747
  end
830
-
748
+
831
749
  validated_config
832
750
  rescue => e
833
751
  Jekyll.logger.warn("Jekyll Minifier:", "Configuration validation failed: #{e.message}. Using defaults.")
834
752
  {}
835
753
  end
836
-
754
+
837
755
  # Validates individual configuration values based on their key
838
756
  # @param [String] key Configuration key
839
757
  # @param [Object] value Configuration value
@@ -842,32 +760,32 @@ module Jekyll
842
760
  case key
843
761
  # Boolean HTML compression options
844
762
  when HTML_REMOVE_SPACES_INSIDE_TAGS, HTML_REMOVE_MULTI_SPACES, HTML_REMOVE_COMMENTS,
845
- HTML_REMOVE_INTERTAG_SPACES, HTML_REMOVE_QUOTES, HTML_COMPRESS_CSS,
763
+ HTML_REMOVE_INTERTAG_SPACES, HTML_REMOVE_QUOTES, HTML_COMPRESS_CSS,
846
764
  HTML_COMPRESS_JAVASCRIPT, HTML_SIMPLE_DOCTYPE, HTML_REMOVE_SCRIPT_ATTRIBUTES,
847
765
  HTML_REMOVE_STYLE_ATTRIBUTES, HTML_REMOVE_LINK_ATTRIBUTES, HTML_REMOVE_FORM_ATTRIBUTES,
848
766
  HTML_REMOVE_INPUT_ATTRIBUTES, HTML_REMOVE_JAVASCRIPT_PROTOCOL, HTML_REMOVE_HTTP_PROTOCOL,
849
767
  HTML_REMOVE_HTTPS_PROTOCOL, HTML_PRESERVE_LINE_BREAKS, HTML_SIMPLE_BOOLEAN_ATTRIBUTES,
850
768
  HTML_COMPRESS_JS_TEMPLATES, COMPRESS_CSS, COMPRESS_JAVASCRIPT, COMPRESS_JSON,
851
- CSS_MERGE_DUPLICATE_SELECTORS, CSS_OPTIMIZE_SHORTHAND_PROPERTIES,
769
+ CSS_MERGE_DUPLICATE_SELECTORS, CSS_OPTIMIZE_SHORTHAND_PROPERTIES,
852
770
  CSS_ADVANCED_COLOR_OPTIMIZATION, CSS_PRESERVE_IE_HACKS, CSS_COMPRESS_VARIABLES,
853
771
  CSS_ENHANCED_MODE, PRESERVE_PHP
854
772
  ValidationHelpers.validate_boolean(value, key)
855
-
773
+
856
774
  # Array configurations - for backward compatibility, don't validate these strictly
857
775
  when PRESERVE_PATTERNS, EXCLUDE
858
776
  # Let the existing get_array method handle the conversion for backward compatibility
859
777
  value
860
-
778
+
861
779
  # Hash configurations (Terser/Uglifier args)
862
780
  when TERSER_ARGS, UGLIFIER_ARGS
863
781
  validate_compressor_args(value, key)
864
-
782
+
865
783
  else
866
784
  # Pass through other values for backward compatibility
867
785
  value
868
786
  end
869
787
  end
870
-
788
+
871
789
  # Validates compressor arguments (Terser/Uglifier) with security checks
872
790
  # @param [Object] value Compressor arguments
873
791
  # @param [String] key Configuration key name
@@ -875,7 +793,7 @@ module Jekyll
875
793
  def validate_compressor_args(value, key)
876
794
  validated_hash = ValidationHelpers.validate_hash(value, key, 20) # Limit to 20 options
877
795
  return nil unless validated_hash
878
-
796
+
879
797
  # Additional validation for known dangerous options
880
798
  safe_args = {}
881
799
  validated_hash.each do |k, v|
@@ -922,7 +840,7 @@ module Jekyll
922
840
  end
923
841
  end
924
842
  end
925
-
843
+
926
844
  safe_args.empty? ? nil : safe_args
927
845
  end
928
846
 
@@ -937,7 +855,7 @@ module Jekyll
937
855
  def get_array(key)
938
856
  value = @minifier_config[key]
939
857
  return [] if value.nil?
940
-
858
+
941
859
  # For backward compatibility, if value exists but isn't an array, convert it
942
860
  return value if value.respond_to?(:to_a)
943
861
  [value]
@@ -948,11 +866,11 @@ module Jekyll
948
866
  # Support both terser_args and uglifier_args for backward compatibility
949
867
  # Use validated configuration
950
868
  terser_options = @minifier_config[TERSER_ARGS] || @minifier_config[UGLIFIER_ARGS]
951
-
869
+
952
870
  if terser_options && terser_options.respond_to?(:map)
953
871
  # Apply validation to the terser options
954
872
  validated_options = validate_compressor_args(terser_options, TERSER_ARGS)
955
-
873
+
956
874
  if validated_options && !validated_options.empty?
957
875
  # Convert keys to symbols for consistency
958
876
  @computed_values[:terser_args] = Hash[validated_options.map{|(k,v)| [k.to_sym,v]}]
@@ -970,7 +888,7 @@ module Jekyll
970
888
  # This will be made accessible through dependency injection
971
889
  def compile_preserve_patterns(patterns)
972
890
  return [] unless patterns.respond_to?(:map)
973
-
891
+
974
892
  patterns.filter_map { |pattern| compile_single_pattern(pattern) }
975
893
  end
976
894
 
@@ -997,24 +915,24 @@ module Jekyll
997
915
  def valid_regex_pattern?(pattern)
998
916
  return false unless pattern.is_a?(String) && !pattern.empty? && !pattern.strip.empty?
999
917
  return false if pattern.length > 1000
1000
-
918
+
1001
919
  # Basic ReDoS vulnerability checks using a more efficient approach
1002
920
  redos_checks = [
1003
921
  /\([^)]*[+*]\)[+*]/, # nested quantifiers
1004
922
  /\([^)]*\|[^)]*\)[+*]/ # alternation with overlapping patterns
1005
923
  ]
1006
-
924
+
1007
925
  return false if redos_checks.any? { |check| pattern =~ check }
1008
926
  return false if pattern.count('(') > 10 # excessive nesting
1009
927
  return false if pattern.scan(/[+*?]\??/).length > 20 # excessive quantifiers
1010
-
928
+
1011
929
  true
1012
930
  end
1013
931
 
1014
932
  def compile_regex_with_timeout(pattern, timeout_seconds)
1015
933
  result = nil
1016
934
  thread = Thread.new { result = create_regex_safely(pattern) }
1017
-
935
+
1018
936
  if thread.join(timeout_seconds)
1019
937
  result
1020
938
  else
@@ -1042,7 +960,7 @@ module Jekyll
1042
960
 
1043
961
  def output_compressed(path, context)
1044
962
  extension = File.extname(path)
1045
-
963
+
1046
964
  case extension
1047
965
  when '.js'
1048
966
  output_js_or_file(path, context)
@@ -1073,21 +991,21 @@ module Jekyll
1073
991
 
1074
992
  def output_html(path, content)
1075
993
  return output_file(path, content) unless production_environment?
1076
-
994
+
1077
995
  # Validate file path for security
1078
996
  unless Jekyll::Minifier::ValidationHelpers.validate_file_path(path)
1079
997
  Jekyll.logger.warn("Jekyll Minifier:", "Unsafe file path detected, skipping compression: #{path}")
1080
- return output_file(path, content)
998
+ return # Don't write anything for unsafe paths
1081
999
  end
1082
-
1000
+
1083
1001
  # Validate content before compression
1084
1002
  unless Jekyll::Minifier::ValidationHelpers.validate_file_content(content, 'html', path)
1085
1003
  Jekyll.logger.warn("Jekyll Minifier:", "Unsafe HTML content detected, skipping compression: #{path}")
1086
1004
  return output_file(path, content)
1087
1005
  end
1088
-
1006
+
1089
1007
  config = Jekyll::Minifier::CompressionConfig.new(@site.config)
1090
-
1008
+
1091
1009
  begin
1092
1010
  compressor = Jekyll::Minifier::CompressorFactory.create_html_compressor(config)
1093
1011
  compressed_content = compressor.compress(content)
@@ -1100,48 +1018,48 @@ module Jekyll
1100
1018
 
1101
1019
  def output_js(path, content)
1102
1020
  return output_file(path, content) unless production_environment?
1103
-
1021
+
1104
1022
  # Validate file path for security
1105
1023
  unless Jekyll::Minifier::ValidationHelpers.validate_file_path(path)
1106
1024
  Jekyll.logger.warn("Jekyll Minifier:", "Unsafe file path detected, skipping compression: #{path}")
1107
- return output_file(path, content)
1025
+ return # Don't write anything for unsafe paths
1108
1026
  end
1109
-
1027
+
1110
1028
  config = Jekyll::Minifier::CompressionConfig.new(@site.config)
1111
1029
  return output_file(path, content) unless config.compress_javascript?
1112
-
1030
+
1113
1031
  compressed_content = Jekyll::Minifier::CompressorFactory.compress_js(content, config, path)
1114
1032
  output_file(path, compressed_content)
1115
1033
  end
1116
1034
 
1117
1035
  def output_json(path, content)
1118
1036
  return output_file(path, content) unless production_environment?
1119
-
1037
+
1120
1038
  # Validate file path for security
1121
1039
  unless Jekyll::Minifier::ValidationHelpers.validate_file_path(path)
1122
1040
  Jekyll.logger.warn("Jekyll Minifier:", "Unsafe file path detected, skipping compression: #{path}")
1123
- return output_file(path, content)
1041
+ return # Don't write anything for unsafe paths
1124
1042
  end
1125
-
1043
+
1126
1044
  config = Jekyll::Minifier::CompressionConfig.new(@site.config)
1127
1045
  return output_file(path, content) unless config.compress_json?
1128
-
1046
+
1129
1047
  compressed_content = Jekyll::Minifier::CompressorFactory.compress_json(content, path)
1130
1048
  output_file(path, compressed_content)
1131
1049
  end
1132
1050
 
1133
1051
  def output_css(path, content)
1134
1052
  return output_file(path, content) unless production_environment?
1135
-
1053
+
1136
1054
  # Validate file path for security
1137
1055
  unless Jekyll::Minifier::ValidationHelpers.validate_file_path(path)
1138
1056
  Jekyll.logger.warn("Jekyll Minifier:", "Unsafe file path detected, skipping compression: #{path}")
1139
- return output_file(path, content)
1057
+ return # Don't write anything for unsafe paths
1140
1058
  end
1141
-
1059
+
1142
1060
  config = Jekyll::Minifier::CompressionConfig.new(@site.config)
1143
1061
  return output_file(path, content) unless config.compress_css?
1144
-
1062
+
1145
1063
  compressed_content = Jekyll::Minifier::CompressorFactory.compress_css(content, config, path)
1146
1064
  output_file(path, compressed_content)
1147
1065
  end
@@ -1238,7 +1156,7 @@ module Jekyll
1238
1156
  def process_static_file(dest_path)
1239
1157
  extension = File.extname(dest_path)
1240
1158
  content = File.read(path)
1241
-
1159
+
1242
1160
  case extension
1243
1161
  when '.js'
1244
1162
  process_js_file(dest_path, content)
@@ -24,48 +24,56 @@ describe "Jekyll Minifier Environment Validation" do
24
24
  # Verify files exist and are minified
25
25
  expect(File.exist?(dest_dir("assets/css/style.css"))).to be true
26
26
  expect(File.exist?(dest_dir("assets/js/script.js"))).to be true
27
-
27
+
28
28
  # Verify actual minification occurred
29
29
  css_content = File.read(dest_dir("assets/css/style.css"))
30
30
  js_content = File.read(dest_dir("assets/js/script.js"))
31
-
31
+
32
32
  # CSS should be minified (single line, no comments)
33
33
  expect(css_content.lines.count).to eq(1), "CSS should be minified to single line"
34
34
  expect(css_content).not_to include(" "), "CSS should not contain double spaces"
35
-
35
+
36
36
  # JS should be minified (no comments, shortened variables)
37
37
  expect(js_content).not_to include("// "), "JS should not contain comments"
38
38
  expect(js_content).not_to include("\n "), "JS should not contain indentation"
39
-
39
+
40
40
  puts "✓ Production environment: Minification active"
41
41
  puts " - CSS minified: #{css_content.length} characters"
42
42
  puts " - JS minified: #{js_content.length} characters"
43
43
  end
44
44
  end
45
45
 
46
- context "Development Environment Simulation" do
46
+ context "Environment Dependency Validation" do
47
+ before(:each) do
48
+ # Mock the environment as production to ensure minification works
49
+ allow(ENV).to receive(:[]).and_call_original
50
+ allow(ENV).to receive(:[]).with('JEKYLL_ENV').and_return('production')
51
+ site = Jekyll::Site.new(config)
52
+ site.process
53
+ end
54
+
47
55
  it "verifies environment check exists in the minifier" do
48
56
  # Read the main library file to ensure it checks for JEKYLL_ENV
49
57
  minifier_code = File.read(File.expand_path('../../lib/jekyll-minifier.rb', __FILE__))
50
-
58
+
51
59
  # Verify the environment check exists
52
60
  expect(minifier_code).to include('JEKYLL_ENV'), "Minifier should check JEKYLL_ENV"
53
61
  expect(minifier_code).to include('production'), "Minifier should check for production environment"
54
-
62
+
55
63
  puts "✓ Development environment check: Environment validation exists in code"
56
64
  end
57
65
 
58
66
  it "demonstrates that minification is environment-dependent" do
59
- # This test confirms that our current setup (production) results in minified files
60
- # The actual behavior in development would be different (no minification)
61
-
67
+ # This test confirms that when JEKYLL_ENV is set to production, minification occurs
68
+ # We're mocking production environment to ensure the minifier works correctly
69
+
62
70
  current_env = ENV['JEKYLL_ENV']
63
71
  expect(current_env).to eq('production'), "Test is running in production mode as expected"
64
-
72
+
65
73
  # In production, files should be minified
66
74
  css_content = File.read(dest_dir("assets/css/style.css"))
67
75
  expect(css_content.lines.count).to eq(1), "In production, CSS should be minified"
68
-
76
+
69
77
  puts "✓ Environment behavior: Confirmed minification only occurs in production"
70
78
  puts " - Current test environment: #{current_env}"
71
79
  puts " - Minification active: true"
@@ -77,8 +85,8 @@ describe "Jekyll Minifier Environment Validation" do
77
85
  # Verify the minifier is included in Jekyll plugins
78
86
  config_content = File.read(source_dir("_config.yml"))
79
87
  expect(config_content).to include('jekyll-minifier'), "Jekyll config should include minifier plugin"
80
-
88
+
81
89
  puts "✓ Configuration validation: Jekyll properly configured for minification"
82
90
  end
83
91
  end
84
- end
92
+ end
@@ -40,10 +40,10 @@ describe "Jekyll Minifier - Input Validation" do
40
40
  it "handles invalid boolean values gracefully" do
41
41
  expect(Jekyll.logger).to receive(:warn).with("Jekyll Minifier:", /Invalid boolean value/)
42
42
  expect(validator.validate_boolean('invalid', 'test')).to be_nil
43
-
43
+
44
44
  expect(Jekyll.logger).to receive(:warn).with("Jekyll Minifier:", /Invalid boolean value/)
45
45
  expect(validator.validate_boolean(42, 'test')).to be_nil
46
-
46
+
47
47
  expect(Jekyll.logger).to receive(:warn).with("Jekyll Minifier:", /Invalid boolean value/)
48
48
  expect(validator.validate_boolean([], 'test')).to be_nil
49
49
  end
@@ -63,7 +63,7 @@ describe "Jekyll Minifier - Input Validation" do
63
63
  it "enforces range limits" do
64
64
  expect(Jekyll.logger).to receive(:warn).with("Jekyll Minifier:", /out of range/)
65
65
  expect(validator.validate_integer(-5, 'test', 0, 100)).to be_nil
66
-
66
+
67
67
  expect(Jekyll.logger).to receive(:warn).with("Jekyll Minifier:", /out of range/)
68
68
  expect(validator.validate_integer(150, 'test', 0, 100)).to be_nil
69
69
  end
@@ -71,7 +71,7 @@ describe "Jekyll Minifier - Input Validation" do
71
71
  it "handles invalid integer values gracefully" do
72
72
  expect(Jekyll.logger).to receive(:warn).with("Jekyll Minifier:", /Invalid integer value/)
73
73
  expect(validator.validate_integer('not_a_number', 'test')).to be_nil
74
-
74
+
75
75
  expect(Jekyll.logger).to receive(:warn).with("Jekyll Minifier:", /Invalid integer value/)
76
76
  expect(validator.validate_integer([], 'test')).to be_nil
77
77
  end
@@ -145,31 +145,34 @@ describe "Jekyll Minifier - Input Validation" do
145
145
  expect(validator.validate_file_content(invalid_content, 'txt', 'bad.txt')).to be(false)
146
146
  end
147
147
 
148
- it "validates CSS content structure" do
148
+ it "delegates CSS validation to minification libraries" do
149
149
  valid_css = 'body { margin: 0; }'
150
150
  expect(validator.validate_file_content(valid_css, 'css', 'style.css')).to be(true)
151
-
151
+
152
+ # Malformed CSS passes basic validation - actual validation happens in the minifier
152
153
  malformed_css = 'body { margin: 0; ' + '{' * 150 # Too many unbalanced braces
153
- expect(Jekyll.logger).to receive(:warn).with("Jekyll Minifier:", /malformed/)
154
- expect(validator.validate_file_content(malformed_css, 'css', 'bad.css')).to be(false)
154
+ # No warning expected anymore - content validation is delegated
155
+ expect(validator.validate_file_content(malformed_css, 'css', 'bad.css')).to be(true)
155
156
  end
156
157
 
157
- it "validates JavaScript content structure" do
158
+ it "delegates JavaScript validation to minification libraries" do
158
159
  valid_js = 'function test() { return true; }'
159
160
  expect(validator.validate_file_content(valid_js, 'js', 'script.js')).to be(true)
160
-
161
+
162
+ # Malformed JS passes basic validation - actual validation happens in the minifier
161
163
  malformed_js = 'function test() { return true; ' + '(' * 150 # Too many unbalanced parens
162
- expect(Jekyll.logger).to receive(:warn).with("Jekyll Minifier:", /malformed/)
163
- expect(validator.validate_file_content(malformed_js, 'js', 'bad.js')).to be(false)
164
+ # No warning expected anymore - content validation is delegated
165
+ expect(validator.validate_file_content(malformed_js, 'js', 'bad.js')).to be(true)
164
166
  end
165
167
 
166
- it "validates JSON content structure" do
168
+ it "delegates JSON validation to minification libraries" do
167
169
  valid_json = '{"key": "value"}'
168
170
  expect(validator.validate_file_content(valid_json, 'json', 'data.json')).to be(true)
169
-
171
+
172
+ # Invalid JSON passes basic validation - actual validation happens in the minifier
170
173
  invalid_json = 'not json at all'
171
- expect(Jekyll.logger).to receive(:warn).with("Jekyll Minifier:", /valid structure/)
172
- expect(validator.validate_file_content(invalid_json, 'json', 'bad.json')).to be(false)
174
+ # No warning expected anymore - content validation is delegated
175
+ expect(validator.validate_file_content(invalid_json, 'json', 'bad.json')).to be(true)
173
176
  end
174
177
  end
175
178
 
@@ -182,10 +185,10 @@ describe "Jekyll Minifier - Input Validation" do
182
185
  it "rejects directory traversal attempts" do
183
186
  expect(Jekyll.logger).to receive(:warn).with("Jekyll Minifier:", /Unsafe file path/)
184
187
  expect(validator.validate_file_path('../../../etc/passwd')).to be(false)
185
-
188
+
186
189
  expect(Jekyll.logger).to receive(:warn).with("Jekyll Minifier:", /Unsafe file path/)
187
190
  expect(validator.validate_file_path('path\\..\\..\\windows')).to be(false)
188
-
191
+
189
192
  expect(Jekyll.logger).to receive(:warn).with("Jekyll Minifier:", /Unsafe file path/)
190
193
  expect(validator.validate_file_path('~/secrets')).to be(false)
191
194
  end
@@ -226,17 +229,17 @@ describe "Jekyll Minifier - Input Validation" do
226
229
  end
227
230
 
228
231
  config_obj = Jekyll::Minifier::CompressionConfig.new(config)
229
-
232
+
230
233
  # Should handle invalid values gracefully - some with defaults, some with conversion
231
234
  expect(config_obj.compress_css?).to be(true) # Default for invalid boolean
232
235
  expect(config_obj.compress_javascript?).to be(true) # Default for invalid boolean
233
236
  expect(config_obj.preserve_patterns).to eq(["not_an_array"]) # Converted to array for backward compatibility
234
- # exclude_patterns will return the hash as-is for backward compatibility,
237
+ # exclude_patterns will return the hash as-is for backward compatibility,
235
238
  # but get_array will convert it properly when accessed
236
239
  expect(config_obj.exclude_patterns).to be_a(Hash) # Returns invalid hash as-is for compatibility
237
240
  expect(config_obj.terser_args).to be_nil # Nil for invalid hash
238
241
  expect(config_obj.remove_comments).to be(true) # Default for invalid boolean
239
-
242
+
240
243
  # Should have generated warnings
241
244
  expect(warnings.any? { |w| w.include?('Invalid boolean value') }).to be(true)
242
245
  end
@@ -262,7 +265,7 @@ describe "Jekyll Minifier - Input Validation" do
262
265
  allow(Jekyll.logger).to receive(:warn) do |prefix, message|
263
266
  warnings << "#{prefix} #{message}"
264
267
  end
265
-
268
+
266
269
  info_messages = []
267
270
  allow(Jekyll.logger).to receive(:info) do |prefix, message|
268
271
  info_messages << "#{prefix} #{message}"
@@ -270,7 +273,7 @@ describe "Jekyll Minifier - Input Validation" do
270
273
 
271
274
  config_obj = Jekyll::Minifier::CompressionConfig.new(config)
272
275
  terser_args = config_obj.terser_args
273
-
276
+
274
277
  expect(terser_args).to be_a(Hash)
275
278
  expect(terser_args[:eval]).to be(true) # Allowed after validation
276
279
  # Terser args should be present and have some validated options
@@ -279,7 +282,7 @@ describe "Jekyll Minifier - Input Validation" do
279
282
  expect(terser_args[:unknown_option]).to eq("test")
280
283
  expect(terser_args[:ecma]).to eq(2015)
281
284
  expect(terser_args).not_to have_key(:harmony) # Should be filtered
282
-
285
+
283
286
  # Should log filtering of harmony option
284
287
  expect(info_messages.any? { |m| m.include?('harmony') }).to be(true)
285
288
  end
@@ -302,12 +305,12 @@ describe "Jekyll Minifier - Input Validation" do
302
305
  end
303
306
 
304
307
  config_obj = Jekyll::Minifier::CompressionConfig.new(config)
305
-
308
+
306
309
  # For backward compatibility, arrays are not truncated during config validation
307
310
  # Size limits are applied at the ValidationHelpers level when explicitly called
308
311
  expect(config_obj.preserve_patterns.size).to eq(150) # Full array preserved for compatibility
309
312
  expect(config_obj.exclude_patterns.size).to eq(150) # Full array preserved for compatibility
310
-
313
+
311
314
  # The arrays are preserved for backward compatibility
312
315
  # Validation warnings may occur depending on internal implementation
313
316
  expect(config_obj).to be_a(Jekyll::Minifier::CompressionConfig)
@@ -323,7 +326,7 @@ describe "Jekyll Minifier - Input Validation" do
323
326
 
324
327
  it "handles malformed configuration gracefully" do
325
328
  config_obj = Jekyll::Minifier::CompressionConfig.new(config)
326
-
329
+
327
330
  # Should use all defaults
328
331
  expect(config_obj.compress_css?).to be(true)
329
332
  expect(config_obj.compress_javascript?).to be(true)
@@ -336,46 +339,51 @@ describe "Jekyll Minifier - Input Validation" do
336
339
  describe "Content validation during compression" do
337
340
  context "with oversized files" do
338
341
  it "skips compression for files that are too large" do
339
- # Create a large content string
340
- large_content = 'a' * (60 * 1024 * 1024) # 60MB
341
-
342
+ # Create a large content string just above the 50MB limit
343
+ large_content = 'a' * (51 * 1024 * 1024) # 51MB
344
+
342
345
  expect(Jekyll.logger).to receive(:warn).with("Jekyll Minifier:", /too large/)
343
346
  expect(Jekyll.logger).to receive(:warn).with("Jekyll Minifier:", /Skipping CSS compression/)
344
-
345
- # Create a test compressor with proper site reference
347
+
348
+ # Create a test compressor with proper site reference and mock output_file
346
349
  test_compressor = Class.new do
347
350
  include Jekyll::Compressor
348
351
  attr_accessor :site
349
-
352
+
350
353
  def initialize(site)
351
354
  @site = site
352
355
  end
356
+
357
+ # Override output_file to prevent actual disk writes during testing
358
+ def output_file(dest, content)
359
+ # Do nothing - prevent file write
360
+ end
353
361
  end
354
-
362
+
355
363
  compressor = test_compressor.new(site)
356
-
357
- # Should return original content without compression
364
+
365
+ # Should return without writing to disk
358
366
  compressor.output_css('test.css', large_content)
359
367
  end
360
368
  end
361
369
 
362
370
  context "with malformed content" do
363
- it "handles CSS with too many unbalanced braces" do
371
+ it "delegates CSS validation to the minifier library" do
364
372
  malformed_css = 'body { margin: 0; ' + '{' * 150
365
-
366
- expect(Jekyll.logger).to receive(:warn).with("Jekyll Minifier:", /malformed/)
367
- expect(Jekyll.logger).to receive(:warn).with("Jekyll Minifier:", /Skipping CSS compression/)
368
-
373
+
374
+ # CSS minifier will handle the malformed CSS itself
375
+ # CSSminify2 doesn't necessarily warn - it just returns what it can process
376
+
369
377
  # Create a test compressor with proper site reference
370
378
  test_compressor = Class.new do
371
379
  include Jekyll::Compressor
372
380
  attr_accessor :site
373
-
381
+
374
382
  def initialize(site)
375
383
  @site = site
376
384
  end
377
385
  end
378
-
386
+
379
387
  compressor = test_compressor.new(site)
380
388
  compressor.output_css('bad.css', malformed_css)
381
389
  end
@@ -383,21 +391,21 @@ describe "Jekyll Minifier - Input Validation" do
383
391
  it "handles JavaScript with compression errors gracefully" do
384
392
  # Test with truly invalid JavaScript that will cause Terser to fail
385
393
  invalid_js = 'function test() { return <invalid syntax> ; }'
386
-
394
+
387
395
  expect(Jekyll.logger).to receive(:warn).with("Jekyll Minifier:", /compression failed/)
388
-
396
+
389
397
  # Create a test compressor with proper site reference
390
398
  test_compressor = Class.new do
391
399
  include Jekyll::Compressor
392
400
  attr_accessor :site
393
-
401
+
394
402
  def initialize(site)
395
403
  @site = site
396
404
  end
397
405
  end
398
-
406
+
399
407
  compressor = test_compressor.new(site)
400
-
408
+
401
409
  # Should handle the error and use original content
402
410
  compressor.output_js('bad.js', invalid_js)
403
411
  end
@@ -407,18 +415,20 @@ describe "Jekyll Minifier - Input Validation" do
407
415
  it "rejects directory traversal in file paths" do
408
416
  expect(Jekyll.logger).to receive(:warn).with("Jekyll Minifier:", /Unsafe file path/)
409
417
  expect(Jekyll.logger).to receive(:warn).with("Jekyll Minifier:", /skipping compression/)
410
-
418
+
411
419
  # Create a test compressor with proper site reference
412
420
  test_compressor = Class.new do
413
421
  include Jekyll::Compressor
414
422
  attr_accessor :site
415
-
423
+
416
424
  def initialize(site)
417
425
  @site = site
418
426
  end
419
427
  end
420
-
428
+
421
429
  compressor = test_compressor.new(site)
430
+
431
+ # This should trigger the file path validation and skip compression
422
432
  compressor.output_css('../../../etc/passwd', 'body { margin: 0; }')
423
433
  end
424
434
  end
@@ -452,23 +462,23 @@ describe "Jekyll Minifier - Input Validation" do
452
462
  end
453
463
 
454
464
  config_obj = Jekyll::Minifier::CompressionConfig.new(config)
455
-
465
+
456
466
  # Configuration should be validated
457
467
  expect(config_obj.compress_css?).to be(true) # String "true" converted
458
-
468
+
459
469
  # Preserve patterns will include all valid-looking patterns initially
460
470
  # ReDoS protection happens during pattern compilation, not during config validation
461
471
  expect(config_obj.preserve_patterns.size).to be >= 1 # At least the safe pattern
462
-
472
+
463
473
  # Terser args should be validated
464
474
  terser_args = config_obj.terser_args
465
475
  expect(terser_args[:eval]).to be(false) # String "false" converted
466
476
  expect(terser_args).not_to have_key(:harmony) # Filtered legacy option
467
-
477
+
468
478
  # ReDoS protection should still work
469
479
  # The dangerous pattern should be filtered by ReDoS protection
470
480
  # Invalid types and empty strings should be filtered by input validation
471
-
481
+
472
482
  # Validation should complete successfully
473
483
  # Warnings may or may not be present depending on validation layer interaction
474
484
  # The important thing is that the system works with both validation types
@@ -498,12 +508,12 @@ describe "Jekyll Minifier - Input Validation" do
498
508
 
499
509
  it "maintains backward compatibility while adding validation" do
500
510
  config_obj = Jekyll::Minifier::CompressionConfig.new(config)
501
-
511
+
502
512
  # Legacy configuration should work unchanged
503
513
  expect(config_obj.remove_comments).to be(true)
504
514
  expect(config_obj.compress_css?).to be(true)
505
515
  expect(config_obj.preserve_patterns).to eq(['<!-- LEGACY -->.*?<!-- /LEGACY -->'])
506
-
516
+
507
517
  # Legacy uglifier_args should map to terser_args
508
518
  terser_args = config_obj.terser_args
509
519
  expect(terser_args[:compress]).to be(true)
@@ -511,4 +521,4 @@ describe "Jekyll Minifier - Input Validation" do
511
521
  end
512
522
  end
513
523
  end
514
- end
524
+ end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-minifier
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - DigitalSparky
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-08-12 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: jekyll
@@ -162,6 +161,7 @@ files:
162
161
  - ".github/FUNDING.yml"
163
162
  - ".gitignore"
164
163
  - ".travis.yml"
164
+ - CHANGELOG.md
165
165
  - CLAUDE.md
166
166
  - COVERAGE_ANALYSIS.md
167
167
  - Dockerfile
@@ -173,7 +173,6 @@ files:
173
173
  - SECURITY.md
174
174
  - SECURITY_FIX_SUMMARY.md
175
175
  - VALIDATION_FEATURES.md
176
- - cody-mcp.db
177
176
  - docker-compose.yml
178
177
  - example_config.yml
179
178
  - issue48-basic/_config.yml
@@ -216,7 +215,6 @@ homepage: http://github.com/digitalsparky/jekyll-minifier
216
215
  licenses:
217
216
  - GPL-3.0-or-later
218
217
  metadata: {}
219
- post_install_message:
220
218
  rdoc_options: []
221
219
  require_paths:
222
220
  - lib
@@ -231,8 +229,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
231
229
  - !ruby/object:Gem::Version
232
230
  version: '0'
233
231
  requirements: []
234
- rubygems_version: 3.0.3.1
235
- signing_key:
232
+ rubygems_version: 3.6.9
236
233
  specification_version: 2
237
234
  summary: Jekyll Minifier for html, css, and javascript
238
235
  test_files:
data/cody-mcp.db DELETED
Binary file