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 +4 -4
- data/.gitignore +14 -0
- data/CHANGELOG.md +52 -0
- data/lib/jekyll-minifier/version.rb +1 -1
- data/lib/jekyll-minifier.rb +100 -182
- data/spec/environment_validation_spec.rb +22 -14
- data/spec/input_validation_spec.rb +69 -59
- metadata +4 -7
- data/cody-mcp.db +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1f83fb1bf20cca0322484c8871091bd3ec548ea0e0011d7b77bf64e9f2b8662a
|
4
|
+
data.tar.gz: 11224edc99cf216907c72bbd7f62f19963eb8df7d27d056732dd1147a991922a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b9cd2eafb30ed5130fd0ec29f73ddc43fb30af5264e0463b321cc70e0ffa96ac82d8f5b584631814f9e651cb179655878423dd6265ba74f1f515c35c27eee413
|
7
|
+
data.tar.gz: f112bc19ee2f0556eec29aa08f221a72e1a9ccfba5f7f0a817242948250801532c132a1d38d0eef5c3260f2a54fe551e3302cc5af72e32c3215e7506ff8db481
|
data/.gitignore
CHANGED
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.
|
data/lib/jekyll-minifier.rb
CHANGED
@@ -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
|
-
#
|
192
|
-
|
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
|
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
|
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
|
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
|
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 "
|
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
|
60
|
-
#
|
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 "
|
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
|
-
|
154
|
-
expect(validator.validate_file_content(malformed_css, 'css', 'bad.css')).to be(
|
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 "
|
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
|
-
|
163
|
-
expect(validator.validate_file_content(malformed_js, 'js', 'bad.js')).to be(
|
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 "
|
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
|
-
|
172
|
-
expect(validator.validate_file_content(invalid_json, 'json', 'bad.json')).to be(
|
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' * (
|
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
|
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 "
|
371
|
+
it "delegates CSS validation to the minifier library" do
|
364
372
|
malformed_css = 'body { margin: 0; ' + '{' * 150
|
365
|
-
|
366
|
-
|
367
|
-
|
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.
|
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:
|
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.
|
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
|