From 19f33c6ff164ecd3e4b64515cde73d9c3eb00f4f Mon Sep 17 00:00:00 2001 From: Tom Lehman Date: Mon, 13 Oct 2025 14:35:21 -0400 Subject: [PATCH 1/2] Fix data uri normalization bug --- lib/data_uri.rb | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/data_uri.rb b/lib/data_uri.rb index ae33178..80398b9 100644 --- a/lib/data_uri.rb +++ b/lib/data_uri.rb @@ -13,7 +13,17 @@ class DataUri attr_reader :uri, :match def initialize(uri) - match = REGEXP.match(uri) + unless uri.is_a?(String) + raise ArgumentError, 'invalid data URI' + end + + normalized_uri = uri + + if normalized_uri.start_with?("data:,") + normalized_uri = normalized_uri.sub("data:,", "data:text/plain,") + end + + match = REGEXP.match(normalized_uri) raise ArgumentError, 'invalid data URI' unless match @uri = uri @@ -66,18 +76,10 @@ def base64? end def mimetype - if String(match[:mimetype]).empty? || uri.starts_with?("data:,") - return 'text/plain' - end - match[:mimetype] end def data - # Special case: if it's "data:," with no mediatype, return everything after comma - if uri.start_with?("data:,") - return uri[6..-1] # Everything after "data:," - end match[:data] end From 3922db38acecbdcf06c5161dd6b654385da11c8b Mon Sep 17 00:00:00 2001 From: Tom Lehman Date: Mon, 13 Oct 2025 14:53:47 -0400 Subject: [PATCH 2/2] Simplify --- Gemfile | 1 + Gemfile.lock | 11 +++ app/models/collections_params_extractor.rb | 7 +- app/models/generic_protocol_extractor.rb | 9 +- lib/data_uri.rb | 95 ---------------------- 5 files changed, 25 insertions(+), 98 deletions(-) delete mode 100644 lib/data_uri.rb diff --git a/Gemfile b/Gemfile index cbc9fee..c5f47d0 100644 --- a/Gemfile +++ b/Gemfile @@ -65,6 +65,7 @@ gem "memoist", "~> 0.16.2" gem "awesome_print", "~> 1.9" +gem 'facet_rails_common', git: 'https://github.com/0xfacet/facet_rails_common.git' gem "redis", "~> 5.0" diff --git a/Gemfile.lock b/Gemfile.lock index dadf792..ef643b7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -14,6 +14,13 @@ GIT rbsecp256k1 (~> 6.0) scrypt (~> 3.0) +GIT + remote: https://github.com/0xfacet/facet_rails_common.git + revision: 2d5cf107df9ea12bb7ab59a8b1ce4d38b4de483b + specs: + facet_rails_common (0.1.0) + order_query (~> 0.5.3) + GEM remote: https://rubygems.org/ specs: @@ -216,6 +223,9 @@ GEM bigdecimal (>= 3.0) ostruct (>= 0.2) openssl (3.3.0) + order_query (0.5.5) + activerecord (>= 5.0, < 8.1) + activesupport (>= 5.0, < 8.1) ostruct (0.6.3) parallel (1.27.0) parser (3.2.2.4) @@ -406,6 +416,7 @@ DEPENDENCIES debug dotenv-rails (~> 2.8) eth! + facet_rails_common! fastlz (~> 0.1.0) httparty (~> 0.22.0) jwt (~> 2.8) diff --git a/app/models/collections_params_extractor.rb b/app/models/collections_params_extractor.rb index 9141faa..2fec0b9 100644 --- a/app/models/collections_params_extractor.rb +++ b/app/models/collections_params_extractor.rb @@ -101,7 +101,12 @@ def extract(content_uri) begin # Parse JSON (preserves key order) # Use DataUri to correctly handle optional parameters like ESIP6 - json_str = DataUri.new(content_uri).decoded_data + json_str = if content_uri.start_with?("data:,{") + content_uri.sub(/\Adata:,/, '') + else + DataUri.new(content_uri).decoded_data + end + # TODO: make sure this is safe data = JSON.parse(json_str) diff --git a/app/models/generic_protocol_extractor.rb b/app/models/generic_protocol_extractor.rb index 002b39b..6859808 100644 --- a/app/models/generic_protocol_extractor.rb +++ b/app/models/generic_protocol_extractor.rb @@ -26,8 +26,11 @@ def extract(content_uri) return DEFAULT_PARAMS unless valid_data_uri?(content_uri) begin # Extract JSON from data URI using DataUri to support optional params (e.g., ESIP6) - json_str = DataUri.new(content_uri).decoded_data - + json_str = if content_uri.start_with?("data:,{") + content_uri.sub(/\Adata:,/, '') + else + DataUri.new(content_uri).decoded_data + end # Parse with security checks data = parse_json_safely(json_str) @@ -57,6 +60,8 @@ def valid_data_uri?(uri) return false unless uri.is_a?(String) return false unless DataUri.valid?(uri) + return true if uri.start_with?("data:,{") + # Ensure the payload is JSON (starts with '{') begin payload = DataUri.new(uri).decoded_data diff --git a/lib/data_uri.rb b/lib/data_uri.rb deleted file mode 100644 index 80398b9..0000000 --- a/lib/data_uri.rb +++ /dev/null @@ -1,95 +0,0 @@ -class DataUri - REGEXP = %r{ - \Adata: - (? - (? .+? / .+? )? - (? (?: ; .+? = .+? )* ) - )? - (?;base64)? - , - (?.*) - }x.freeze - - attr_reader :uri, :match - - def initialize(uri) - unless uri.is_a?(String) - raise ArgumentError, 'invalid data URI' - end - - normalized_uri = uri - - if normalized_uri.start_with?("data:,") - normalized_uri = normalized_uri.sub("data:,", "data:text/plain,") - end - - match = REGEXP.match(normalized_uri) - raise ArgumentError, 'invalid data URI' unless match - - @uri = uri - @match = match - - validate_base64_content - end - - def self.valid?(uri) - begin - DataUri.new(uri) - true - rescue ArgumentError - false - end - end - - def self.esip6?(uri) - begin - parameters = DataUri.new(uri).parameters - - parameters.include?("rule=esip6") - rescue ArgumentError - false - end - end - - def validate_base64_content - if base64? - begin - Base64.strict_decode64(data) - rescue ArgumentError - raise ArgumentError, 'malformed base64 content' - end - end - end - - def mediatype - "#{mimetype}#{parameters}" - end - - def decoded_data - return data unless base64? - - Base64.decode64(data) - end - - def base64? - !String(extension).empty? - end - - def mimetype - match[:mimetype] - end - - def data - match[:data] - end - - def parameters - return [] if String(match[:mimetype]).empty? && String(match[:parameters]).empty? - - match[:parameters].split(";").reject(&:empty?) - end - - def extension - match[:extension] - end -end