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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7743996e98fff70baf3c82a0f2a26dfafb24cb0c9ddc46cfc6a3be5a1df4c92f
4
- data.tar.gz: 2b747045e71df0e69e7d6bdd3cb2b19309fcea16617770982b5743a4c6db9edc
3
+ metadata.gz: 8878ff3cfcfe50696d437b251a372f66d99a8098707e7cfd919554ec24d996fb
4
+ data.tar.gz: dbc5ae0b61c125a88fda3310d7dff72e0bb5a5d52d94ae863455ff607c5aab35
5
5
  SHA512:
6
- metadata.gz: f94e1f6feeda985b8c5091c0235181bb34165f4ee42120fa808b5b8a39cd8deaabc8ae9fe68a9c05d33598d184680d3b49c40f9791b1f0d2fb57689a87b73054
7
- data.tar.gz: 6d31b432dddc04f237bddd28e6580d294c9427a22b0fda2947a9e2d9e3aba469b2fd06cdb2e0560d8edb60f1383c6916ba407a7efb257202d7cb95e381514083
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
- "json_encode" => ->(v, *_a) { JSON.generate(v) rescue v.to_s },
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
- "to_json" => ->(v, *a) {
2429
- indent = a[0] ? a[0].to_i : nil
2430
- json = indent ? JSON.pretty_generate(v) : JSON.generate(v)
2431
- # Escape <, >, & for safe HTML embedding
2432
- Tina4::SafeString.new(json.gsub("<", '\u003c').gsub(">", '\u003e').gsub("&", '\u0026'))
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tina4
4
- VERSION = "3.13.87"
4
+ VERSION = "3.13.88"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tina4ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.13.87
4
+ version: 3.13.88
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tina4 Team