tina4ruby 3.13.87 → 3.13.88
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/lib/tina4/frond.rb +70 -12
- data/lib/tina4/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8878ff3cfcfe50696d437b251a372f66d99a8098707e7cfd919554ec24d996fb
|
|
4
|
+
data.tar.gz: dbc5ae0b61c125a88fda3310d7dff72e0bb5a5d52d94ae863455ff607c5aab35
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b51a87207d60bff1cd55241e23eb3b7325980752430bfef840ed8fce8e1ba491fbdf4096dd8369548112163ec40147b5e70874b3d6b66c174d82a5161e3f2062
|
|
7
|
+
data.tar.gz: 8b03d0f03526f60931fefafdf78f21e84b3e85dfb040af5106a1a7c26677e110fc21a2274b8d623bbbab60c23ba70661d8612ae02c43f024d29e02a82ca5e167
|
data/lib/tina4/frond.rb
CHANGED
|
@@ -429,6 +429,66 @@ module Tina4
|
|
|
429
429
|
str.to_s.gsub(HTML_ESCAPE_RE, HTML_ESCAPE_MAP)
|
|
430
430
|
end
|
|
431
431
|
|
|
432
|
+
# Serializes a value to compact JSON text that is always valid JSON.
|
|
433
|
+
#
|
|
434
|
+
# Never raises and never returns an empty string: a non-finite float becomes
|
|
435
|
+
# null (the JSON spec has no Infinity or NaN) and malformed UTF-8 is scrubbed,
|
|
436
|
+
# so a payload always arrives, in the worst case as null.
|
|
437
|
+
def self.json_text(value)
|
|
438
|
+
JSON.generate(value)
|
|
439
|
+
rescue StandardError
|
|
440
|
+
# Only reached when the happy path raised, so a well-formed payload never
|
|
441
|
+
# pays for the walk.
|
|
442
|
+
begin
|
|
443
|
+
JSON.generate(json_sanitize(value))
|
|
444
|
+
rescue StandardError
|
|
445
|
+
"null"
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
# Replaces anything JSON.generate refuses with a JSON-representable stand-in.
|
|
450
|
+
def self.json_sanitize(value)
|
|
451
|
+
case value
|
|
452
|
+
when Float then value.finite? ? value : nil
|
|
453
|
+
when Hash then value.transform_values { |item| json_sanitize(item) }
|
|
454
|
+
when Array then value.map { |item| json_sanitize(item) }
|
|
455
|
+
when String then value.valid_encoding? ? value : value.scrub
|
|
456
|
+
else value
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
# Serializes to JSON that is valid JSON, valid JavaScript, and safe in HTML.
|
|
461
|
+
#
|
|
462
|
+
# THE cross-framework contract for json_encode / to_json / tojson. Keep the
|
|
463
|
+
# four implementations byte-identical; frond_expression_corpus.txt locks it.
|
|
464
|
+
#
|
|
465
|
+
# Three things this must never do, each of which was a real bug:
|
|
466
|
+
#
|
|
467
|
+
# 1. Never emit a non-finite literal. JSON.generate raises on Infinity, and
|
|
468
|
+
# the old `rescue v.to_s` turned that raise into Ruby inspect output --
|
|
469
|
+
# `{"a" => 1.0}` -- which no JSON.parse will read. Reported as
|
|
470
|
+
# tina4-php#184 by justin-k-bruce, who hit the same class of bug in PHP.
|
|
471
|
+
# 2. Never emit nothing, and never emit something that still parses and means
|
|
472
|
+
# something else. "var ROWS = ;" is at least a loud SyntaxError.
|
|
473
|
+
# 3. Never HTML-escape it. Entity-encoding JSON produces {"a":1},
|
|
474
|
+
# a SyntaxError inside <script>, which is the filter's whole point. Escape
|
|
475
|
+
# only the dangerous characters, as JSON \uXXXX escapes: the result stays
|
|
476
|
+
# valid JSON AND valid JavaScript, </script> cannot terminate the block,
|
|
477
|
+
# and it is safe inside a single-quoted attribute. This is what Jinja2's
|
|
478
|
+
# tojson does, and it is why the result is a SafeString.
|
|
479
|
+
#
|
|
480
|
+
# U+2028 and U+2029 join that escape set. Both are legal inside a JSON string
|
|
481
|
+
# and both were illegal inside a JavaScript string literal before ES2019.
|
|
482
|
+
def self.json_safe(value)
|
|
483
|
+
Tina4::SafeString.new(json_text(value).gsub(JSON_ESCAPE_RE, JSON_ESCAPE_MAP))
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
JSON_ESCAPE_MAP = {
|
|
487
|
+
"<" => "\\u003c", ">" => "\\u003e", "&" => "\\u0026", "'" => "\\u0027",
|
|
488
|
+
"\u2028" => "\\u2028", "\u2029" => "\\u2029"
|
|
489
|
+
}.freeze
|
|
490
|
+
JSON_ESCAPE_RE = /[<>&'\u2028\u2029]/
|
|
491
|
+
|
|
432
492
|
private
|
|
433
493
|
|
|
434
494
|
# Keep a memo cache bounded (ADR-0004). Call immediately before inserting
|
|
@@ -2406,7 +2466,10 @@ module Tina4
|
|
|
2406
2466
|
"e" => ->(v, *_a) { Tina4::SafeString.new(Frond.escape_html(v.to_s)) },
|
|
2407
2467
|
"raw" => ->(v, *_a) { v },
|
|
2408
2468
|
"safe" => ->(v, *_a) { v },
|
|
2409
|
-
|
|
2469
|
+
# Frond.json_safe, never a bare JSON.generate: generate RAISES on an
|
|
2470
|
+
# Infinity/NaN float, and the old `rescue v.to_s` answered that raise with
|
|
2471
|
+
# Ruby inspect output that no JSON.parse will read. See Frond.json_safe.
|
|
2472
|
+
"json_encode" => ->(v, *_a) { Frond.json_safe(v) },
|
|
2410
2473
|
"json_decode" => ->(v, *_a) { v.is_a?(String) ? (JSON.parse(v) rescue v) : v },
|
|
2411
2474
|
"base64_encode" => ->(v, *_a) { Base64.strict_encode64(v.is_a?(String) ? v : v.to_s) },
|
|
2412
2475
|
"base64encode" => ->(v, *_a) { Base64.strict_encode64(v.is_a?(String) ? v : v.to_s) },
|
|
@@ -2425,17 +2488,12 @@ module Tina4
|
|
|
2425
2488
|
"url_encode" => ->(v, *_a) { ERB::Util.url_encode(v.to_s) },
|
|
2426
2489
|
|
|
2427
2490
|
# -- JSON / JS --
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
},
|
|
2434
|
-
"tojson" => ->(v, *a) {
|
|
2435
|
-
indent = a[0] ? a[0].to_i : nil
|
|
2436
|
-
json = indent ? JSON.pretty_generate(v) : JSON.generate(v)
|
|
2437
|
-
Tina4::SafeString.new(json.gsub("<", '\u003c').gsub(">", '\u003e').gsub("&", '\u0026'))
|
|
2438
|
-
},
|
|
2491
|
+
# Same serializer as json_encode -- the three names are one behaviour.
|
|
2492
|
+
# The old indent argument is gone: PHP cannot honour an arbitrary indent
|
|
2493
|
+
# (JSON_PRETTY_PRINT is fixed at four spaces), so honouring it here alone
|
|
2494
|
+
# broke byte-parity for the one filter whose whole job is a wire format.
|
|
2495
|
+
"to_json" => ->(v, *_a) { Frond.json_safe(v) },
|
|
2496
|
+
"tojson" => ->(v, *_a) { Frond.json_safe(v) },
|
|
2439
2497
|
"js_escape" => ->(v, *_a) {
|
|
2440
2498
|
Tina4::SafeString.new(
|
|
2441
2499
|
v.to_s.gsub("\\", "\\\\").gsub("'", "\\'").gsub('"', '\\"')
|
data/lib/tina4/version.rb
CHANGED