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 ae33178..0000000 --- a/lib/data_uri.rb +++ /dev/null @@ -1,93 +0,0 @@ -class DataUri - REGEXP = %r{ - \Adata: - (? - (? .+? / .+? )? - (? (?: ; .+? = .+? )* ) - )? - (?;base64)? - , - (?.*) - }x.freeze - - attr_reader :uri, :match - - def initialize(uri) - match = REGEXP.match(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 - 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 - - def parameters - return [] if String(match[:mimetype]).empty? && String(match[:parameters]).empty? - - match[:parameters].split(";").reject(&:empty?) - end - - def extension - match[:extension] - end -end